From 34b5ae593bee3f40caa654ee0ff01b89b90db1f2 Mon Sep 17 00:00:00 2001 From: Kelaw - Keshav's Agent Date: Wed, 6 May 2026 01:37:18 +0530 Subject: [PATCH] Coalesce Codex native tool progress --- CHANGELOG.md | 2 +- .../src/app-server/event-projector.test.ts | 1 + .../codex/src/app-server/event-projector.ts | 2 + .../reply/agent-runner-execution.test.ts | 49 +++++++++++++++++++ .../reply/agent-runner-execution.ts | 2 +- src/infra/agent-events.ts | 2 + 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efb13f7eea..8a6cedfb886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Docs: https://docs.openclaw.ai - Dependencies: override transitive `ip-address` to `10.2.0` so the runtime lockfile no longer includes the vulnerable `10.1.0` build flagged by Dependabot alert 109. Thanks @vincentkoc. - Feishu: hydrate missing native topic starter thread IDs before session routing so first turns and follow-ups stay in the same topic session. Fixes #78262. Thanks @joeyzenghuan. - LINE: reject `dmPolicy: "open"` configs without wildcard `allowFrom` so webhook DMs fail validation instead of being acknowledged and silently blocked before inbound processing. Fixes #78316. +- Telegram/Codex: keep message-tool-only progress drafts visible and render native Codex tool progress once per tool instead of duplicating item/tool draft lines. Fixes #75641. (#77949) - Providers/xAI: stop sending OpenAI-style reasoning effort controls to native Grok Responses models, so `xai/grok-4.3` no longer fails live Docker/Gateway runs with `Invalid reasoning effort`. - Providers/xAI: clamp the bundled xAI thinking profile to `off` so live Gateway runs cannot send unsupported reasoning levels to native Grok Responses models. - Matrix/approvals: retry approval delivery up to 3 times with a short backoff so transient Matrix send failures do not strand pending approval prompts. (#78179) Thanks @Patrick-Erichsen. @@ -302,7 +303,6 @@ Docs: https://docs.openclaw.ai - Doctor/plugins: skip channel-derived official plugin installs when another configured plugin is the effective owner for the same channel, so `doctor --repair` does not reinstall `feishu` while `openclaw-lark` handles `channels.feishu`. Fixes #76623. Thanks @fuyizheng3120. - Doctor/plugins: do not treat `plugins.allow` entries as configured plugins during missing-plugin repair, so restrictive allowlists no longer install allowed-but-unused plugins. Thanks @vincentkoc. - Doctor/sessions: clear auto-created stale session routing state from the sessions store when `doctor --fix` sees plugin-owned model/runtime/auth/session bindings outside the current configured route, while leaving explicit user model choices for manual review. Refs #68615. -- CLI/sessions: prune old unreferenced transcript, compaction checkpoint, and trajectory artifacts during normal `sessions cleanup`, so gateway restart or crash orphans do not accumulate indefinitely outside `sessions.json`. Fixes #77608. Thanks @slideshow-dingo. - CLI/sessions: cap `openclaw sessions` output to the newest 100 rows by default and add `--limit ` plus JSON pagination metadata, so repeated machine polling of large session stores cannot fan out into unbounded per-row enrichment/output work. Fixes #77500. Thanks @Kaotic3. - CLI/update: report corrupt or unloadable managed plugins as post-update warnings instead of disabling them or turning a successful OpenClaw package update into a failed update result. Thanks @vincentkoc and @Patrick-Erichsen. - CLI/update: use an absolute POSIX npm script shell during package-manager updates, so restricted PATH environments can still run dependency lifecycle scripts while updating from `--tag main`. Fixes #77530. Thanks @PeterTremonti. diff --git a/extensions/codex/src/app-server/event-projector.test.ts b/extensions/codex/src/app-server/event-projector.test.ts index d3d6747ed3e..a553459e3c9 100644 --- a/extensions/codex/src/app-server/event-projector.test.ts +++ b/extensions/codex/src/app-server/event-projector.test.ts @@ -706,6 +706,7 @@ describe("CodexAppServerEventProjector", () => { kind: "command", name: "bash", itemId: "cmd-1", + suppressChannelProgress: true, }), }); expect(onAgentEvent).toHaveBeenCalledWith({ diff --git a/extensions/codex/src/app-server/event-projector.ts b/extensions/codex/src/app-server/event-projector.ts index c5e2886bcea..0a3a86f764e 100644 --- a/extensions/codex/src/app-server/event-projector.ts +++ b/extensions/codex/src/app-server/event-projector.ts @@ -663,6 +663,7 @@ export class CodexAppServerEventProjector { return; } const meta = itemMeta(item, this.toolProgressDetailMode()); + const suppressChannelProgress = shouldSynthesizeToolProgressForItem(item); this.emitAgentEvent({ stream: "item", data: { @@ -673,6 +674,7 @@ export class CodexAppServerEventProjector { status: params.phase === "start" ? "running" : itemStatus(item), ...(itemName(item) ? { name: itemName(item) } : {}), ...(meta ? { meta } : {}), + ...(suppressChannelProgress ? { suppressChannelProgress: true } : {}), }, }); } diff --git a/src/auto-reply/reply/agent-runner-execution.test.ts b/src/auto-reply/reply/agent-runner-execution.test.ts index 72ccfc26518..a27012c2a77 100644 --- a/src/auto-reply/reply/agent-runner-execution.test.ts +++ b/src/auto-reply/reply/agent-runner-execution.test.ts @@ -1145,6 +1145,55 @@ describe("runAgentTurnWithFallback", () => { }); }); + it("skips channel item progress when a matching tool event carries the progress", async () => { + const onItemEvent = vi.fn(); + const onToolStart = vi.fn(); + state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => { + await params.onAgentEvent?.({ + stream: "item", + data: { + itemId: "cmd-1", + kind: "command", + title: "Command", + name: "bash", + phase: "start", + status: "running", + suppressChannelProgress: true, + }, + }); + await params.onAgentEvent?.({ + stream: "tool", + data: { + itemId: "cmd-1", + toolCallId: "cmd-1", + name: "bash", + phase: "start", + args: { command: "pnpm test" }, + }, + }); + 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(onItemEvent).not.toHaveBeenCalled(); + expect(onToolStart).toHaveBeenCalledWith({ + name: "bash", + phase: "start", + args: { command: "pnpm test" }, + detailMode: undefined, + }); + }); + it("forwards raw tool progress detail mode to tool-start reply options", async () => { const onToolStart = vi.fn(); state.runEmbeddedPiAgentMock.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 53126ec75b1..18eb88929c9 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -1675,7 +1675,7 @@ export async function runAgentTurnWithFallback(params: { ]); } } - if (evt.stream === "item") { + if (evt.stream === "item" && evt.data.suppressChannelProgress !== true) { await params.opts?.onItemEvent?.({ itemId: readStringValue(evt.data.itemId), kind: readStringValue(evt.data.kind), diff --git a/src/infra/agent-events.ts b/src/infra/agent-events.ts index e1fc3a54340..80d421f1460 100644 --- a/src/infra/agent-events.ts +++ b/src/infra/agent-events.ts @@ -40,6 +40,8 @@ export type AgentItemEventData = { error?: string; summary?: string; progressText?: string; + /** Preserve item telemetry while letting channel progress render a sibling tool event instead. */ + suppressChannelProgress?: boolean; approvalId?: string; approvalSlug?: string; };