Skip to content

Commit 7315914

Browse files
committed
fix(acp): suppress commentary relay leakage
1 parent 43bd554 commit 7315914

4 files changed

Lines changed: 92 additions & 3 deletions

src/agents/acp-spawn-parent-stream.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,66 @@ describe("startAcpSpawnParentStreamRelay", () => {
290290
relay.dispose();
291291
});
292292

293+
it("suppresses commentary-phase assistant relay text", () => {
294+
const relay = startAcpSpawnParentStreamRelay({
295+
runId: "run-commentary",
296+
parentSessionKey: "agent:main:main",
297+
childSessionKey: "agent:codex:acp:child-commentary",
298+
agentId: "codex",
299+
streamFlushMs: 10,
300+
noOutputNoticeMs: 120_000,
301+
});
302+
303+
emitAgentEvent({
304+
runId: "run-commentary",
305+
stream: "assistant",
306+
data: {
307+
delta: "checking thread context; then post a tight progress reply here.",
308+
phase: "commentary",
309+
},
310+
});
311+
vi.advanceTimersByTime(15);
312+
313+
const texts = collectedTexts();
314+
expect(texts.some((text) => text.includes("checking thread context"))).toBe(false);
315+
expect(texts.some((text) => text.includes("post a tight progress reply here"))).toBe(false);
316+
relay.dispose();
317+
});
318+
319+
it("still relays final_answer assistant text after suppressed commentary", () => {
320+
const relay = startAcpSpawnParentStreamRelay({
321+
runId: "run-final",
322+
parentSessionKey: "agent:main:main",
323+
childSessionKey: "agent:codex:acp:child-final",
324+
agentId: "codex",
325+
streamFlushMs: 10,
326+
noOutputNoticeMs: 120_000,
327+
});
328+
329+
emitAgentEvent({
330+
runId: "run-final",
331+
stream: "assistant",
332+
data: {
333+
delta: "checking thread context; then post a tight progress reply here.",
334+
phase: "commentary",
335+
},
336+
});
337+
emitAgentEvent({
338+
runId: "run-final",
339+
stream: "assistant",
340+
data: {
341+
delta: "final answer ready",
342+
phase: "final_answer",
343+
},
344+
});
345+
vi.advanceTimersByTime(15);
346+
347+
const texts = collectedTexts();
348+
expect(texts.some((text) => text.includes("checking thread context"))).toBe(false);
349+
expect(texts.some((text) => text.includes("codex: final answer ready"))).toBe(true);
350+
relay.dispose();
351+
});
352+
293353
it("resolves ACP spawn stream log path from session metadata", () => {
294354
readAcpSessionEntryMock.mockReturnValue({
295355
storePath: "/tmp/openclaw/agents/codex/sessions/sessions.json",

src/agents/acp-spawn-parent-stream.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { onAgentEvent } from "../infra/agent-events.js";
66
import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
77
import { enqueueSystemEvent } from "../infra/system-events.js";
88
import { scopedHeartbeatWakeOptions } from "../routing/session-key.js";
9+
import { normalizeAssistantPhase } from "../shared/chat-message-content.js";
910
import { normalizeOptionalString } from "../shared/string-coerce.js";
1011
import { recordTaskRunProgressByRunId } from "../tasks/task-executor.js";
1112
import { type DeliveryContext } from "../utils/delivery-context.js";
@@ -310,14 +311,25 @@ export function startAcpSpawnParentStreamRelay(params: {
310311

311312
if (event.stream === "assistant") {
312313
const data = event.data;
314+
const assistantPhase = normalizeAssistantPhase(
315+
(data as { phase?: unknown } | undefined)?.phase,
316+
);
313317
const deltaCandidate =
314318
(data as { delta?: unknown } | undefined)?.delta ??
315319
(data as { text?: unknown } | undefined)?.text;
316320
const delta = typeof deltaCandidate === "string" ? deltaCandidate : undefined;
317321
if (!delta || !delta.trim()) {
318322
return;
319323
}
320-
logEvent("assistant_delta", { delta });
324+
logEvent("assistant_delta", {
325+
delta,
326+
...(assistantPhase ? { phase: assistantPhase } : {}),
327+
});
328+
329+
if (assistantPhase === "commentary") {
330+
lastProgressAt = Date.now();
331+
return;
332+
}
321333

322334
if (stallNotified) {
323335
stallNotified = false;

src/agents/pi-embedded-subscribe.handlers.messages.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,14 @@ describe("buildAssistantStreamData", () => {
178178
delta: "he",
179179
replace: true,
180180
mediaUrl: "https://example.com/a.png",
181+
phase: "final_answer",
181182
}),
182183
).toEqual({
183184
text: "hello",
184185
delta: "he",
185186
replace: true,
186187
mediaUrls: ["https://example.com/a.png"],
188+
phase: "final_answer",
187189
});
188190
});
189191
});

src/agents/pi-embedded-subscribe.handlers.messages.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { parseReplyDirectives } from "../auto-reply/reply/reply-directives.js";
55
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
66
import { emitAgentEvent } from "../infra/agent-events.js";
77
import { createInlineCodeState } from "../markdown/code-spans.js";
8-
import { resolveAssistantMessagePhase } from "../shared/chat-message-content.js";
8+
import {
9+
resolveAssistantMessagePhase,
10+
type AssistantPhase,
11+
} from "../shared/chat-message-content.js";
912
import { normalizeOptionalString } from "../shared/string-coerce.js";
1013
import {
1114
isMessagingToolDuplicateNormalized,
@@ -165,13 +168,21 @@ export function buildAssistantStreamData(params: {
165168
replace?: boolean;
166169
mediaUrls?: string[];
167170
mediaUrl?: string;
168-
}): { text: string; delta: string; replace?: true; mediaUrls?: string[] } {
171+
phase?: AssistantPhase;
172+
}): {
173+
text: string;
174+
delta: string;
175+
replace?: true;
176+
mediaUrls?: string[];
177+
phase?: AssistantPhase;
178+
} {
169179
const mediaUrls = resolveSendableOutboundReplyParts(params).mediaUrls;
170180
return {
171181
text: params.text ?? "",
172182
delta: params.delta ?? "",
173183
replace: params.replace ? true : undefined,
174184
mediaUrls: mediaUrls.length ? mediaUrls : undefined,
185+
phase: params.phase,
175186
};
176187
}
177188

@@ -212,6 +223,7 @@ export function handleMessageUpdate(
212223
ctx.state.deterministicApprovalPromptPending || ctx.state.deterministicApprovalPromptSent;
213224

214225
const assistantEvent = evt.assistantMessageEvent;
226+
const assistantPhase = resolveAssistantMessagePhase(msg);
215227
const assistantRecord =
216228
assistantEvent && typeof assistantEvent === "object"
217229
? (assistantEvent as Record<string, unknown>)
@@ -388,6 +400,7 @@ export function handleMessageUpdate(
388400
delta: deltaText,
389401
replace,
390402
mediaUrls,
403+
phase: assistantPhase,
391404
});
392405
emitAgentEvent({
393406
runId: ctx.params.runId,
@@ -440,6 +453,7 @@ export function handleMessageEnd(
440453
}
441454

442455
const assistantMessage = msg;
456+
const assistantPhase = resolveAssistantMessagePhase(assistantMessage);
443457
const suppressVisibleAssistantOutput = shouldSuppressAssistantVisibleOutput(assistantMessage);
444458
const suppressDeterministicApprovalOutput =
445459
ctx.state.deterministicApprovalPromptPending || ctx.state.deterministicApprovalPromptSent;
@@ -512,6 +526,7 @@ export function handleMessageEnd(
512526
delta: finalStreamDelta,
513527
replace: shouldReplaceFinalStream,
514528
mediaUrls,
529+
phase: assistantPhase,
515530
});
516531
emitAgentEvent({
517532
runId: ctx.params.runId,

0 commit comments

Comments
 (0)