Coalesce Codex native tool progress

This commit is contained in:
Kelaw - Keshav's Agent
2026-05-06 01:37:18 +05:30
committed by Peter Steinberger
parent 7523e21021
commit 34b5ae593b
6 changed files with 56 additions and 2 deletions

View File

@@ -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 <n|all>` 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.

View File

@@ -706,6 +706,7 @@ describe("CodexAppServerEventProjector", () => {
kind: "command",
name: "bash",
itemId: "cmd-1",
suppressChannelProgress: true,
}),
});
expect(onAgentEvent).toHaveBeenCalledWith({

View File

@@ -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 } : {}),
},
});
}

View File

@@ -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) => {

View File

@@ -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),

View File

@@ -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;
};