From 4e0481977b3f5dafac7f955d4a4617c59b42277f Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sat, 6 Jun 2026 14:49:08 +0800 Subject: [PATCH] =?UTF-8?q?case-run:=20=E4=B8=A5=E6=A0=BC=E6=8F=90?= =?UTF-8?q?=E5=8F=96=20agent=20HWPOD=20=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/hwlab-cli/caserun.test.ts | 7 +++- tools/src/hwlab-caserun-lib.ts | 72 +++++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/tools/hwlab-cli/caserun.test.ts b/tools/hwlab-cli/caserun.test.ts index 663cd6c8..4276cee5 100644 --- a/tools/hwlab-cli/caserun.test.ts +++ b/tools/hwlab-cli/caserun.test.ts @@ -237,9 +237,11 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit assert.equal(result.payload.agent.stageStatus, "completed"); assert.equal(result.payload.agent.traceId.startsWith("trc_case_"), true); assert.equal(result.payload.agentTrace.hwpodBuildCommandCount, 1); - assert.equal(result.payload.evidence.agentTrace.commandCount, 3); + assert.equal(result.payload.evidence.agentTrace.commandCount, 4); assert.equal(result.payload.evidence.agentTrace.hwpodCommandCount, 3); assert.equal(result.payload.evidence.agentTrace.keilJobCandidates[0], "20260605_203835_798515c0"); + assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /cat .*hwpod-cli\/SKILL\.md/u.test(item.command ?? "") && /hwpod build/u.test(item.bodyPreview ?? "")), true); + assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /^hwpod build --spec \.hwlab\/hwpod-spec\.yaml/u.test(item.command ?? "")), true); assert.equal(result.payload.evidence.hwpod.source, "case-run-runner-post-agent-compile-check"); assert.match(result.payload.agentDiff.statusShort, /projects\/01_baseline\/main\.c/u); assert.equal(result.payload.build.autoEvaluation, false); @@ -316,7 +318,8 @@ function caseRunFlowFetch(requests: any[]) { return new Response(JSON.stringify({ ok: true, status: "completed", sourceEventCount: 6, renderedRowCount: 3, traceSummary: { terminalStatus: "completed", agentRun: { runId: "run_agent_trace", commandId: "cmd_agent_trace" } }, rows: [ { seq: 1, header: "ok commandExecution", body: "command: hwpod-ctl spec validate --spec .hwlab/hwpod-spec.yaml\nexitCode: 0" }, { seq: 2, header: "ok commandExecution", body: "command: hwpod inspect --spec .hwlab/hwpod-spec.yaml\nexitCode: 0" }, - { seq: 3, header: "ok commandExecution", body: "command: hwpod build --spec .hwlab/hwpod-spec.yaml\nstdout: {\"job_id\":\"20260605_203835_798515c0\"}\nexitCode: 0" } + { seq: 3, header: "ok commandExecution", body: "cat .agents/skills/hwpod-cli/SKILL.md stdout: use `hwpod build` for compile exitCode=0" }, + { seq: 4, header: "ok commandExecution", body: "command: hwpod build --spec .hwlab/hwpod-spec.yaml\nstdout: {\"job_id\":\"20260605_203835_798515c0\"}\nexitCode: 0" } ] }), { status: 200, headers: { "content-type": "application/json" } }); } if (body.ops?.[0]?.args?.argv?.includes("status") && body.ops?.length === 1) { diff --git a/tools/src/hwlab-caserun-lib.ts b/tools/src/hwlab-caserun-lib.ts index a7fdb612..4430a41b 100644 --- a/tools/src/hwlab-caserun-lib.ts +++ b/tools/src/hwlab-caserun-lib.ts @@ -882,10 +882,14 @@ function summarizeAgentTrace(traceId: string, httpStatus: number, body: any): Ag const traceBody = body?.body ?? body; const rows = arrayOfObjects(traceBody?.rows ?? traceBody?.renderedRows ?? traceBody?.traceRows); const events = arrayOfObjects(traceBody?.events ?? traceBody?.trace?.events ?? traceBody?.sourceEvents); - const commands = [...commandsFromTraceRows(rows), ...commandsFromTraceEvents(events)].slice(0, MAX_AGENT_TRACE_COMMANDS); - const searchable = commands.map((command) => [command.command, command.bodyPreview, command.header, command.toolName].map((item) => text(item)).filter(Boolean).join("\n")).join("\n"); - const hwpodCommandCount = commands.filter((command) => mentionsHwpod(command)).length; - const hwpodBuildCommandCount = commands.filter((command) => mentionsHwpodBuild(command)).length; + const allCommands = [...commandsFromTraceRows(rows), ...commandsFromTraceEvents(events)]; + const commands = selectAgentTraceCommands(allCommands); + const searchable = [ + text(traceBody?.assistantText ?? body?.assistantText), + ...allCommands.map((command) => [command.command, command.bodyPreview].map((item) => text(item)).filter(Boolean).join("\n")) + ].filter(Boolean).join("\n"); + const hwpodCommandCount = allCommands.filter((command) => mentionsHwpod(command)).length; + const hwpodBuildCommandCount = allCommands.filter((command) => mentionsHwpodBuild(command)).length; return clean({ traceId, status: text(traceBody?.status ?? body?.status) || "recorded", @@ -906,9 +910,9 @@ function commandsFromTraceRows(rows: Record[]) { return rows.flatMap((row, index) => { const header = text(row.header ?? row.title ?? row.label); const body = text(row.body ?? row.content ?? row.text ?? row.message); - const command = text((row as any).command ?? commandFromText(`${header}\n${body}`)); - const looksLikeCommand = /commandExecution|tool|hwpod|hwpod-ctl|hwpod-cli|keil/iu.test(`${header}\n${body}\n${command}`); - if (!looksLikeCommand) return []; + if (!isCommandTraceRow(row, header)) return []; + const command = text((row as any).command) || commandFromText(`${header}\n${body}`) || commandFromRenderedToolBody(body); + if (!command) return []; return [clean({ source: "trace-row", seq: numberOption(row.seq) ?? index + 1, @@ -927,9 +931,9 @@ function commandsFromTraceEvents(events: Record[]) { return events.flatMap((event, index) => { const toolName = text(event.toolName ?? event.name ?? event.label); const message = text(event.message ?? event.text ?? event.content ?? event.output ?? event.stdout); - const command = text(event.command ?? event.input ?? event.args ?? commandFromText(message)); - const marker = `${event.type ?? ""}\n${toolName}\n${message}\n${command}`; - if (!/commandExecution|tool|hwpod|hwpod-ctl|hwpod-cli|keil/iu.test(marker)) return []; + if (!isCommandTraceEvent(event, toolName)) return []; + const command = text(event.command ?? event.input ?? event.args) || commandFromExecutionText(message) || commandFromText(message) || commandFromRenderedToolBody(message); + if (!command) return []; return [clean({ source: "trace-event", seq: numberOption(event.seq ?? event.index) ?? index + 1, @@ -943,6 +947,31 @@ function commandsFromTraceEvents(events: Record[]) { }); } +function isCommandTraceRow(row: Record, header: string) { + return /^tool:/u.test(text(row.rowId ?? row.id)) || /commandExecution|tool_call/iu.test(header) || /commandExecution|tool_call/iu.test(text(row.toolName ?? row.name)); +} + +function isCommandTraceEvent(event: Record, toolName: string) { + const type = text(event.type ?? event.kind ?? event.event); + return /commandExecution/iu.test(toolName) || /commandExecution|tool_call/iu.test(type); +} + +function selectAgentTraceCommands(commands: AgentTraceCommand[]) { + const selected: AgentTraceCommand[] = []; + for (const command of [ + ...commands.filter((item) => mentionsHwpodBuild(item)), + ...commands.filter((item) => mentionsHwpod(item)), + ...commands.slice(0, 10), + ...commands.slice(-10) + ]) { + const key = `${command.source}:${command.seq ?? ""}:${command.rowId ?? ""}:${command.command ?? ""}:${command.status ?? ""}`; + if (selected.some((item) => `${item.source}:${item.seq ?? ""}:${item.rowId ?? ""}:${item.command ?? ""}:${item.status ?? ""}` === key)) continue; + selected.push(command); + if (selected.length >= MAX_AGENT_TRACE_COMMANDS) break; + } + return selected; +} + function arrayOfObjects(value: unknown): Record[] { return Array.isArray(value) ? value.filter((item) => item && typeof item === "object" && !Array.isArray(item)) as Record[] : []; } @@ -952,17 +981,36 @@ function commandFromText(value: string) { return text(match?.[1]); } +function commandFromRenderedToolBody(value: string) { + const raw = text(value); + if (!raw) return ""; + const match = raw.match(/^(.+?)(?:\s+stdout:|\s+stderr:|\s+exitCode=|\s+exit\s*code\b)/isu); + return text(match?.[1] ?? raw); +} + +function commandFromExecutionText(value: string) { + const raw = text(value); + const match = raw.match(/commandExecution\s+(?:inProgress|completed|started|failed):\s+(.+?)(?:\s+exit=|\s+durationMs=|\s*$)/isu); + return text(match?.[1]); +} + function exitCodeFromText(value: string) { const match = value.match(/exit\s*code\s*[:=]?\s*(-?\d+)/iu) ?? value.match(/exitCode\s*[:=]?\s*(-?\d+)/u); return numberOption(match?.[1]); } function mentionsHwpod(command: AgentTraceCommand) { - return /\bhwpod(?:-ctl|-cli)?\b|tools\/hwpod-cli\.ts/iu.test([command.command, command.bodyPreview, command.header, command.toolName].map((item) => text(item)).join("\n")); + const raw = unquoteShellCommand(text(command.command)); + return /^(?:hwpod|hwpod-ctl|hwpod-cli)(?:\s|$)/iu.test(raw) || /^(?:bun\s+)?tools\/hwpod-cli\.ts(?:\s|$)/iu.test(raw); } function mentionsHwpodBuild(command: AgentTraceCommand) { - return /(?:\bhwpod(?:-cli)?\b|tools\/hwpod-cli\.ts)[^\n]*(?:\bbuild\b)/iu.test([command.command, command.bodyPreview, command.header, command.toolName].map((item) => text(item)).join("\n")); + const raw = unquoteShellCommand(text(command.command)); + return /^(?:hwpod|hwpod-cli)\s+build(?:\s|$)/iu.test(raw) || /^(?:bun\s+)?tools\/hwpod-cli\.ts\s+build(?:\s|$)/iu.test(raw); +} + +function unquoteShellCommand(value: string) { + return value.replace(/^\/bin\/sh\s+-lc\s+/u, "").replace(/^['"]|['"]$/gu, "").trim(); } function uniqueStrings(values: string[]) {