From ae0d35c727eb5fd9dda3f7a4e7d418e49cb37965 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 3 Dec 2025 09:07:17 +0000 Subject: [PATCH] Auto-reply: add verbose session hint --- src/auto-reply/reply.ts | 100 ++++++++++++++++++++++------------------ src/index.core.test.ts | 36 +++++++++++++++ 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index 2d6f95d83..09310fa0a 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -489,11 +489,11 @@ export async function getReplyFromConfig( const isHeartbeat = opts?.isHeartbeat === true; - if (reply && reply.mode === "command") { - const heartbeatCommand = isHeartbeat - ? (reply as { heartbeatCommand?: string[] }).heartbeatCommand - : undefined; - const commandArgs = heartbeatCommand?.length + if (reply && reply.mode === "command") { + const heartbeatCommand = isHeartbeat + ? (reply as { heartbeatCommand?: string[] }).heartbeatCommand + : undefined; + const commandArgs = heartbeatCommand?.length ? heartbeatCommand : reply.command; @@ -502,38 +502,34 @@ export async function getReplyFromConfig( return undefined; } - await onReplyStart(); - const commandReply = { - ...reply, - command: commandArgs, - mode: "command" as const, - }; - try { - const runResult = await runCommandReply({ - reply: commandReply, - templatingCtx, - sendSystemOnce, - isNewSession, - isFirstTurnInSession, - systemSent, - timeoutMs, - timeoutSeconds, - commandRunner, - thinkLevel: resolvedThinkLevel, - verboseLevel: resolvedVerboseLevel, - }); - const payloadArray = runResult.payloads ?? []; - const meta = runResult.meta; - const normalizedPayloads = - payloadArray.length === 1 ? payloadArray[0] : payloadArray; - if ( - !normalizedPayloads || - (Array.isArray(normalizedPayloads) && normalizedPayloads.length === 0) - ) { - return undefined; - } - if (sessionCfg && sessionStore && sessionKey) { - const returnedSessionId = meta.agentMeta?.sessionId; + await onReplyStart(); + const commandReply = { + ...reply, + command: commandArgs, + mode: "command" as const, + }; + try { + const runResult = await runCommandReply({ + reply: commandReply, + templatingCtx, + sendSystemOnce, + isNewSession, + isFirstTurnInSession, + systemSent, + timeoutMs, + timeoutSeconds, + commandRunner, + thinkLevel: resolvedThinkLevel, + verboseLevel: resolvedVerboseLevel, + }); + const payloadArray = runResult.payloads ?? []; + const meta = runResult.meta; + let finalPayloads = payloadArray; + if (!finalPayloads || finalPayloads.length === 0) { + return undefined; + } + if (sessionCfg && sessionStore && sessionKey) { + const returnedSessionId = meta.agentMeta?.sessionId; if (returnedSessionId && returnedSessionId !== sessionId) { const entry = sessionEntry ?? sessionStore[sessionKey] ?? { @@ -557,14 +553,28 @@ export async function getReplyFromConfig( } } } - if (meta.agentMeta && isVerbose()) { - logVerbose(`Agent meta: ${JSON.stringify(meta.agentMeta)}`); - } - return normalizedPayloads; - } finally { - cleanupTyping(); - } - } + if (meta.agentMeta && isVerbose()) { + logVerbose(`Agent meta: ${JSON.stringify(meta.agentMeta)}`); + } + // If verbose is enabled and this is a new session, prepend a session hint. + const sessionIdHint = + resolvedVerboseLevel === "on" && isNewSession + ? sessionId ?? + meta.agentMeta?.sessionId ?? + templatingCtx.SessionId ?? + "unknown" + : undefined; + if (sessionIdHint) { + finalPayloads = [ + { text: `🧭 New session: ${sessionIdHint}` }, + ...payloadArray, + ]; + } + return finalPayloads.length === 1 ? finalPayloads[0] : finalPayloads; + } finally { + cleanupTyping(); + } + } cleanupTyping(); return undefined; diff --git a/src/index.core.test.ts b/src/index.core.test.ts index a6ca9420a..52d836a66 100644 --- a/src/index.core.test.ts +++ b/src/index.core.test.ts @@ -750,6 +750,42 @@ describe("config and templating", () => { ); }); + it("prepends session hint when new session and verbose on", async () => { + const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({ + stdout: "ok", + stderr: "", + code: 0, + signal: null, + killed: false, + }); + vi.spyOn(crypto, "randomUUID").mockReturnValue("sess-uuid"); + const storeDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), "warelay-session-"), + ); + const storePath = path.join(storeDir, "sessions.json"); + const cfg = { + inbound: { + reply: { + mode: "command" as const, + command: ["echo", "{{Body}}"], + agent: { kind: "claude" }, + session: { store: storePath }, + }, + }, + }; + + const res = await index.getReplyFromConfig( + { Body: "/new /v on hi", From: "+1", To: "+2" }, + undefined, + cfg, + runSpy, + ); + + const payloads = Array.isArray(res) ? res : res ? [res] : []; + expect(payloads[0]?.text).toBe("🧭 New session: sess-uuid"); + expect(payloads[1]?.text).toBe("ok"); + }); + it("treats directive-only even when bracket prefixes are present", async () => { const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({ stdout: "ok",