diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.e2e.test.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.e2e.test.ts index d9ce20b07e9..20097404db5 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.e2e.test.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.e2e.test.ts @@ -354,6 +354,22 @@ describe("overflow compaction in run loop", () => { expect(log.warn).not.toHaveBeenCalledWith(expect.stringContaining("source=assistantError")); }); + it("returns an explicit timeout payload when the run times out before producing any reply", async () => { + mockedRunEmbeddedAttempt.mockResolvedValue( + makeAttemptResult({ + aborted: true, + timedOut: true, + timedOutDuringCompaction: false, + assistantTexts: [], + }), + ); + + const result = await runEmbeddedPiAgent(baseParams); + + expect(result.payloads?.[0]?.isError).toBe(true); + expect(result.payloads?.[0]?.text).toContain("timed out"); + }); + it("sets promptTokens from the latest model call usage, not accumulated attempt usage", async () => { mockedRunEmbeddedAttempt.mockResolvedValue( makeAttemptResult({ diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index 74a07e37f09..666431ffa7e 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -907,6 +907,31 @@ export async function runEmbeddedPiAgent( inlineToolResultsAllowed: false, }); + // Timeout aborts can leave the run without any assistant payloads. + // Emit an explicit timeout error instead of silently completing, so + // callers do not lose the turn as an orphaned user message. + if (timedOut && !timedOutDuringCompaction && payloads.length === 0) { + return { + payloads: [ + { + text: + "Request timed out before a response was generated. " + + "Please try again, or increase `agents.defaults.timeoutSeconds` in your config.", + isError: true, + }, + ], + meta: { + durationMs: Date.now() - started, + agentMeta, + aborted, + systemPromptReport: attempt.systemPromptReport, + }, + didSendViaMessagingTool: attempt.didSendViaMessagingTool, + messagingToolSentTexts: attempt.messagingToolSentTexts, + messagingToolSentTargets: attempt.messagingToolSentTargets, + }; + } + log.debug( `embedded run done: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - started} aborted=${aborted}`, );