
















@@ -3,13 +3,16 @@ import type { AssistantMessage, Usage } from "@mariozechner/pi-ai";
33import { SessionManager } from "@mariozechner/pi-coding-agent";
44import {
55formatErrorMessage,
6+inferToolMetaFromArgs,
67normalizeUsage,
78runAgentHarnessAfterCompactionHook,
89runAgentHarnessBeforeCompactionHook,
910type EmbeddedRunAttemptParams,
1011type EmbeddedRunAttemptResult,
12+formatToolAggregate,
1113type MessagingToolSend,
1214} from "openclaw/plugin-sdk/agent-harness-runtime";
15+import { readCodexTurn } from "./protocol-validators.js";
1316import {
1417isJsonObject,
1518type CodexServerNotification,
@@ -18,7 +21,6 @@ import {
1821type JsonObject,
1922type JsonValue,
2023} from "./protocol.js";
21-import { readCodexTurn } from "./protocol-validators.js";
22242325export type CodexAppServerToolTelemetry = {
2426didSendViaMessagingTool: boolean;
@@ -62,6 +64,8 @@ export class CodexAppServerEventProjector {
6264private readonly activeItemIds = new Set<string>();
6365private readonly completedItemIds = new Set<string>();
6466private readonly activeCompactionItemIds = new Set<string>();
67+private readonly toolResultSummaryItemIds = new Set<string>();
68+private readonly toolResultOutputItemIds = new Set<string>();
6569private readonly toolMetas = new Map<string, { toolName: string; meta?: string }>();
6670private assistantStarted = false;
6771private reasoningStarted = false;
@@ -106,6 +110,12 @@ export class CodexAppServerEventProjector {
106110case "item/completed":
107111await this.handleItemCompleted(params);
108112break;
113+case "item/commandExecution/outputDelta":
114+this.handleOutputDelta(params, "bash");
115+break;
116+case "item/fileChange/outputDelta":
117+this.handleOutputDelta(params, "apply_patch");
118+break;
109119case "item/autoApprovalReview/started":
110120case "item/autoApprovalReview/completed":
111121this.handleGuardianReviewNotification(notification.method, params);
@@ -307,6 +317,7 @@ export class CodexAppServerEventProjector {
307317});
308318}
309319this.emitStandardItemEvent({ phase: "start", item });
320+this.emitToolResultSummary(item);
310321this.emitAgentEvent({
311322stream: "codex_app_server.item",
312323data: { phase: "started", itemId, type: item?.type },
@@ -359,6 +370,8 @@ export class CodexAppServerEventProjector {
359370}
360371this.recordToolMeta(item);
361372this.emitStandardItemEvent({ phase: "end", item });
373+this.emitToolResultSummary(item);
374+this.emitToolResultOutput(item);
362375this.emitAgentEvent({
363376stream: "codex_app_server.item",
364377data: { phase: "completed", itemId, type: item?.type },
@@ -423,11 +436,26 @@ export class CodexAppServerEventProjector {
423436this.emitPlanUpdate({ explanation: undefined, steps: splitPlanText(item.text) });
424437}
425438this.recordToolMeta(item);
439+this.emitToolResultSummary(item);
440+this.emitToolResultOutput(item);
426441}
427442this.activeCompactionItemIds.clear();
428443await this.maybeEndReasoning();
429444}
430445446+private handleOutputDelta(params: JsonObject, toolName: string): void {
447+const itemId = readString(params, "itemId");
448+const delta = readString(params, "delta");
449+if (!itemId || !delta || !this.shouldEmitToolOutput()) {
450+return;
451+}
452+this.emitToolResultMessage({
453+ itemId,
454+text: formatToolOutput(toolName, undefined, delta),
455+output: true,
456+});
457+}
458+431459private handleRawResponseItemCompleted(params: JsonObject): void {
432460const item = isJsonObject(params.item) ? params.item : undefined;
433461if (!item || readString(item, "role") !== "assistant") {
@@ -492,6 +520,71 @@ export class CodexAppServerEventProjector {
492520});
493521}
494522523+private emitToolResultSummary(item: CodexThreadItem | undefined): void {
524+if (!item || !this.params.onToolResult || !this.shouldEmitToolResult()) {
525+return;
526+}
527+const itemId = item.id;
528+if (this.toolResultSummaryItemIds.has(itemId)) {
529+return;
530+}
531+const toolName = itemName(item);
532+if (!toolName) {
533+return;
534+}
535+this.toolResultSummaryItemIds.add(itemId);
536+const meta = itemMeta(item);
537+this.emitToolResultMessage({
538+ itemId,
539+text: formatToolSummary(toolName, meta),
540+});
541+}
542+543+private emitToolResultOutput(item: CodexThreadItem | undefined): void {
544+if (!item || !this.params.onToolResult || !this.shouldEmitToolOutput()) {
545+return;
546+}
547+const itemId = item.id;
548+if (this.toolResultOutputItemIds.has(itemId)) {
549+return;
550+}
551+const toolName = itemName(item);
552+const output = itemOutputText(item);
553+if (!toolName || !output) {
554+return;
555+}
556+this.emitToolResultMessage({
557+ itemId,
558+text: formatToolOutput(toolName, itemMeta(item), output),
559+output: true,
560+});
561+}
562+563+private emitToolResultMessage(params: { itemId: string; text: string; output?: boolean }): void {
564+if (params.output) {
565+this.toolResultOutputItemIds.add(params.itemId);
566+}
567+try {
568+void Promise.resolve(this.params.onToolResult?.({ text: params.text })).catch(() => {
569+// Tool progress delivery is best-effort and should not affect the turn.
570+});
571+} catch {
572+// Tool progress delivery is best-effort and should not affect the turn.
573+}
574+}
575+576+private shouldEmitToolResult(): boolean {
577+return typeof this.params.shouldEmitToolResult === "function"
578+ ? this.params.shouldEmitToolResult()
579+ : this.params.verboseLevel === "on" || this.params.verboseLevel === "full";
580+}
581+582+private shouldEmitToolOutput(): boolean {
583+return typeof this.params.shouldEmitToolOutput === "function"
584+ ? this.params.shouldEmitToolOutput()
585+ : this.params.verboseLevel === "full";
586+}
587+495588private recordToolMeta(item: CodexThreadItem | undefined): void {
496589if (!item) {
497590return;
@@ -777,7 +870,67 @@ function itemMeta(item: CodexThreadItem): string | undefined {
777870if (item.type === "webSearch" && typeof item.query === "string") {
778871return item.query;
779872}
780-return readItemString(item, "status");
873+const toolName = itemName(item);
874+if ((item.type === "dynamicToolCall" || item.type === "mcpToolCall") && toolName) {
875+return inferToolMetaFromArgs(toolName, item.arguments);
876+}
877+return undefined;
878+}
879+880+function itemOutputText(item: CodexThreadItem): string | undefined {
881+if (item.type === "commandExecution") {
882+return item.aggregatedOutput?.trim() || undefined;
883+}
884+if (item.type === "dynamicToolCall") {
885+return collectDynamicToolContentText(item.contentItems).trim() || undefined;
886+}
887+if (item.type === "mcpToolCall") {
888+if (item.error) {
889+return stringifyJsonValue(item.error);
890+}
891+return item.result ? stringifyJsonValue(item.result) : undefined;
892+}
893+return undefined;
894+}
895+896+function collectDynamicToolContentText(
897+contentItems: Extract<CodexThreadItem, { type: "dynamicToolCall" }>["contentItems"],
898+): string {
899+if (!Array.isArray(contentItems)) {
900+return "";
901+}
902+return contentItems
903+.flatMap((entry) => {
904+if (!isJsonObject(entry)) {
905+return [];
906+}
907+const text = readString(entry, "text");
908+return text ? [text] : [];
909+})
910+.join("\n");
911+}
912+913+function stringifyJsonValue(value: unknown): string | undefined {
914+try {
915+return JSON.stringify(value, null, 2);
916+} catch {
917+return undefined;
918+}
919+}
920+921+function formatToolSummary(toolName: string, meta?: string): string {
922+const trimmedMeta = meta?.trim();
923+return formatToolAggregate(toolName, trimmedMeta ? [trimmedMeta] : undefined, {
924+markdown: true,
925+});
926+}
927+928+function formatToolOutput(toolName: string, meta: string | undefined, output: string): string {
929+const trimmed = output.trim();
930+if (!trimmed) {
931+return formatToolSummary(toolName, meta);
932+}
933+return `${formatToolSummary(toolName, meta)}\n\`\`\`txt\n${trimmed}\n\`\`\``;
781934}
782935783936function readItemString(item: CodexThreadItem, key: string): string | undefined {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。