mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 17:24:46 +00:00
fix: restore Codex snapshot tool progress (#82917)
# Conflicts: # CHANGELOG.md
This commit is contained in:
committed by
GitHub
parent
3fad770510
commit
421b9e2819
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Agents/skills: apply the full effective tool policy pipeline to inline `command-dispatch: tool` skill dispatch before owner-only filtering, preserving configured allow, deny, sandbox, sender, group, and subagent restrictions. (#78525)
|
||||
- Codex/Telegram: synthesize native Codex tool progress from final turn snapshots so Telegram `/verbose` stays visible when command events arrive only at completion.
|
||||
- Providers/GitHub Copilot: request identity-encoded Copilot API responses across token exchange, catalog, model calls, usage, and embeddings so compressed Business-account error payloads no longer reach JSON parsers as gzip bytes. Fixes #82871. Thanks @tonyfe01.
|
||||
- Telegram: preserve replied-to bot messages, captions, and media metadata in group reply chains so follow-up replies understand what the user is reacting to. (#82863)
|
||||
- Providers/Together: update PI runtime packages to 0.74.1 and emit Together-style `reasoning.enabled`/`max_tokens` controls for reasoning-capable OpenAI-completions models.
|
||||
|
||||
@@ -1253,6 +1253,174 @@ describe("CodexAppServerEventProjector", () => {
|
||||
expect(toolResultContentItem.content).toBe("ok");
|
||||
});
|
||||
|
||||
it("synthesizes native tool progress from turn completion snapshots", async () => {
|
||||
const onAgentEvent = vi.fn();
|
||||
const onToolResult = vi.fn();
|
||||
const projector = await createProjector({
|
||||
...(await createParams()),
|
||||
verboseLevel: "on",
|
||||
onAgentEvent,
|
||||
onToolResult,
|
||||
});
|
||||
|
||||
await projector.handleNotification(
|
||||
turnCompleted([
|
||||
{
|
||||
type: "commandExecution",
|
||||
id: "cmd-snapshot",
|
||||
command: "pnpm test extensions/codex",
|
||||
cwd: "/workspace",
|
||||
processId: null,
|
||||
source: "agent",
|
||||
status: "completed",
|
||||
commandActions: [],
|
||||
aggregatedOutput: "ok",
|
||||
exitCode: 0,
|
||||
durationMs: 42,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const itemStart = findAgentEvent(onAgentEvent, {
|
||||
stream: "item",
|
||||
phase: "start",
|
||||
itemId: "cmd-snapshot",
|
||||
}).data;
|
||||
expect(itemStart.kind).toBe("command");
|
||||
expect(itemStart.name).toBe("bash");
|
||||
expect(itemStart.suppressChannelProgress).toBe(true);
|
||||
const toolStart = findAgentEvent(onAgentEvent, {
|
||||
stream: "tool",
|
||||
phase: "start",
|
||||
itemId: "cmd-snapshot",
|
||||
name: "bash",
|
||||
}).data;
|
||||
expect(toolStart.args).toEqual({ command: "pnpm test extensions/codex", cwd: "/workspace" });
|
||||
const toolResult = findAgentEvent(onAgentEvent, {
|
||||
stream: "tool",
|
||||
phase: "result",
|
||||
itemId: "cmd-snapshot",
|
||||
name: "bash",
|
||||
}).data;
|
||||
expect(toolResult.status).toBe("completed");
|
||||
expect(toolResult.isError).toBe(false);
|
||||
expect(onToolResult).toHaveBeenCalledWith({
|
||||
text: "🛠️ `run tests (workspace)`",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not duplicate native tool starts when the snapshot completes a started item", async () => {
|
||||
const onAgentEvent = vi.fn();
|
||||
const projector = await createProjector({ ...(await createParams()), onAgentEvent });
|
||||
const commandItem = {
|
||||
type: "commandExecution",
|
||||
id: "cmd-started",
|
||||
command: "pnpm test extensions/codex",
|
||||
cwd: "/workspace",
|
||||
processId: null,
|
||||
source: "agent",
|
||||
status: "completed",
|
||||
commandActions: [],
|
||||
aggregatedOutput: "ok",
|
||||
exitCode: 0,
|
||||
durationMs: 42,
|
||||
};
|
||||
|
||||
await projector.handleNotification(
|
||||
forCurrentTurn("item/started", {
|
||||
item: { ...commandItem, status: "inProgress", aggregatedOutput: null, exitCode: null },
|
||||
}),
|
||||
);
|
||||
await projector.handleNotification(turnCompleted([commandItem]));
|
||||
|
||||
const toolEvents = onAgentEvent.mock.calls
|
||||
.map((call) => requireRecord(call[0], "agent event"))
|
||||
.filter((event) => event.stream === "tool")
|
||||
.map((event) => requireRecord(event.data, "agent event data"));
|
||||
expect(
|
||||
toolEvents.filter((event) => event.phase === "start" && event.itemId === "cmd-started"),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
toolEvents.filter((event) => event.phase === "result" && event.itemId === "cmd-started"),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("does not synthesize completed progress for running turn completion snapshots", async () => {
|
||||
const onAgentEvent = vi.fn();
|
||||
const projector = await createProjector({ ...(await createParams()), onAgentEvent });
|
||||
|
||||
await projector.handleNotification(
|
||||
turnCompleted([
|
||||
{
|
||||
type: "commandExecution",
|
||||
id: "cmd-running-snapshot",
|
||||
command: "pnpm test extensions/codex",
|
||||
cwd: "/workspace",
|
||||
processId: null,
|
||||
source: "agent",
|
||||
status: "inProgress",
|
||||
commandActions: [],
|
||||
aggregatedOutput: null,
|
||||
exitCode: null,
|
||||
durationMs: null,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const toolEvents = onAgentEvent.mock.calls
|
||||
.map((call) => requireRecord(call[0], "agent event"))
|
||||
.filter((event) => event.stream === "tool")
|
||||
.map((event) => requireRecord(event.data, "agent event data"));
|
||||
expect(toolEvents).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not synthesize progress for stale prior-turn snapshot items", async () => {
|
||||
const onAgentEvent = vi.fn();
|
||||
const projector = await createProjector({ ...(await createParams()), onAgentEvent });
|
||||
|
||||
await projector.handleNotification(
|
||||
turnCompleted([
|
||||
{
|
||||
type: "commandExecution",
|
||||
id: "cmd-prior-turn",
|
||||
turnId: "turn-old",
|
||||
command: "pnpm test extensions/codex",
|
||||
cwd: "/workspace",
|
||||
processId: null,
|
||||
source: "agent",
|
||||
status: "completed",
|
||||
commandActions: [],
|
||||
aggregatedOutput: "ok",
|
||||
exitCode: 0,
|
||||
durationMs: 42,
|
||||
},
|
||||
{
|
||||
type: "commandExecution",
|
||||
id: "cmd-current-turn",
|
||||
turnId: TURN_ID,
|
||||
command: "pnpm test extensions/codex",
|
||||
cwd: "/workspace",
|
||||
processId: null,
|
||||
source: "agent",
|
||||
status: "completed",
|
||||
commandActions: [],
|
||||
aggregatedOutput: "ok",
|
||||
exitCode: 0,
|
||||
durationMs: 42,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const toolEvents = onAgentEvent.mock.calls
|
||||
.map((call) => requireRecord(call[0], "agent event"))
|
||||
.filter((event) => event.stream === "tool")
|
||||
.map((event) => requireRecord(event.data, "agent event data"));
|
||||
expect(toolEvents.map((event) => event.itemId)).toEqual([
|
||||
"cmd-current-turn",
|
||||
"cmd-current-turn",
|
||||
]);
|
||||
});
|
||||
|
||||
it("orders declined native tool diagnostics after their start event", async () => {
|
||||
const projector = await createProjector();
|
||||
const diagnosticEvents: DiagnosticEventPayload[] = [];
|
||||
|
||||
@@ -644,6 +644,7 @@ export class CodexAppServerEventProjector {
|
||||
this.emitPlanUpdate({ explanation: undefined, steps: splitPlanText(item.text) });
|
||||
}
|
||||
this.recordToolMeta(item);
|
||||
this.emitSnapshotOnlyNativeToolProgress(item);
|
||||
this.recordNativeToolTranscriptCall(item);
|
||||
this.recordNativeToolTranscriptResult(item);
|
||||
this.emitAfterToolCallObservation(item);
|
||||
@@ -654,6 +655,31 @@ export class CodexAppServerEventProjector {
|
||||
await this.maybeEndReasoning();
|
||||
}
|
||||
|
||||
private emitSnapshotOnlyNativeToolProgress(item: CodexThreadItem): void {
|
||||
if (
|
||||
!shouldSynthesizeToolProgressForItem(item) ||
|
||||
!this.isCurrentTurnSnapshotItem(item) ||
|
||||
this.completedItemIds.has(item.id) ||
|
||||
itemStatus(item) === "running"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const wasStarted = this.activeItemIds.has(item.id);
|
||||
if (!wasStarted) {
|
||||
this.emitStandardItemEvent({ phase: "start", item });
|
||||
this.emitNormalizedToolItemEvent({ phase: "start", item });
|
||||
}
|
||||
this.activeItemIds.delete(item.id);
|
||||
this.emitStandardItemEvent({ phase: "end", item });
|
||||
this.emitNormalizedToolItemEvent({ phase: "result", item });
|
||||
this.completedItemIds.add(item.id);
|
||||
}
|
||||
|
||||
private isCurrentTurnSnapshotItem(item: CodexThreadItem): boolean {
|
||||
const itemTurnId = readItemString(item, "turnId") ?? readItemString(item, "turn_id");
|
||||
return itemTurnId === undefined || itemTurnId === this.turnId;
|
||||
}
|
||||
|
||||
private handleOutputDelta(params: JsonObject, toolName: string): void {
|
||||
const itemId = readString(params, "itemId");
|
||||
const delta = readString(params, "delta");
|
||||
|
||||
Reference in New Issue
Block a user