diff --git a/packages/agent-core/src/agent-loop.test.ts b/packages/agent-core/src/agent-loop.test.ts index 774f7c09698a..495df409e640 100644 --- a/packages/agent-core/src/agent-loop.test.ts +++ b/packages/agent-core/src/agent-loop.test.ts @@ -730,6 +730,65 @@ describe("agentLoop tool termination", () => { }; } + it("marks lifecycle events from the concrete hidden tool instance", async () => { + let turn = 0; + const streamFn: StreamFn = () => { + turn += 1; + const stream = createAssistantMessageEventStream(); + queueMicrotask(() => { + const message = + turn === 1 + ? makeAssistantMessage([ + { type: "toolCall", id: "call-wait", name: "wait", arguments: {} }, + ]) + : makeAssistantMessage([{ type: "text", text: "done" }]); + stream.push({ + type: "done", + reason: message.stopReason === "toolUse" ? "toolUse" : "stop", + message, + }); + stream.end(); + }); + return stream; + }; + const hiddenTool: AgentTool = { + ...makeTool("wait", []), + hideFromChannelProgress: true, + execute: async (_toolCallId, _args, _signal, onUpdate) => { + onUpdate?.({ + content: [{ type: "text", text: "still waiting" }], + details: { status: "waiting" }, + }); + return { + content: [{ type: "text", text: "resumed" }], + details: { status: "completed" }, + }; + }, + }; + + const events = await collectEvents( + agentLoop( + [{ role: "user", content: "resume", timestamp: 1 }], + { systemPrompt: "", messages: [], tools: [hiddenTool] }, + { ...config, toolExecution: "sequential" }, + undefined, + streamFn, + ), + ); + const lifecycleEvents = events.filter((event) => event.type.startsWith("tool_execution_")); + + expect(lifecycleEvents.map((event) => event.type)).toEqual([ + "tool_execution_start", + "tool_execution_update", + "tool_execution_end", + ]); + expect( + lifecycleEvents.every( + (event) => "hideFromChannelProgress" in event && event.hideFromChannelProgress === true, + ), + ).toBe(true); + }); + it("continues after a side-effect tool result when afterToolCall records it without terminate", async () => { const executed: string[] = []; let turn = 0; diff --git a/packages/agent-core/src/agent-loop.ts b/packages/agent-core/src/agent-loop.ts index 2a5f067e0455..c77bb3abf13f 100644 --- a/packages/agent-core/src/agent-loop.ts +++ b/packages/agent-core/src/agent-loop.ts @@ -605,6 +605,19 @@ type ResolvedToolCallOutcome = | { kind: "resolved"; tool?: AgentTool } | { kind: "error"; error: unknown }; +function hidesToolCallFromChannelProgress( + context: AgentContext, + toolCall: AgentToolCall, + resolvedToolCalls: Map, +): boolean { + const resolution = resolvedToolCalls.get(toolCall); + const tool = + resolution?.kind === "resolved" + ? resolution.tool + : context.tools?.find((candidate) => candidate.name === toolCall.name); + return tool?.hideFromChannelProgress === true; +} + async function executeToolCallsSequential( currentContext: AgentContext, assistantMessage: AssistantMessage, @@ -618,11 +631,17 @@ async function executeToolCallsSequential( const messages: ToolResultMessage[] = []; for (const toolCall of toolCalls) { + const hideFromChannelProgress = hidesToolCallFromChannelProgress( + currentContext, + toolCall, + resolvedToolCalls, + ); await emit({ type: "tool_execution_start", toolCallId: toolCall.id, toolName: toolCall.name, args: toolCall.arguments, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }); const preparation = await prepareToolCall( @@ -640,6 +659,7 @@ async function executeToolCallsSequential( result: preparation.result, isError: preparation.isError, executionStarted: false, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }; } else { const executed = await executePreparedToolCall(preparation, signal, emit); @@ -682,11 +702,17 @@ async function executeToolCallsParallel( const finalizedCalls: FinalizedToolCallEntry[] = []; for (const toolCall of toolCalls) { + const hideFromChannelProgress = hidesToolCallFromChannelProgress( + currentContext, + toolCall, + resolvedToolCalls, + ); await emit({ type: "tool_execution_start", toolCallId: toolCall.id, toolName: toolCall.name, args: toolCall.arguments, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }); const preparation = await prepareToolCall( @@ -703,6 +729,7 @@ async function executeToolCallsParallel( result: preparation.result, isError: preparation.isError, executionStarted: false, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), } satisfies FinalizedToolCallOutcome; await emitToolExecutionEnd(finalized, emit); finalizedCalls.push(finalized); @@ -769,6 +796,7 @@ type FinalizedToolCallOutcome = { result: AgentToolResult; isError: boolean; executionStarted: boolean; + hideFromChannelProgress?: boolean; }; type FinalizedToolCallEntry = FinalizedToolCallOutcome | (() => Promise); @@ -947,6 +975,9 @@ async function executePreparedToolCall( toolName: prepared.toolCall.name, args: prepared.toolCall.arguments, partialResult, + ...(prepared.tool.hideFromChannelProgress === true + ? { hideFromChannelProgress: true } + : {}), }), ), ); @@ -1006,6 +1037,7 @@ async function finalizeExecutedToolCall( result, isError, executionStarted: true, + ...(prepared.tool.hideFromChannelProgress === true ? { hideFromChannelProgress: true } : {}), }; } @@ -1027,6 +1059,7 @@ async function emitToolExecutionEnd( result: finalized.result, isError: finalized.isError, executionStarted: finalized.executionStarted, + ...(finalized.hideFromChannelProgress === true ? { hideFromChannelProgress: true } : {}), }); } diff --git a/packages/agent-core/src/types.ts b/packages/agent-core/src/types.ts index 5873ddf2397e..a3758f898d58 100644 --- a/packages/agent-core/src/types.ts +++ b/packages/agent-core/src/types.ts @@ -462,6 +462,8 @@ export interface AgentTool< > extends Tool { /** Human-readable label for UI display. */ label: string; + /** Preserve lifecycle telemetry without rendering transient channel progress. */ + hideFromChannelProgress?: boolean; /** * Optional compatibility shim for raw tool-call arguments before schema validation. * Must return an object that matches `TParameters`. @@ -514,13 +516,20 @@ export type AgentEvent = | { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent } | { type: "message_end"; message: AgentMessage } // Tool execution lifecycle - | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: unknown } + | { + type: "tool_execution_start"; + toolCallId: string; + toolName: string; + args: unknown; + hideFromChannelProgress?: boolean; + } | { type: "tool_execution_update"; toolCallId: string; toolName: string; args: unknown; partialResult: unknown; + hideFromChannelProgress?: boolean; } | { type: "tool_execution_end"; @@ -530,4 +539,5 @@ export type AgentEvent = isError: boolean; /** False when resolution, argument preparation, validation, or policy blocked execution. */ executionStarted?: boolean; + hideFromChannelProgress?: boolean; }; diff --git a/src/agents/code-mode.test.ts b/src/agents/code-mode.test.ts index ec207ab2ff13..3a9461dd3a84 100644 --- a/src/agents/code-mode.test.ts +++ b/src/agents/code-mode.test.ts @@ -269,6 +269,13 @@ describe("Code Mode", () => { expect(compacted.catalogToolCount).toBe(2); }); + it("marks only the internal wait control as hidden from channel progress", () => { + const { tools } = createCodeModeHarness(); + + expect(tools[0].hideFromChannelProgress).toBeUndefined(); + expect(tools[1].hideFromChannelProgress).toBe(true); + }); + it("tells models to return the final code value", () => { const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness(); const compacted = applyCodeModeCatalog({ diff --git a/src/agents/code-mode.ts b/src/agents/code-mode.ts index e4ed6fac25a7..5a816f759976 100644 --- a/src/agents/code-mode.ts +++ b/src/agents/code-mode.ts @@ -1172,6 +1172,7 @@ export function createCodeModeTools(ctx: CodeModeToolContext): AnyAgentTool[] { const waitTool = markCodeModeControlTool({ name: CODE_MODE_WAIT_TOOL_NAME, label: "wait", + hideFromChannelProgress: true, description: "Resume a suspended OpenClaw code mode run returned by exec.", parameters: Type.Object({ runId: Type.String({ description: "Code mode run id returned by exec." }), diff --git a/src/agents/embedded-agent-runner/run/attempt.ts b/src/agents/embedded-agent-runner/run/attempt.ts index 690184d5774f..8a8b31ab2c35 100644 --- a/src/agents/embedded-agent-runner/run/attempt.ts +++ b/src/agents/embedded-agent-runner/run/attempt.ts @@ -3756,6 +3756,9 @@ export async function runEmbeddedAttempt( toolCallId: toolParams.toolCallId, args: toolParams.input, replaySafe: replaySafeTools.has(toolParams.tool as never), + hideFromChannelProgress: + "hideFromChannelProgress" in toolParams.tool && + toolParams.tool.hideFromChannelProgress === true, execute: async () => await toolParams.tool.execute( toolParams.toolCallId, diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.test.ts b/src/agents/embedded-agent-subscribe.handlers.tools.test.ts index 5ebd7c8e57a5..0c315e58f213 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.test.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.test.ts @@ -393,6 +393,68 @@ describe("handleToolExecutionStart read path checks", () => { expect(ctx.state.toolMetaById.has("tool-callback-rejects")).toBe(true); expect(warn).toHaveBeenCalledWith(expect.stringContaining("tool agent event callback failed")); }); + + it("preserves hidden tool telemetry while marking its channel progress private", async () => { + const { ctx, onAgentEvent } = createTestContext(); + + await handleToolExecutionStart(ctx, { + type: "tool_execution_start", + toolName: "wait", + toolCallId: "tool-code-wait", + args: { runId: "cm_1" }, + hideFromChannelProgress: true, + }); + handleToolExecutionUpdate(ctx, { + type: "tool_execution_update", + toolName: "wait", + toolCallId: "tool-code-wait", + args: { runId: "cm_1" }, + partialResult: { status: "waiting" }, + hideFromChannelProgress: true, + }); + await handleToolExecutionEnd(ctx, { + type: "tool_execution_end", + toolName: "wait", + toolCallId: "tool-code-wait", + isError: false, + result: { details: { status: "completed" } }, + hideFromChannelProgress: true, + }); + + const lifecycleEvents = onAgentEvent.mock.calls + .map((call) => call[0] as CapturedAgentEvent) + .filter((event) => event.data?.name === "wait"); + expect(lifecycleEvents).not.toHaveLength(0); + expect(lifecycleEvents.every((event) => event.data?.hideFromChannelProgress === true)).toBe( + true, + ); + }); + + it("keeps an unmarked catalog tool named wait visible", async () => { + const { ctx, onAgentEvent } = createTestContext(); + + await handleToolExecutionStart(ctx, { + type: "tool_execution_start", + toolName: "wait", + toolCallId: "tool-catalog-wait", + args: {}, + }); + await handleToolExecutionEnd(ctx, { + type: "tool_execution_end", + toolName: "wait", + toolCallId: "tool-catalog-wait", + isError: false, + result: { details: { status: "completed" } }, + }); + + const lifecycleEvents = onAgentEvent.mock.calls + .map((call) => call[0] as CapturedAgentEvent) + .filter((event) => event.data?.name === "wait"); + expect(lifecycleEvents).not.toHaveLength(0); + expect(lifecycleEvents.every((event) => event.data?.hideFromChannelProgress !== true)).toBe( + true, + ); + }); }); describe("handleToolExecutionEnd cron mutation tracking", () => { diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.ts b/src/agents/embedded-agent-subscribe.handlers.tools.ts index a0c665b1a419..f2e05a7eb69e 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.ts @@ -788,6 +788,7 @@ export function handleToolExecutionStart( toolCallId: string; args: unknown; replaySafe?: boolean; + hideFromChannelProgress?: boolean; }, ): void | Promise { const continueAfterBlockReplyFlush = (): void | Promise => { @@ -804,6 +805,7 @@ export function handleToolExecutionStart( const continueToolExecutionStart = () => { const rawToolName = evt.toolName; const toolName = normalizeToolName(rawToolName); + const hideFromChannelProgress = evt.hideFromChannelProgress === true; const toolCallId = evt.toolCallId; const args = evt.args; const runId = ctx.params.runId; @@ -911,6 +913,7 @@ export function handleToolExecutionStart( name: toolName, toolCallId, args: sanitizeToolArgs(args) as Record, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }, }); const itemData: AgentItemEventData = { @@ -923,6 +926,7 @@ export function handleToolExecutionStart( meta, toolCallId, startedAt, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }; emitTrackedItemEvent(ctx, itemData); // Best-effort typing signal; do not block tool summaries on slow emitters. @@ -933,6 +937,7 @@ export function handleToolExecutionStart( name: toolName, toolCallId, args: sanitizeToolArgs(args) as Record, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }, }); @@ -1026,10 +1031,12 @@ export function handleToolExecutionUpdate( toolName: string; toolCallId: string; partialResult?: unknown; + hideFromChannelProgress?: boolean; }, ) { const toolName = normalizeToolName(evt.toolName); const toolCallId = evt.toolCallId; + const hideFromChannelProgress = evt.hideFromChannelProgress === true; const partial = evt.partialResult; const sanitized = sanitizeToolResult(partial); const isExecTool = isExecToolName(toolName); @@ -1048,6 +1055,7 @@ export function handleToolExecutionUpdate( name: toolName, toolCallId, partialResult: liveResult, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }, }); } @@ -1059,6 +1067,7 @@ export function handleToolExecutionUpdate( status: "running", name: toolName, toolCallId, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), ...(toolProgress ? { progressText: toolProgress.text } : { meta: ctx.state.toolMetaById.get(toolCallId)?.meta }), @@ -1071,6 +1080,7 @@ export function handleToolExecutionUpdate( phase: "update", name: toolName, toolCallId, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }, }); } @@ -1118,6 +1128,7 @@ export async function handleToolExecutionEnd( ) { const rawToolName = evt.toolName; const toolName = normalizeToolName(rawToolName); + const hideFromChannelProgress = evt.hideFromChannelProgress === true; const toolCallId = evt.toolCallId; const runId = ctx.params.runId; const isError = evt.isError; @@ -1330,6 +1341,7 @@ export async function handleToolExecutionEnd( meta, isError: isToolError, result: eventResult, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }, }); const endedAt = Date.now(); @@ -1345,6 +1357,7 @@ export async function handleToolExecutionEnd( toolCallId, startedAt: startData?.startTime, endedAt, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), ...(isToolError && extractToolErrorMessage(sanitizedResult) ? { error: extractToolErrorMessage(sanitizedResult) } : {}), @@ -1358,6 +1371,7 @@ export async function handleToolExecutionEnd( toolCallId, meta, isError: isToolError, + ...(hideFromChannelProgress ? { hideFromChannelProgress: true } : {}), }, }); diff --git a/src/agents/embedded-agent-subscribe.ts b/src/agents/embedded-agent-subscribe.ts index 7ba8d4e90ba9..0b345e8a6851 100644 --- a/src/agents/embedded-agent-subscribe.ts +++ b/src/agents/embedded-agent-subscribe.ts @@ -1350,6 +1350,7 @@ export function subscribeEmbeddedAgentSession(params: SubscribeEmbeddedAgentSess toolCallId: string; args: unknown; replaySafe?: boolean; + hideFromChannelProgress?: boolean; execute: () => Promise; }): Promise => { await handleToolExecutionStart(ctx, { @@ -1358,6 +1359,7 @@ export function subscribeEmbeddedAgentSession(params: SubscribeEmbeddedAgentSess toolCallId: toolParams.toolCallId, args: toolParams.args, replaySafe: toolParams.replaySafe, + hideFromChannelProgress: toolParams.hideFromChannelProgress, } as never); try { const result = await toolParams.execute(); @@ -1368,6 +1370,7 @@ export function subscribeEmbeddedAgentSession(params: SubscribeEmbeddedAgentSess isError: false, executionStarted: true, result, + hideFromChannelProgress: toolParams.hideFromChannelProgress, } as never); return result; } catch (error) { @@ -1378,6 +1381,7 @@ export function subscribeEmbeddedAgentSession(params: SubscribeEmbeddedAgentSess isError: true, executionStarted: true, result: buildToolLifecycleErrorResult(error), + hideFromChannelProgress: toolParams.hideFromChannelProgress, } as never); throw error; } diff --git a/src/auto-reply/reply/agent-runner-execution.test.ts b/src/auto-reply/reply/agent-runner-execution.test.ts index bdbeb672cd84..9a4720907660 100644 --- a/src/auto-reply/reply/agent-runner-execution.test.ts +++ b/src/auto-reply/reply/agent-runner-execution.test.ts @@ -4566,6 +4566,82 @@ describe("runAgentTurnWithFallback", () => { }); }); + it("hides internal lifecycle events while preserving visible tool progress", async () => { + const onItemEvent = vi.fn(); + const onToolStart = vi.fn(); + state.runEmbeddedAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => { + await params.onAgentEvent?.({ + stream: "tool", + data: { + name: "exec", + phase: "start", + args: { command: "pwd" }, + }, + }); + await params.onAgentEvent?.({ + stream: "item", + data: { + itemId: "tool:exec-1", + kind: "tool", + title: "exec pwd", + name: "exec", + phase: "start", + status: "running", + }, + }); + await params.onAgentEvent?.({ + stream: "tool", + data: { + name: "wait", + phase: "start", + args: { runId: "ordinary_wait" }, + }, + }); + await params.onAgentEvent?.({ + stream: "tool", + data: { + name: "wait", + phase: "start", + args: { runId: "cm_1" }, + hideFromChannelProgress: true, + }, + }); + await params.onAgentEvent?.({ + stream: "item", + data: { + itemId: "tool:wait-1", + kind: "tool", + title: "wait", + name: "wait", + phase: "start", + status: "running", + hideFromChannelProgress: true, + }, + }); + return { payloads: [{ text: "final" }], meta: {} }; + }); + + const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); + const result = await runAgentTurnWithFallback({ + ...createMinimalRunAgentTurnParams({ + opts: { onItemEvent, onToolStart } satisfies GetReplyOptions, + }), + }); + + expect(result.kind).toBe("success"); + expect(onToolStart).toHaveBeenCalledTimes(2); + expect(onToolStart).toHaveBeenCalledWith( + expect.objectContaining({ name: "exec", phase: "start" }), + ); + expect(onToolStart).toHaveBeenCalledWith( + expect.objectContaining({ name: "wait", phase: "start" }), + ); + expect(onItemEvent).toHaveBeenCalledTimes(1); + expect(onItemEvent).toHaveBeenCalledWith( + expect.objectContaining({ name: "exec", phase: "start" }), + ); + }); + it("forwards raw tool progress detail mode to tool-start reply options", async () => { const onToolStart = vi.fn(); state.runEmbeddedAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => { diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index b7538728ba68..0cf5d6f9374f 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -2697,7 +2697,7 @@ async function runAgentTurnWithFallbackInternal( } // Trigger typing when tools start executing. // Must await to ensure typing indicator starts before tool summaries are emitted. - if (evt.stream === "tool") { + if (evt.stream === "tool" && evt.data.hideFromChannelProgress !== true) { const phase = readStringValue(evt.data.phase) ?? ""; const name = readStringValue(evt.data.name); const toolCallId = readStringValue(evt.data.toolCallId) ?? ""; @@ -2741,6 +2741,8 @@ async function runAgentTurnWithFallbackInternal( evt.stream === "item" && evt.data.suppressChannelProgress === true && Boolean(params.opts?.onToolStart); + const hideItemFromChannelProgress = + evt.stream === "item" && evt.data.hideFromChannelProgress === true; const itemPhase = evt.stream === "item" ? readStringValue(evt.data.phase) : ""; const itemName = @@ -2793,6 +2795,7 @@ async function runAgentTurnWithFallbackInternal( } if ( evt.stream === "item" && + !hideItemFromChannelProgress && !suppressItemChannelProgress && (!suppressProgressAfterMessageToolDelivery || completedMessageToolDelivery) diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index 9c688739a14b..015d1568e82a 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -3165,6 +3165,60 @@ describe("createFollowupRunner progress forwarding", () => { expect(onCommandOutput).not.toHaveBeenCalled(); }); + it("keeps internal tool lifecycle events out of queued channel progress", async () => { + const onToolStart = vi.fn(async () => {}); + const onItemEvent = vi.fn(async () => {}); + + runEmbeddedAgentMock.mockImplementationOnce( + async (args: { + onAgentEvent?: (evt: { stream: string; data: Record }) => Promise; + }) => { + await args.onAgentEvent?.({ + stream: "tool", + data: { + phase: "start", + name: "wait", + hideFromChannelProgress: true, + }, + }); + await args.onAgentEvent?.({ + stream: "item", + data: { + phase: "start", + itemId: "tool:wait-1", + title: "wait", + hideFromChannelProgress: true, + }, + }); + return { payloads: [{ text: "final" }], meta: { agentMeta: {} } }; + }, + ); + + const runner = createFollowupRunner({ + opts: { + allowToolLifecycleWhenProgressHidden: true, + onToolStart, + onItemEvent, + }, + typing: createMockTypingController(), + typingMode: "instant", + defaultModel: "claude", + }); + + await runner( + createQueuedRun({ + run: { + messageProvider: "discord", + sourceReplyDeliveryMode: "message_tool_only", + verboseLevel: "off", + }, + }), + ); + + expect(onToolStart).not.toHaveBeenCalled(); + expect(onItemEvent).not.toHaveBeenCalled(); + }); + it("keeps queued follow-up progress quiet when verbose state is missing", async () => { const onToolStart = vi.fn(async () => {}); const onCommandOutput = vi.fn(async () => {}); diff --git a/src/auto-reply/reply/followup-runner.ts b/src/auto-reply/reply/followup-runner.ts index 3d14ef8b821c..1559a803b23b 100644 --- a/src/auto-reply/reply/followup-runner.ts +++ b/src/auto-reply/reply/followup-runner.ts @@ -167,7 +167,7 @@ async function forwardFollowupProgressEvent(params: { return; } - if (evt.stream === "tool") { + if (evt.stream === "tool" && evt.data.hideFromChannelProgress !== true) { const phase = readStringValue(evt.data.phase) ?? ""; const name = readStringValue(evt.data.name); if (phase === "start" || phase === "update") { @@ -193,7 +193,9 @@ async function forwardFollowupProgressEvent(params: { evt.stream === "item" && evt.data.suppressChannelProgress === true && Boolean(opts?.onToolStart); - if (evt.stream === "item" && !suppressItemChannelProgress) { + const hideItemFromChannelProgress = + evt.stream === "item" && evt.data.hideFromChannelProgress === true; + if (evt.stream === "item" && !suppressItemChannelProgress && !hideItemFromChannelProgress) { await opts?.onItemEvent?.({ itemId: readStringValue(evt.data.itemId), toolCallId: readStringValue(evt.data.toolCallId), diff --git a/src/infra/agent-events.ts b/src/infra/agent-events.ts index 60d7b090e19f..5d84428e0158 100644 --- a/src/infra/agent-events.ts +++ b/src/infra/agent-events.ts @@ -2,9 +2,9 @@ import { AsyncLocalStorage } from "node:async_hooks"; import { randomUUID } from "node:crypto"; import type { VerboseLevel } from "../auto-reply/thinking.js"; -import { createAbortError } from "./abort-signal.js"; import { resolveGlobalSingleton } from "../shared/global-singleton.js"; import { notifyListeners, registerListener } from "../shared/listeners.js"; +import { createAbortError } from "./abort-signal.js"; /** Stream name for agent events delivered to gateway listeners and plugin host hooks. */ export type AgentEventStream = @@ -51,6 +51,8 @@ export type AgentItemEventData = { progressText?: string; /** Preserve item telemetry while letting channel progress render a sibling tool event instead. */ suppressChannelProgress?: boolean; + /** Preserve activity telemetry without rendering this internal item in channel progress. */ + hideFromChannelProgress?: boolean; approvalId?: string; approvalSlug?: string; };