From 31ecbbd5bf765b41d49806a0cb136192fab3bb33 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 27 May 2026 09:31:35 +0200 Subject: [PATCH] fix(agents): ignore failed subagent placeholders --- src/agents/subagent-announce-delivery.test.ts | 35 +++++++++++++++++++ src/agents/subagent-announce-delivery.ts | 24 ++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/agents/subagent-announce-delivery.test.ts b/src/agents/subagent-announce-delivery.test.ts index 01ff08b7d495..cb36fa9fa906 100644 --- a/src/agents/subagent-announce-delivery.test.ts +++ b/src/agents/subagent-announce-delivery.test.ts @@ -1003,6 +1003,41 @@ describe("deliverSubagentAnnouncement completion delivery", () => { ); }); + it("does not directly deliver failed subagent placeholder output", async () => { + const callGateway = createGatewayMock({ + result: { + payloads: [], + }, + }); + const sendMessage = createSendMessageMock(); + + const result = await deliverDiscordDirectMessageCompletion({ + callGateway, + sendMessage, + internalEvents: [ + { + type: "task_completion", + source: "subagent", + childSessionKey: "agent:worker:subagent:child", + childSessionId: "child-session-id", + announceType: "subagent task", + taskLabel: "direct completion smoke", + status: "error", + statusLabel: "failed: all models failed", + result: "(no output)", + replyInstruction: "Summarize the result.", + }, + ], + }); + + expectRecordFields(result, { + delivered: false, + path: "direct", + error: "completion agent did not produce a visible reply", + }); + expect(sendMessage).not.toHaveBeenCalled(); + }); + it("directly delivers unprefixed direct targets recognized by the channel grammar", async () => { registerDirectTargetTestChannel("qa-channel"); const callGateway = createGatewayMock({ diff --git a/src/agents/subagent-announce-delivery.ts b/src/agents/subagent-announce-delivery.ts index 7d0d8ae0cb6b..32198d28284d 100644 --- a/src/agents/subagent-announce-delivery.ts +++ b/src/agents/subagent-announce-delivery.ts @@ -767,14 +767,29 @@ function resolveTextCompletionDirectFallback(events: readonly AgentInternalEvent if (event?.type !== "task_completion" || event.source !== "subagent") { continue; } + if (event.status !== "ok") { + continue; + } const result = typeof event.result === "string" ? event.result.trim() : ""; - if (result) { + if (result && result !== "(no output)") { return result; } } return undefined; } +function hasFailedSubagentNoOutputCompletion(events: readonly AgentInternalEvent[] | undefined) { + return ( + events?.some( + (event) => + event.type === "task_completion" && + event.source === "subagent" && + event.status !== "ok" && + event.result.trim() === "(no output)", + ) === true + ); +} + async function deliverTextCompletionDirect(params: { cfg: OpenClawConfig; requesterSessionKey: string; @@ -1190,6 +1205,13 @@ async function sendSubagentAnnounceDirectly(params: { if (textDelivery) { return textDelivery; } + if (hasFailedSubagentNoOutputCompletion(params.internalEvents)) { + return { + delivered: false, + path: "direct", + error: "completion agent did not produce a visible reply", + }; + } } if ( params.expectsCompletionMessage &&