diff --git a/docs/reference/spec-v02-hwlab-cli.md b/docs/reference/spec-v02-hwlab-cli.md index 883b6d92..a2f0e361 100644 --- a/docs/reference/spec-v02-hwlab-cli.md +++ b/docs/reference/spec-v02-hwlab-cli.md @@ -25,7 +25,7 @@ - `client workbench restore/status/watch/reset` 是账号 workspace 的非视觉入口:`restore/status` 对应 `GET /v1/workbench/workspace`,`watch` 对应 `/events?afterRevision=`,`reset --confirm` 对应服务端 reset。输出必须显示 workspace revision、selected conversation/session、active trace 和本地 state file,且不得保存 password、session token 原文以外的 Secret 值。 - `client agent send` 是 Cloud Web Code Agent composer 的非视觉等价入口。它必须支持 `--from-trace`、`--conversation-id`、`--session-id`、`--thread-id` 和 `--retry-of`,并在输出中返回 redacted continuation 摘要,证明本次 CLI 请求是否覆盖 Web 的继续会话路径。浏览器 issue 中已经给出 traceId 时,复现命令优先使用 `--from-trace `,让 CLI 先走 `/v1/agent/chat/inspect` 读取 Web 上下文,再提交同源 `/v1/agent/chat`。 - `client agent send` 默认先恢复账号 workspace,再向 `/v1/agent/chat` 发送 `workspaceId` 和 `expectedWorkspaceRevision`;服务端接受后 CLI 保存新的 workspace revision,终态轮询后再 PATCH workspace 清理终态 `activeTraceId`。只有显式 `--no-workspace` 才跳过这一默认恢复路径。 -- `client agent trace --render web` 必须调用 Cloud Web trace row 的同一纯转换路径,输出 `render="web"`、renderer 标识、source event count、rendered row count 和 row 摘要。浏览器 trace 展示错乱时,必须先用该 CLI 入口确认 Web 渲染转换是否已经乱序、重复、缺 final response 或吞掉关键 row,再继续修浏览器 DOM/CSS。 +- `client agent trace --render web` 必须调用 Cloud Web trace row 的同一纯转换路径,输出 `render="web"`、renderer 标识、source event count、rendered row count、默认压制的 noise event count 和 row 摘要。浏览器 trace 展示错乱时,必须先用该 CLI 入口确认 Web 渲染转换是否已经乱序、重复、缺 final response、吞掉关键 row 或只显示泛化 tool call,再继续修浏览器 DOM/CSS。 - AgentRun v0.1 短连接 runner 已要求支持同 run/runner 多轮 command。CLI 仍应把 Web 提交的 `conversationId/sessionId/threadId` 原样送到 Cloud Web API,用于验证 adapter 是否在 runner lease 有效时复用同一个 AgentRun `runId` / `jobName` 并创建新 `commandId`;每轮都新建 runner 或重新 bundle 不是通过状态,trace 中的原因说明只能用于定位。 - `client harness`、`client harness-ops` 和 `client harness-opt` 吸收 G14 harness-ops 的短连接业务能力:health、submit、result、trace、wait 和 audit。 - harness 系列命令只调用 Cloud Web/Code Agent 同源 API,不创建镜像、Job、常驻服务,也不执行 hot-sync、kubectl cp 或硬编码 namespace/pod 的运行面写路径。 diff --git a/docs/reference/spec-v02-hwlab-cloud-web.md b/docs/reference/spec-v02-hwlab-cloud-web.md index b3f9e38a..c77298df 100644 --- a/docs/reference/spec-v02-hwlab-cloud-web.md +++ b/docs/reference/spec-v02-hwlab-cloud-web.md @@ -9,7 +9,7 @@ - 为浏览器提供同源代理,避免前端直接跨域调用内部 ClusterIP。 - Cloud Web 与 `hwlab-cli client` 必须共享同一组非视觉业务 API。浏览器遇到的 Code Agent continuation、trace/result、device-pod list/status 和 device-pod job 问题,必须能通过 `hwlab-cli client` 走同一 `19666` Cloud Web path 复现;不能让 CLI 长期绕到 `19667` Cloud API 后把 Web 路径缺口误判为业务已通过。 - 浏览器启动后必须从 `GET /v1/workbench/workspace` hydrate 账号 workspace;同一个账号在多个浏览器标签页、多个浏览器或 CLI profile 中应看到同一个 `workspaceId`、selected conversation/session/thread、provider profile 和 active trace。浏览器 localStorage 只能作为短期缓存,并必须绑定 actor,不能作为 workspace authority。 -- Cloud Web trace 展示与 `hwlab-cli client agent trace --render web` 必须共享同一套 trace row 纯转换路径。Web 发生 row 顺序错乱、final response 缺失、assistant 消息被吞或噪声事件淹没时,先用 CLI 输出同一渲染 row 摘要复现;CLI 可复现说明是 trace row 转换问题,CLI 不可复现再进入 DOM/CSS/滚动状态调查。 +- Cloud Web trace 展示与 `hwlab-cli client agent trace --render web` 必须共享同一套 trace row 纯转换路径。Web 发生 row 顺序错乱、final response 缺失、assistant 消息被吞、tool call 只显示泛化占位或噪声事件淹没时,先用 CLI 输出同一渲染 row 摘要和 noise event count 复现;CLI 可复现说明是 trace row 转换问题,CLI 不可复现再进入 DOM/CSS/滚动状态调查。默认展示应压制 AgentRun backend echo、token/rate-limit/status/terminal echo 等低价值事件,但原始 trace JSON 仍必须保留用于 `--full`/下载排障。 - Code Agent result `completed` 只有在同时包含真实 provider/model/trace/conversation 元数据、`providerTrace` 和可展示的 final assistant response 时,才能被 Web 标记为真实完成;`provider=agentrun-v01` 只是执行基础设施标识,不得替代上游 provider/model,也不得把 SOURCE、fixture、echo、mock 或 stub 当成 DEV-LIVE 完成。 - 同一 conversation/session 的后续用户消息必须在 AgentRun runner lease 有效时复用已存在的 AgentRun run/runner 继续新 command/turn;只有 runner 不可用、已过期或协议明确要求新 runner 时才重新 bundle 和启动 runner。每条消息都重新 bundle/runner 属于 v0.2 AgentRun 接入缺口,不能只靠 trace 显示原因当成已完成。 diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 74ce9d38..577ca3c7 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -798,7 +798,19 @@ function mapAgentRunEvent(event, mapping = {}) { return { ...base, type: "assistant", status: "running", label: "agentrun:assistant:message", message: textPayload(payload, "assistant message"), text: textPayload(payload, "") }; } if (event.type === "tool_call") { - return { ...base, type: "tool", status: String(payload.status ?? "running"), label: `agentrun:tool:${String(payload.name ?? payload.method ?? "call")}`, message: textPayload(payload, "tool call") }; + const commandExecution = agentRunCommandExecutionEvent(base, payload); + if (commandExecution) return commandExecution; + return { + ...base, + type: "tool_call", + status: String(payload.status ?? (payload.method === "item/completed" ? "completed" : "running")), + label: `agentrun:tool:${String(payload.name ?? payload.method ?? "call")}`, + itemId: payload.item?.id ?? payload.itemId ?? null, + toolName: payload.item?.type ?? payload.name ?? payload.method ?? "call", + message: textPayload(payload, "tool call"), + outputBytes: typeof payload.outputBytes === "number" ? payload.outputBytes : payload.summary?.outputBytes, + outputTruncated: payload.outputTruncated === true || payload.summary?.outputTruncated === true + }; } if (event.type === "command_output") { const stream = payload.stream === "stderr" ? "stderr" : "stdout"; @@ -817,6 +829,30 @@ function mapAgentRunEvent(event, mapping = {}) { return { ...base, type: "backend", status: "running", label: `agentrun:event:${String(event.type ?? "unknown")}`, message: textPayload(payload, "AgentRun event") }; } +function agentRunCommandExecutionEvent(base, payload = {}) { + const item = payload.item && typeof payload.item === "object" ? payload.item : null; + if (item?.type !== "commandExecution") return null; + const method = String(payload.method ?? ""); + const completed = method === "item/completed" || item.status === "completed"; + const output = firstNonEmpty(item.aggregatedOutput, item.stdout, item.output, payload.summary?.text, payload.itemPreview); + return { + ...base, + type: "tool_call", + status: completed ? "completed" : "started", + label: completed ? "item/commandExecution:completed" : "item/commandExecution:started", + toolName: "commandExecution", + itemId: item.id ?? null, + command: item.command ?? item.commandLine ?? null, + exitCode: Number.isInteger(item.exitCode) ? item.exitCode : undefined, + durationMs: typeof item.durationMs === "number" ? item.durationMs : undefined, + outputBytes: typeof payload.outputBytes === "number" ? payload.outputBytes : payload.summary?.outputBytes, + stdoutSummary: output, + outputSummary: output, + outputTruncated: payload.outputTruncated === true || payload.summary?.outputTruncated === true, + message: output || textPayload(payload, "commandExecution") + }; +} + async function agentRunJson(fetchImpl, managerUrl, path, { method = "GET", body, timeoutMs = 20_000 } = {}) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); diff --git a/internal/cloud/server-agent-chat.test.ts b/internal/cloud/server-agent-chat.test.ts index f1e9a480..94c0b35e 100644 --- a/internal/cloud/server-agent-chat.test.ts +++ b/internal/cloud/server-agent-chat.test.ts @@ -214,8 +214,12 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte const second = url.searchParams.get("afterSeq") === "3" || calls.some((call) => call.path === "/api/v1/runs/run_hwlab_adapter/commands/cmd_hwlab_adapter_second/result"); return send({ items: [ { id: second ? "evt_4" : "evt_1", runId: "run_hwlab_adapter", seq: second ? 4 : 1, type: "backend_status", payload: { phase: second ? "turn-started" : "runner-job-created", commandId: second ? "cmd_hwlab_adapter_second" : "cmd_hwlab_adapter", attemptId: "attempt_hwlab_adapter", jobName: "agentrun-v01-runner-hwlab-adapter", namespace: "agentrun-v01" }, createdAt: "2026-06-01T00:00:00.000Z" }, - { id: second ? "evt_5" : "evt_2", runId: "run_hwlab_adapter", seq: second ? 5 : 2, type: "assistant_message", payload: { commandId: second ? "cmd_hwlab_adapter_second" : "cmd_hwlab_adapter", text: second ? "AgentRun adapter 复用已有 runner 完成第二轮。" : "AgentRun adapter 已接管 HWLAB Code Agent。" }, createdAt: "2026-06-01T00:00:01.000Z" }, - { id: second ? "evt_6" : "evt_3", runId: "run_hwlab_adapter", seq: second ? 6 : 3, type: "terminal_status", payload: { commandId: second ? "cmd_hwlab_adapter_second" : "cmd_hwlab_adapter", terminalStatus: "completed" }, createdAt: "2026-06-01T00:00:02.000Z" } + ...(second ? [] : [ + { id: "evt_tool", runId: "run_hwlab_adapter", seq: 2, type: "tool_call", payload: { method: "item/completed", item: { type: "commandExecution", id: "call_agentrun_tool", command: "/bin/sh -lc 'bun tools/device-pod-cli.ts profile list --api-base-url http://74.48.78.17:19666'", status: "completed", exitCode: 0, durationMs: 708, aggregatedOutput: '{"ok":true,"action":"profile.list"}' }, summary: { outputBytes: 42, outputTruncated: false }, commandId: "cmd_hwlab_adapter", runnerId: "runner_hwlab_adapter", attemptId: "attempt_hwlab_adapter" }, createdAt: "2026-06-01T00:00:00.500Z" }, + { id: "evt_noise", runId: "run_hwlab_adapter", seq: 3, type: "backend_status", payload: { phase: "thread/status/changed", commandId: "cmd_hwlab_adapter" }, createdAt: "2026-06-01T00:00:00.750Z" } + ]), + { id: second ? "evt_5" : "evt_2", runId: "run_hwlab_adapter", seq: second ? 5 : 4, type: "assistant_message", payload: { commandId: second ? "cmd_hwlab_adapter_second" : "cmd_hwlab_adapter", text: second ? "AgentRun adapter 复用已有 runner 完成第二轮。" : "AgentRun adapter 已接管 HWLAB Code Agent。" }, createdAt: "2026-06-01T00:00:01.000Z" }, + { id: second ? "evt_6" : "evt_3", runId: "run_hwlab_adapter", seq: second ? 6 : 5, type: "terminal_status", payload: { commandId: second ? "cmd_hwlab_adapter_second" : "cmd_hwlab_adapter", terminalStatus: "completed" }, createdAt: "2026-06-01T00:00:02.000Z" } ] }); } if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_adapter/commands/cmd_hwlab_adapter/result") { @@ -232,8 +236,8 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte terminalStatus: "completed", completed: true, reply: "AgentRun adapter 已接管 HWLAB Code Agent。", - lastSeq: 3, - eventCount: 3, + lastSeq: 5, + eventCount: 5, sessionRef: { sessionId: "ses_server-test-agentrun", conversationId: "cnv_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" } }); } @@ -335,6 +339,10 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte const traceBody = await trace.json(); assert.ok(traceBody.events.some((event) => event.runId === "run_hwlab_adapter")); assert.ok(traceBody.events.some((event) => event.label === "agentrun:assistant:message")); + const commandTraceEvent = traceBody.events.find((event) => event.label === "item/commandExecution:completed"); + assert.equal(commandTraceEvent.toolName, "commandExecution"); + assert.match(commandTraceEvent.command, /device-pod-cli\.ts profile list/u); + assert.match(commandTraceEvent.stdoutSummary, /profile\.list/u); const secondTraceId = "trc_server-test-agentrun-adapter-second"; const second = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { diff --git a/tools/hwlab-cli/client.test.ts b/tools/hwlab-cli/client.test.ts index da6428e5..99a5435e 100644 --- a/tools/hwlab-cli/client.test.ts +++ b/tools/hwlab-cli/client.test.ts @@ -443,6 +443,31 @@ test("hwlab-cli client agent trace can render with the Web trace row path", asyn assert.ok(result.payload.body.rows.some((row: any) => /助手最后一条消息/u.test(row.header))); }); +test("hwlab-cli Web trace render reports suppressed noise and command tool details", async () => { + const result = await runHwlabCli(["client", "agent", "trace", "trc_render_agentrun", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], { + fetchImpl: async () => new Response(JSON.stringify({ + status: "completed", + traceId: "trc_render_agentrun", + events: [ + { traceId: "trc_render_agentrun", seq: 1, label: "agentrun:backend:command-created", status: "running", type: "backend", createdAt: "2026-06-01T13:00:00.000Z" }, + { traceId: "trc_render_agentrun", seq: 2, label: "agentrun:backend:thread/status/changed", status: "running", type: "backend", createdAt: "2026-06-01T13:00:01.000Z" }, + { traceId: "trc_render_agentrun", seq: 3, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", itemId: "call_1", command: "/bin/sh -lc 'bun tools/device-pod-cli.ts profile list --api-base-url http://74.48.78.17:19666'", exitCode: 0, stdoutSummary: '{"ok":true,"action":"profile.list"}', createdAt: "2026-06-01T13:00:02.000Z" }, + { traceId: "trc_render_agentrun", seq: 4, label: "agentrun:assistant:message", type: "assistant", status: "running", message: "当前可见 device-pod。", createdAt: "2026-06-01T13:00:03.000Z" } + ] + }), { status: 200 }) + }); + + assert.equal(result.exitCode, 0); + assert.equal(result.payload.body.render, "web"); + assert.equal(result.payload.body.sourceEventCount, 4); + assert.equal(result.payload.body.noiseEventCount, 2); + const text = JSON.stringify(result.payload.body.rows); + assert.equal(text.includes("thread/status/changed"), false); + assert.match(text, /device-pod-cli\.ts profile list/u); + assert.match(text, /profile\.list/u); + assert.match(text, /当前可见 device-pod/u); +}); + test("hwlab-cli client agent trace auto logs in when protected trace has no local session", async () => { const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-auth-")); const calls: any[] = []; diff --git a/tools/src/hwlab-cli-lib.ts b/tools/src/hwlab-cli-lib.ts index 6209afcc..0915355c 100644 --- a/tools/src/hwlab-cli-lib.ts +++ b/tools/src/hwlab-cli-lib.ts @@ -1,7 +1,7 @@ import { createHash } from "node:crypto"; import { chmod, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"; import path from "node:path"; -import { traceDisplayRows } from "../../web/hwlab-cloud-web/app-trace.ts"; +import { traceDisplayRows, traceNoiseEventCount } from "../../web/hwlab-cloud-web/app-trace.ts"; const VERSION = "0.2.0-client"; const CLI_NAME = "hwlab-cli"; @@ -1697,6 +1697,7 @@ function traceBodyForCli(body: any, parsed: ParsedArgs) { function webTraceRenderBody(traceObject: any, parsed: ParsedArgs) { const events = Array.isArray(traceObject?.events) ? traceObject.events : []; const rows = traceDisplayRows(traceObject ?? {}, events); + const noiseEventCount = traceNoiseEventCount(events); const rowLimit = Math.max(1, Math.min(numberOption(parsed.limit) ?? 80, 500)); const rowTail = parsed.full === true ? rows : rows.slice(-rowLimit); return pruneUndefined({ @@ -1707,6 +1708,8 @@ function webTraceRenderBody(traceObject: any, parsed: ParsedArgs) { sourceEventCount: traceObject?.eventCount ?? events.length, renderedRowCount: rows.length, returnedRowCount: rowTail.length, + noiseEventCount, + omittedNoiseCount: noiseEventCount, rowLimit: parsed.full === true ? null : rowLimit, eventCountMismatch: Number.isInteger(traceObject?.eventCount) && traceObject.eventCount !== events.length ? { declared: traceObject.eventCount, diff --git a/web/hwlab-cloud-web/app-trace.test.ts b/web/hwlab-cloud-web/app-trace.test.ts index 9952486e..95d63e5e 100644 --- a/web/hwlab-cloud-web/app-trace.test.ts +++ b/web/hwlab-cloud-web/app-trace.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { traceDisplayRows } from "./app-trace.ts"; +import { traceDisplayRows, traceNoiseEventCount } from "./app-trace.ts"; test("trace display rows render request, setup, command, assistant markdown, and completion path", () => { const trace = { @@ -67,6 +67,48 @@ test("trace display rows render assistant item text when stream snapshot is abse assert.match(text, /可以继续指定要操作的 device-pod/u); }); +test("trace display rows suppress low-value AgentRun backend noise", () => { + const events = [ + event(1, "agentrun:request:accepted", { message: "accepted" }), + event(2, "agentrun:backend:command-created"), + event(3, "agentrun:backend:thread/status/changed"), + event(4, "agentrun:backend:thread/tokenUsage/updated"), + event(5, "agentrun:backend:account/rateLimits/updated"), + event(6, "agentrun:backend:codex-app-server-starting"), + commandEvent(7, "item/commandExecution:completed", { + itemId: "call_1", + command: "/bin/sh -lc 'bun tools/device-pod-cli.ts profile list --api-base-url http://74.48.78.17:19666'", + status: "completed", + exitCode: 0, + durationMs: 708, + stdoutSummary: '{"ok":true,"action":"profile.list","body":{"devicePods":[{"devicePodId":"D601-F103-V2"}]}}' + }), + event(8, "agentrun:output:stdout", { + type: "output", + status: "running", + message: '{"ok":true,"action":"profile.list"}' + }), + event(9, "agentrun:assistant:message", { + type: "assistant", + status: "running", + message: "当前可见 1 个 device-pod。" + }), + event(10, "agentrun:terminal:completed", { type: "result", terminal: true, status: "completed" }) + ]; + + const rows = traceDisplayRows({ traceId: "trc_agentrun_noise", events }, events); + const text = rows.map((row) => `${row.header}\n${row.body ?? ""}`).join("\n---\n"); + + assert.equal(traceNoiseEventCount(events), 4); + assert.equal(/thread\/status\/changed/u.test(text), false); + assert.equal(/tokenUsage/u.test(text), false); + assert.match(text, /工具调用/u); + assert.match(text, /device-pod-cli\.ts profile list/u); + assert.match(text, /profile\.list/u); + assert.match(text, /助手最后一条消息/u); + assert.match(text, /当前可见 1 个 device-pod/u); +}); + function traceEvents() { return [ event(1, "request:accepted", { promptSummary: "请检查 trace" }), diff --git a/web/hwlab-cloud-web/app-trace.ts b/web/hwlab-cloud-web/app-trace.ts index 4bf8b1a2..2f61c75c 100644 --- a/web/hwlab-cloud-web/app-trace.ts +++ b/web/hwlab-cloud-web/app-trace.ts @@ -345,6 +345,10 @@ export function traceDisplayRows(trace, events) { return rows.length > 0 ? rows : events.map((event) => traceDisplayRow(trace, event, { includeNoise: true })).filter(Boolean); } +export function traceNoiseEventCount(events) { + return Array.isArray(events) ? events.filter((event) => isNoisyTraceEvent(event)).length : 0; +} + function compactTraceTextTail(text, maxLength) { const clean = cleanTraceText(text); if (clean.length <= maxLength) return clean; @@ -909,12 +913,12 @@ function isCommandExecutionTraceEvent(event) { function isAssistantMessageTraceEvent(event) { const label = String(event?.label ?? ""); - return event?.type === "assistant_message" || label.startsWith("assistant:"); + return event?.type === "assistant_message" || event?.type === "assistant" || label.startsWith("assistant:") || label === "agentrun:assistant:message"; } function isTerminalAssistantTraceEvent(event) { const label = String(event?.label ?? ""); - return event?.terminal === true || label === "assistant:completed"; + return event?.terminal === true || label === "assistant:completed" || label === "agentrun:assistant:message"; } function isRequestTraceEvent(event) { @@ -926,12 +930,13 @@ function isRequestTraceEvent(event) { function isSetupTraceEvent(event) { const label = String(event?.label ?? ""); - return /^session:|^stdio:|^prompt:|^thread:|^turn:started|^turn:start|turn\/start|codex turn|tool:codex-app-server/iu.test(label); + return /^session:|^stdio:|^prompt:|^thread:|^turn:started|^turn:start|turn\/start|codex turn|tool:codex-app-server/iu.test(label) || + /^agentrun:backend:(codex-app-server-starting|initialize:completed|thread\/start|thread\/resume|thread\/started|turn\/start|turn\/started)/iu.test(label); } function isCompletionTraceEvent(event) { const label = String(event?.label ?? ""); - return label.startsWith("turn:completed") || label.startsWith("result:completed"); + return label.startsWith("turn:completed") || label.startsWith("result:completed") || label === "agentrun:backend:turn/completed" || label === "agentrun:result:completed"; } function tracePromptText(event) { @@ -946,14 +951,24 @@ function traceNonEmptyString(value) { function isNoisyTraceEvent(event) { const label = String(event?.label ?? ""); if (isToolOutputChunkTraceEvent(event)) return false; + if (isCommandExecutionTraceEvent(event) || isAssistantMessageTraceEvent(event)) return false; + if (isRequestTraceEvent(event) || isSetupTraceEvent(event) || isCompletionTraceEvent(event)) return false; + if (isLowValueAgentRunBackendEvent(label)) return true; + if (/^agentrun:tool:item\/(started|completed)$/u.test(label)) return true; if (/token_count|outputDelta:chunk/iu.test(label)) return true; if (event?.type === "event" && !event.outputSummary && !event.message && !event.errorCode) return true; return false; } +function isLowValueAgentRunBackendEvent(label) { + return /^agentrun:backend:(run-created|command-created|runner-job-created|thread\/status\/changed|thread\/tokenUsage\/updated|account\/rateLimits\/updated|remoteControl\/status\/changed|configWarning|codex-app-server-closed|session-updated|command-terminal)$/u.test(String(label ?? "")); +} + function isToolOutputChunkTraceEvent(event) { const label = String(event?.label ?? ""); return label === "item/commandExecution/outputDelta" || + label === "agentrun:output:stdout" || + label === "agentrun:output:stderr" || (event?.type === "tool_call" && event?.status === "output_chunk" && !String(event?.toolName ?? "").startsWith("codex-app-server")); }