case-run: 严格提取 agent HWPOD 命令

This commit is contained in:
Codex Agent
2026-06-06 14:49:08 +08:00
parent c2c016b20f
commit 4e0481977b
2 changed files with 65 additions and 14 deletions
+5 -2
View File
@@ -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) {
+60 -12
View File
@@ -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<string, unknown>[]) {
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<string, unknown>[]) {
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<string, unknown>[]) {
});
}
function isCommandTraceRow(row: Record<string, unknown>, 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<string, unknown>, 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<string, unknown>[] {
return Array.isArray(value) ? value.filter((item) => item && typeof item === "object" && !Array.isArray(item)) as Record<string, unknown>[] : [];
}
@@ -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[]) {