case-run: 严格提取 agent HWPOD 命令
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
Reference in New Issue
Block a user