From 36b0796976060edbb748acc4470018a83a923858 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 5 Dec 2025 23:01:37 +0000 Subject: [PATCH] fix: handle prompt-too-long by resetting session and continuing inline directives --- src/auto-reply/command-reply.test.ts | 29 +++++++++++++++++++++ src/auto-reply/command-reply.ts | 38 ++++++++++++++++++++-------- src/auto-reply/reply.ts | 20 +++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/auto-reply/command-reply.test.ts b/src/auto-reply/command-reply.test.ts index bc7c49868..5fb5abe87 100644 --- a/src/auto-reply/command-reply.test.ts +++ b/src/auto-reply/command-reply.test.ts @@ -251,6 +251,35 @@ describe("runCommandReply (pi)", () => { expect(meta.killed).toBe(true); }); + it("surfaces prompt-too-long and flags meta for session reset", async () => { + mockPiRpc({ + stdout: + '{"type":"agent_end","message":{"role":"assistant","content":[],"errorMessage":"400 {\\"type\\":\\"error\\",\\"error\\":{\\"type\\":\\"invalid_request_error\\",\\"message\\":\\"prompt is too long: 200333 tokens > 200000 maximum\\"}}"}}', + stderr: "", + code: 0, + }); + + const { payloads, meta } = await runCommandReply({ + reply: { + mode: "command", + command: ["pi", "{{Body}}"], + agent: { kind: "pi" }, + }, + templatingCtx: noopTemplateCtx, + sendSystemOnce: false, + isNewSession: false, + isFirstTurnInSession: false, + systemSent: false, + timeoutMs: 1000, + timeoutSeconds: 1, + commandRunner: vi.fn(), + enqueue: enqueueImmediate, + }); + + expect(payloads?.[0]?.text).toMatch(/history is too long/i); + expect(meta.agentMeta?.extra?.promptTooLong).toBe(true); + }); + it("collapses rpc deltas instead of emitting raw JSON spam", async () => { mockPiRpc({ stdout: [ diff --git a/src/auto-reply/command-reply.ts b/src/auto-reply/command-reply.ts index b1c01a9d5..a99745862 100644 --- a/src/auto-reply/command-reply.ts +++ b/src/auto-reply/command-reply.ts @@ -677,18 +677,19 @@ export async function runCommandReply( } }, }); - const rawStdout = stdout.trim(); - const rpcAssistantText = extractRpcAssistantText(stdout); - let mediaFromCommand: string[] | undefined; - const trimmed = stripRpcNoise(rawStdout); - if (stderr?.trim()) { - logVerbose(`Command auto-reply stderr: ${stderr.trim()}`); - } + const rawStdout = stdout.trim(); + const rpcAssistantText = extractRpcAssistantText(stdout); + let mediaFromCommand: string[] | undefined; + const trimmed = stripRpcNoise(rawStdout); + if (stderr?.trim()) { + logVerbose(`Command auto-reply stderr: ${stderr.trim()}`); + } + const promptTooLong = rawStdout.includes("prompt is too long"); - const logFailure = () => { - const truncate = (s?: string) => - s ? (s.length > 4000 ? `${s.slice(0, 4000)}…` : s) : undefined; - logger.warn( + const logFailure = () => { + const truncate = (s?: string) => + s ? (s.length > 4000 ? `${s.slice(0, 4000)}…` : s) : undefined; + logger.warn( { code, signal, @@ -702,6 +703,21 @@ export async function runCommandReply( ); }; + if (promptTooLong) { + const text = + "⚠️ Session history is too long. Starting a fresh session — please resend your last message."; + const meta: CommandReplyMeta = { + durationMs: Date.now() - started, + queuedMs, + queuedAhead, + exitCode: code, + signal, + killed, + agentMeta: { extra: { promptTooLong: true } }, + }; + return { payloads: [{ text }], meta }; + } + const parsed = trimmed ? agent.parseOutput(trimmed) : undefined; // Collect assistant texts and tool results from parseOutput (tau RPC can emit many). diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index 3e1257159..3ddced8f4 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -701,6 +701,26 @@ export async function getReplyFromConfig( }); const payloadArray = runResult.payloads ?? []; const meta = runResult.meta; + const promptTooLong = + meta.agentMeta && + typeof meta.agentMeta.extra === "object" && + (meta.agentMeta.extra as { promptTooLong?: boolean }).promptTooLong; + + if (promptTooLong && sessionCfg && sessionStore && sessionKey) { + // Rotate to a new session to avoid hitting context limits again. + const newId = crypto.randomUUID(); + sessionEntry = { + sessionId: newId, + updatedAt: Date.now(), + systemSent, + abortedLastRun: false, + }; + sessionStore[sessionKey] = sessionEntry; + await saveSessionStore(storePath, sessionStore); + sessionId = newId; + isNewSession = true; + } + let finalPayloads = payloadArray; if (!finalPayloads || finalPayloads.length === 0) { return undefined;