





















@@ -30,6 +30,11 @@ import {
3030import { readRecentCodexRateLimits, rememberCodexRateLimits } from "./rate-limit-cache.js";
3131import { formatCodexUsageLimitErrorMessage } from "./rate-limits.js";
3232import { readCodexMirroredSessionHistoryMessages } from "./session-history.js";
33+import {
34+resolveCodexToolProgressDetailMode,
35+sanitizeCodexAgentEventRecord,
36+sanitizeCodexToolArguments,
37+} from "./tool-progress-normalization.js";
3338import { attachCodexMirrorIdentity } from "./transcript-mirror.js";
34393540export type CodexAppServerToolTelemetry = {
@@ -396,6 +401,7 @@ export class CodexAppServerEventProjector {
396401});
397402}
398403this.emitStandardItemEvent({ phase: "start", item });
404+this.emitNormalizedToolItemEvent({ phase: "start", item });
399405this.emitToolResultSummary(item);
400406this.emitAgentEvent({
401407stream: "codex_app_server.item",
@@ -449,6 +455,7 @@ export class CodexAppServerEventProjector {
449455}
450456this.recordToolMeta(item);
451457this.emitStandardItemEvent({ phase: "end", item });
458+this.emitNormalizedToolItemEvent({ phase: "result", item });
452459this.emitToolResultSummary(item);
453460this.emitToolResultOutput(item);
454461this.emitAgentEvent({
@@ -656,6 +663,7 @@ export class CodexAppServerEventProjector {
656663return;
657664}
658665const meta = itemMeta(item, this.toolProgressDetailMode());
666+const suppressChannelProgress = shouldSuppressChannelProgressForItem(item);
659667this.emitAgentEvent({
660668stream: "item",
661669data: {
@@ -666,6 +674,42 @@ export class CodexAppServerEventProjector {
666674status: params.phase === "start" ? "running" : itemStatus(item),
667675 ...(itemName(item) ? { name: itemName(item) } : {}),
668676 ...(meta ? { meta } : {}),
677+ ...(suppressChannelProgress ? { suppressChannelProgress: true } : {}),
678+},
679+});
680+}
681+682+private emitNormalizedToolItemEvent(params: {
683+phase: "start" | "result";
684+item: CodexThreadItem | undefined;
685+}): void {
686+const { item } = params;
687+if (!item || !shouldSynthesizeToolProgressForItem(item)) {
688+return;
689+}
690+const name = itemName(item);
691+if (!name) {
692+return;
693+}
694+const meta = itemMeta(item, this.toolProgressDetailMode());
695+const args = params.phase === "start" ? itemToolArgs(item) : undefined;
696+const status = params.phase === "result" ? itemStatus(item) : "running";
697+this.emitAgentEvent({
698+stream: "tool",
699+data: {
700+phase: params.phase,
701+ name,
702+itemId: item.id,
703+toolCallId: item.id,
704+ ...(meta ? { meta } : {}),
705+ ...(args ? { args } : {}),
706+ ...(params.phase === "result"
707+ ? {
708+ status,
709+isError: isNonSuccessItemStatus(status),
710+ ...itemToolResult(item),
711+}
712+ : {}),
669713},
670714});
671715}
@@ -743,7 +787,7 @@ export class CodexAppServerEventProjector {
743787}
744788745789private toolProgressDetailMode(): ToolProgressDetailMode {
746-return this.params.toolProgressDetail === "raw" ? "raw" : "explain";
790+return resolveCodexToolProgressDetailMode(this.params.toolProgressDetail);
747791}
748792749793private recordToolMeta(item: CodexThreadItem | undefined): void {
@@ -1074,17 +1118,24 @@ function itemTitle(item: CodexThreadItem): string {
10741118}
10751119}
107611201077-function itemStatus(item: CodexThreadItem): "completed" | "failed" | "running" {
1121+function itemStatus(item: CodexThreadItem): "completed" | "failed" | "running" | "blocked" {
10781122const status = readItemString(item, "status");
10791123if (status === "failed") {
10801124return "failed";
10811125}
1126+if (status === "declined") {
1127+return "blocked";
1128+}
10821129if (status === "inProgress" || status === "running") {
10831130return "running";
10841131}
10851132return "completed";
10861133}
108711341135+function isNonSuccessItemStatus(status: ReturnType<typeof itemStatus>): boolean {
1136+return status === "failed" || status === "blocked";
1137+}
1138+10881139function itemName(item: CodexThreadItem): string | undefined {
10891140if (item.type === "dynamicToolCall" && typeof item.tool === "string") {
10901141return item.tool;
@@ -1105,6 +1156,78 @@ function itemName(item: CodexThreadItem): string | undefined {
11051156return undefined;
11061157}
110711581159+function shouldSynthesizeToolProgressForItem(item: CodexThreadItem): boolean {
1160+switch (item.type) {
1161+case "commandExecution":
1162+case "fileChange":
1163+case "webSearch":
1164+case "mcpToolCall":
1165+return true;
1166+default:
1167+return false;
1168+}
1169+}
1170+1171+function shouldSuppressChannelProgressForItem(item: CodexThreadItem): boolean {
1172+if (shouldSynthesizeToolProgressForItem(item)) {
1173+return true;
1174+}
1175+// Dynamic OpenClaw tool requests are emitted at the item/tool/call request
1176+// boundary in run-attempt.ts. Re-emitting item notifications to channels can
1177+// duplicate start/result progress when the app-server sends both signals.
1178+return item.type === "dynamicToolCall";
1179+}
1180+1181+function itemToolArgs(item: CodexThreadItem): Record<string, unknown> | undefined {
1182+if (item.type === "commandExecution") {
1183+return sanitizeCodexAgentEventRecord({
1184+command: item.command,
1185+ ...(typeof item.cwd === "string" ? { cwd: item.cwd } : {}),
1186+});
1187+}
1188+if (item.type === "webSearch" && typeof item.query === "string") {
1189+return sanitizeCodexAgentEventRecord({ query: item.query });
1190+}
1191+if (item.type === "mcpToolCall") {
1192+return sanitizeCodexToolArguments(item.arguments);
1193+}
1194+return undefined;
1195+}
1196+1197+function itemToolResult(item: CodexThreadItem): { result?: Record<string, unknown> } {
1198+if (item.type === "commandExecution") {
1199+return {
1200+result: sanitizeCodexAgentEventRecord({
1201+status: item.status,
1202+exitCode: item.exitCode,
1203+durationMs: item.durationMs,
1204+}),
1205+};
1206+}
1207+if (item.type === "fileChange") {
1208+return {
1209+result: sanitizeCodexAgentEventRecord({
1210+status: item.status,
1211+changes: item.changes.map((change) => ({ path: change.path, kind: change.kind })),
1212+}),
1213+};
1214+}
1215+if (item.type === "mcpToolCall") {
1216+return {
1217+result: sanitizeCodexAgentEventRecord({
1218+status: item.status,
1219+durationMs: item.durationMs,
1220+ ...(item.error ? { error: item.error } : {}),
1221+ ...(item.result ? { result: item.result } : {}),
1222+}),
1223+};
1224+}
1225+if (item.type === "webSearch") {
1226+return { result: sanitizeCodexAgentEventRecord({ status: "completed" }) };
1227+}
1228+return {};
1229+}
1230+11081231function itemMeta(
11091232item: CodexThreadItem,
11101233detailMode: ToolProgressDetailMode = "explain",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。