diff --git a/src/agents/pi-embedded-runner/run/payloads.ts b/src/agents/pi-embedded-runner/run/payloads.ts index b59315683..be7ca41f5 100644 --- a/src/agents/pi-embedded-runner/run/payloads.ts +++ b/src/agents/pi-embedded-runner/run/payloads.ts @@ -170,7 +170,11 @@ export function buildEmbeddedRunPayloads(params: { errorLower.includes("needs") || errorLower.includes("requires"); - if (!hasUserFacingReply || !isRecoverableError) { + // Show tool errors only when: + // 1. There's no user-facing reply AND the error is not recoverable + // Recoverable errors (validation, missing params) are already in the model's context + // and shouldn't be surfaced to users since the model should retry. + if (!hasUserFacingReply && !isRecoverableError) { const toolSummary = formatToolAggregate( params.lastToolError.toolName, params.lastToolError.meta ? [params.lastToolError.meta] : undefined, @@ -182,8 +186,6 @@ export function buildEmbeddedRunPayloads(params: { isError: true, }); } - // Note: Recoverable errors are already in the model's context as tool_result is_error. - // We only suppress them when a user-facing reply already exists. } const hasAudioAsVoiceTag = replyItems.some((item) => item.audioAsVoice); diff --git a/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.signals-typing-normal-runs.test.ts b/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.signals-typing-normal-runs.test.ts index d5a984a42..4040c6dc5 100644 --- a/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.signals-typing-normal-runs.test.ts +++ b/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.signals-typing-normal-runs.test.ts @@ -179,7 +179,7 @@ describe("runReplyAgent typing (heartbeat)", () => { expect(typing.startTypingOnText).not.toHaveBeenCalled(); expect(typing.startTypingLoop).not.toHaveBeenCalled(); }); - it("starts typing on assistant message start in message mode", async () => { + it("does not start typing on assistant message start without prior text in message mode", async () => { runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedPiAgentParams) => { await params.onAssistantMessageStart?.(); return { payloads: [{ text: "final" }], meta: {} }; @@ -190,7 +190,8 @@ describe("runReplyAgent typing (heartbeat)", () => { }); await run(); - expect(typing.startTypingLoop).toHaveBeenCalled(); + // Typing only starts when there's actual renderable text, not on message start alone + expect(typing.startTypingLoop).not.toHaveBeenCalled(); expect(typing.startTypingOnText).not.toHaveBeenCalled(); }); it("starts typing from reasoning stream in thinking mode", async () => {