fix: archive CaseRun agent transcript artifacts
This commit is contained in:
@@ -350,6 +350,11 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
"summary.md",
|
||||
"run.json",
|
||||
"result.json",
|
||||
"agent-trace.json",
|
||||
"agent-trace.md",
|
||||
"agent-messages.json",
|
||||
"agent-transcript.md",
|
||||
"final-response.md",
|
||||
"agent-prompt.md",
|
||||
"agent-diff.patch",
|
||||
path.join(".hwlab", "hwpod-spec.yaml"),
|
||||
@@ -366,9 +371,30 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
assert.equal(manifest.trace.agentRun.runId, "run_agent_trace");
|
||||
assert.equal(manifest.prompt.sha256, result.payload.evidence.agent.promptSha256);
|
||||
assert.equal(manifest.diff.sha256, result.payload.evidence.agentDiff.diffPatchSha256);
|
||||
assert.equal(manifest.readableAgent.renderer, "tools/src/hwlab-cli/trace-renderer:traceDisplayRows");
|
||||
assert.equal(manifest.readableAgent.messagesPath, "agent-messages.json");
|
||||
assert.equal(manifest.readableAgent.tracePath, "agent-trace.md");
|
||||
assert.equal(manifest.readableAgent.transcriptPath, "agent-transcript.md");
|
||||
assert.equal(manifest.readableAgent.finalResponsePath, "final-response.md");
|
||||
assert.equal(manifest.readableAgent.finalResponse.present, true);
|
||||
assert.equal(manifest.readableAgent.finalResponse.text, "done");
|
||||
assert.equal(manifest.decisions.autoEvaluation, false);
|
||||
assert.equal(manifest.files.some((item: any) => item.path === "agent-messages.json"), true);
|
||||
assert.equal(manifest.files.some((item: any) => item.path === "agent-trace.md"), true);
|
||||
assert.equal(manifest.files.some((item: any) => item.path === "final-response.md"), true);
|
||||
assert.equal(manifest.files.some((item: any) => item.path === "worker.stdout.log"), true);
|
||||
assert.equal(manifest.files.some((item: any) => item.path === ".hwlab/hwpod-spec.yaml"), true);
|
||||
const messages = JSON.parse(await readFile(path.join(registryRunDir, "agent-messages.json"), "utf8"));
|
||||
assert.equal(messages.renderer, "tools/src/hwlab-cli/trace-renderer:traceDisplayRows");
|
||||
assert.equal(messages.finalResponse.present, true);
|
||||
assert.match(JSON.stringify(messages.rows), /hwpod build --spec \.hwlab\/hwpod-spec\.yaml/u);
|
||||
const traceMarkdown = await readFile(path.join(registryRunDir, "agent-trace.md"), "utf8");
|
||||
assert.match(traceMarkdown, /renderer: tools\/src\/hwlab-cli\/trace-renderer:traceDisplayRows/u);
|
||||
assert.match(traceMarkdown, /## Subject Diff/u);
|
||||
assert.match(traceMarkdown, /\+printf\("hello"\);/u);
|
||||
const finalResponse = await readFile(path.join(registryRunDir, "final-response.md"), "utf8");
|
||||
assert.match(finalResponse, /present: true/u);
|
||||
assert.match(finalResponse, /done/u);
|
||||
assert.equal(result.payload.collect.caseRepoRunDir, registryRunDir);
|
||||
assert.equal(result.payload.collect.artifactManifestPath, path.join(registryRunDir, "artifact-manifest.json"));
|
||||
assert.equal(result.payload.collect.caseRepoFiles.some((item: string) => item.endsWith("artifact-manifest.json")), true);
|
||||
@@ -394,6 +420,100 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
}
|
||||
});
|
||||
|
||||
test("case run archives rendered trace transcript when finalResponse is null", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
const caseRepo = path.join(root, "hwlab-case-registry");
|
||||
const cwd = path.join(root, "hwlab");
|
||||
const runId = "run-final-null";
|
||||
const runDir = path.join(cwd, ".state", "hwlab-cli", "caserun", runId);
|
||||
const registryRunDir = path.join(caseRepo, "runs", "d601-f103-v2-compile", runId);
|
||||
try {
|
||||
await mkdir(path.join(caseRepo, ".git"), { recursive: true });
|
||||
await writeFile(path.join(caseRepo, ".git", "HEAD"), "ref: refs/heads/main\n", "utf8");
|
||||
await mkdir(path.join(runDir, ".hwlab"), { recursive: true });
|
||||
await writeFile(path.join(runDir, ".hwlab", "hwpod-spec.yaml"), hwpodSpecText(), "utf8");
|
||||
await writeFile(path.join(runDir, "agent-prompt.md"), "prompt\n", "utf8");
|
||||
await writeFile(path.join(runDir, "agent-diff.patch"), "diff --git a/main.c b/main.c\n+printf(\"hello\");\n", "utf8");
|
||||
await writeFile(path.join(runDir, "worker.stdout.log"), "worker complete\n", "utf8");
|
||||
await writeFile(path.join(runDir, "worker.stderr.log"), "", "utf8");
|
||||
const run = {
|
||||
caseId: "d601-f103-v2-compile",
|
||||
runId,
|
||||
runDir,
|
||||
caseRepo,
|
||||
caseDir: "",
|
||||
caseFile: "",
|
||||
sourceSpecPath: "",
|
||||
specPath: path.join(runDir, ".hwlab", "hwpod-spec.yaml"),
|
||||
apiUrl: "http://api.test",
|
||||
compileOnly: true,
|
||||
createdAt: "2026-06-06T00:00:00.000Z",
|
||||
updatedAt: "2026-06-06T00:00:10.000Z",
|
||||
status: "completed",
|
||||
stage: "completed",
|
||||
definition: {},
|
||||
subject: { repoLocalPath: SUBJECT_REPO_LOCAL_PATH, commitId: SUBJECT_COMMIT_ID, subdir: "projects/01_baseline", worktreePath: "F:\\Work\\HWLAB-CASE-F103\\.worktree\\caserun-run-final-null", workspacePath: "", setup: {} },
|
||||
agent: { stageStatus: "failed", baseUrl: "http://web.test", projectId: "prj_hwpod_workbench", providerProfile: "deepseek", conversationId: "cnv_final_null", sessionId: "ses_final_null", threadId: "thread-final-null", traceId: "trc_final_null", promptPath: path.join(runDir, "agent-prompt.md"), promptSha256: "prompt-sha", result: { ok: true, status: "failed", finalResponse: null }, polls: 2, timedOut: false, sessionResponse: null },
|
||||
agentTrace: { traceId: "trc_final_null", status: "failed", httpStatus: 200, sourceEventCount: 3, renderedRowCount: 0, commandCount: 0, hwpodCommandCount: 0, hwpodBuildCommandCount: 0, keilJobCandidates: [], terminalStatus: "failed", agentRun: { runId: "run_agent_final_null", commandId: "cmd_final_null" }, commands: [] },
|
||||
agentDiff: { statusShort: " M main.c\n", diffStat: " main.c | 1 +\n", diffPatchPath: path.join(runDir, "agent-diff.patch"), diffPatchSha256: "diff-sha", sourceRootStatusShort: "", requests: [] },
|
||||
evidencePath: path.join(runDir, "evidence.json"),
|
||||
_control: { status: "completed", stage: "completed", stdoutPath: path.join(runDir, "worker.stdout.log"), stderrPath: path.join(runDir, "worker.stderr.log"), completedAt: "2026-06-06T00:00:10.000Z", resultPath: path.join(runDir, "result.json") }
|
||||
};
|
||||
const trace = {
|
||||
ok: true,
|
||||
status: "failed",
|
||||
traceId: "trc_final_null",
|
||||
finalResponse: null,
|
||||
events: [
|
||||
{ traceId: "trc_final_null", seq: 1, label: "agentrun:assistant:message", type: "assistant", status: "running", message: "我先检查当前 HWPOD。", createdAt: "2026-06-06T00:00:01.000Z" },
|
||||
{ traceId: "trc_final_null", seq: 2, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", command: "/bin/sh -lc 'hwpod build --spec .hwlab/hwpod-spec.yaml'", stdoutSummary: "build completed", exitCode: 0, createdAt: "2026-06-06T00:00:02.000Z" },
|
||||
{ traceId: "trc_final_null", seq: 3, label: "agentrun:terminal:failed", type: "result", terminal: true, status: "failed", message: "Error running remote compact task: unexpected status 404 Not Found", createdAt: "2026-06-06T00:00:03.000Z" }
|
||||
],
|
||||
error: { message: "Error running remote compact task: unexpected status 404 Not Found" }
|
||||
};
|
||||
const evidence = {
|
||||
contractVersion: "hwpod-case-run-evidence-v1",
|
||||
caseId: run.caseId,
|
||||
runId,
|
||||
compileOnly: true,
|
||||
autoEvaluation: false,
|
||||
status: "recorded",
|
||||
subject: run.subject,
|
||||
agent: { traceId: "trc_final_null", sessionId: "ses_final_null", conversationId: "cnv_final_null", threadId: "thread-final-null", providerProfile: "deepseek", projectId: "prj_hwpod_workbench", promptSha256: "prompt-sha" },
|
||||
agentTrace: run.agentTrace,
|
||||
agentDiff: run.agentDiff,
|
||||
hwpod: { source: "case-run-runner-post-agent-compile-check", exitCode: 0 },
|
||||
artifacts: [],
|
||||
decisions: { downloadSkipped: true }
|
||||
};
|
||||
await writeJsonFile(path.join(runDir, "run.json"), run);
|
||||
await writeJsonFile(path.join(runDir, "agent-trace.json"), trace);
|
||||
await writeJsonFile(path.join(runDir, "evidence.json"), evidence);
|
||||
await writeJsonFile(path.join(runDir, "result.json"), { ok: true, action: "case.run", status: "completed", caseId: run.caseId, runId, runDir, collect: { caseRepoFiles: [] }, evidence: {} });
|
||||
|
||||
const result = await runHwlabCli(["case", "run", "result", runId], { cwd, now: () => "2026-06-06T00:00:11.000Z" });
|
||||
|
||||
assert.equal(result.exitCode, 0);
|
||||
const manifest = JSON.parse(await readFile(path.join(registryRunDir, "artifact-manifest.json"), "utf8"));
|
||||
assert.equal(manifest.readableAgent.finalResponse.present, false);
|
||||
assert.match(manifest.readableAgent.finalResponse.reason, /404 Not Found/u);
|
||||
const messages = JSON.parse(await readFile(path.join(registryRunDir, "agent-messages.json"), "utf8"));
|
||||
assert.equal(messages.finalResponse.present, false);
|
||||
assert.match(JSON.stringify(messages.rows), /我先检查当前 HWPOD/u);
|
||||
assert.match(JSON.stringify(messages.rows), /hwpod build --spec \.hwlab\/hwpod-spec\.yaml/u);
|
||||
const traceMarkdown = await readFile(path.join(registryRunDir, "agent-trace.md"), "utf8");
|
||||
assert.match(traceMarkdown, /finalResponse=null/u);
|
||||
assert.match(traceMarkdown, /Error running remote compact task/u);
|
||||
assert.match(traceMarkdown, /## Subject Diff/u);
|
||||
assert.match(traceMarkdown, /\+printf\("hello"\);/u);
|
||||
const finalMarkdown = await readFile(path.join(registryRunDir, "final-response.md"), "utf8");
|
||||
assert.match(finalMarkdown, /present: false/u);
|
||||
assert.match(finalMarkdown, /404 Not Found/u);
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("case run status reports latest run stage when async control stage is stale", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
const cwd = path.join(root, "hwlab");
|
||||
|
||||
@@ -4,6 +4,7 @@ import { closeSync, openSync } from "node:fs";
|
||||
import { copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import { traceDisplayRows, traceNoiseEventCount, type TraceEventRow } from "./hwlab-cli/trace-renderer.ts";
|
||||
import { readHwpodSpec } from "./hwpod-harness-lib.ts";
|
||||
|
||||
const DEFAULT_API_URL = "http://74.48.78.17:19667";
|
||||
@@ -17,6 +18,7 @@ const MAX_AGENT_TRACE_COMMANDS = 30;
|
||||
const HWLAB_API_KEY_PREFIX = "hwl_live_";
|
||||
const STANDARD_CASE_REPO_PATH = "/root/hwlab-case-registry";
|
||||
const REMOVED_CASE_REPO_PATH = "/root/hwpod-cases";
|
||||
const CASE_TRACE_RENDERER = "tools/src/hwlab-cli/trace-renderer:traceDisplayRows";
|
||||
|
||||
type EnvLike = Record<string, string | undefined>;
|
||||
type ParsedArgs = Record<string, unknown> & { _: string[] };
|
||||
@@ -156,6 +158,16 @@ type CaseRegistryArchive = {
|
||||
manifest: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type CaseReadableAgentArchive = {
|
||||
messagesRel: string;
|
||||
traceRel: string;
|
||||
transcriptRel: string;
|
||||
finalResponseRel: string;
|
||||
generatedFiles: string[];
|
||||
traceRender: Record<string, unknown>;
|
||||
finalResponse: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export async function caseCommand(context: CaseContext) {
|
||||
const subcommand = context.rest[0] || "help";
|
||||
if (["help", "--help", "-h"].includes(subcommand) || context.parsed.help === true) return caseHelp();
|
||||
@@ -718,6 +730,7 @@ async function collectAgentTraceEvidence(context: CaseContext, run: PreparedCase
|
||||
const nextRun = await updateRun(context, run, { agentTrace: trace, stage: "agent-trace-collected" });
|
||||
return { run: nextRun, trace };
|
||||
}
|
||||
await writeJson(path.join(run.runDir, "agent-trace.json"), response.body);
|
||||
const trace = summarizeAgentTrace(traceId, response.status, response.body);
|
||||
const nextRun = await updateRun(context, run, { agentTrace: trace, stage: "agent-trace-collected" });
|
||||
return { run: nextRun, trace };
|
||||
@@ -1389,6 +1402,8 @@ async function archiveCaseRunArtifacts(context: CaseContext, run: PreparedCaseRu
|
||||
["evidence.json", path.join(run.runDir, "evidence.json")],
|
||||
["summary.md", undefined]
|
||||
]);
|
||||
const readableAgent = await writeReadableAgentArtifacts(caseRepoRunDir, run, evidence);
|
||||
for (const rel of readableAgent.generatedFiles) materialized.set(rel, undefined);
|
||||
const skippedFiles: string[] = [];
|
||||
for (const entry of caseRunArtifactEntries(run)) {
|
||||
const rel = artifactRel(entry.rel);
|
||||
@@ -1399,7 +1414,7 @@ async function archiveCaseRunArtifacts(context: CaseContext, run: PreparedCaseRu
|
||||
|
||||
const files: CaseRegistryArchiveFile[] = [];
|
||||
for (const [rel, source] of materialized) files.push(await registryArtifactFileRecord(run, caseRepoRunDir, rel, source));
|
||||
const manifest = caseRunArtifactManifest(context, run, evidence, files, skippedFiles);
|
||||
const manifest = caseRunArtifactManifest(context, run, evidence, files, skippedFiles, readableAgent);
|
||||
const artifactManifestPath = path.join(caseRepoRunDir, "artifact-manifest.json");
|
||||
await writeJson(artifactManifestPath, manifest);
|
||||
const caseRepoFiles = [...files.map((file) => artifactRel(path.join(runRel, file.path))), artifactRel(path.join(runRel, "artifact-manifest.json"))];
|
||||
@@ -1420,6 +1435,159 @@ function collectSummaryFromArchive(run: PreparedCaseRun, evidence: any, archive:
|
||||
};
|
||||
}
|
||||
|
||||
async function writeReadableAgentArtifacts(caseRepoRunDir: string, run: PreparedCaseRun, evidence: any): Promise<CaseReadableAgentArchive> {
|
||||
const messagesRel = "agent-messages.json";
|
||||
const traceRel = "agent-trace.md";
|
||||
const transcriptRel = "agent-transcript.md";
|
||||
const finalResponseRel = "final-response.md";
|
||||
const traceBody = await archivedAgentTraceBody(run, evidence);
|
||||
const events = arrayOfObjects(traceBody?.events ?? traceBody?.trace?.events ?? traceBody?.sourceEvents);
|
||||
const rows = traceDisplayRows(traceBody ?? {}, events);
|
||||
const finalResponse = agentFinalResponse(run, evidence, traceBody, rows);
|
||||
const diffPatch = await readTextIfExists(evidence.agentDiff?.diffPatchPath ?? run.agentDiff?.diffPatchPath ?? path.join(run.runDir, "agent-diff.patch"));
|
||||
const messages = clean({
|
||||
contractVersion: "hwpod-case-run-agent-messages-v1",
|
||||
renderer: CASE_TRACE_RENDERER,
|
||||
traceId: evidence.agent?.traceId ?? run.agent?.traceId ?? traceBody?.traceId ?? run.agentTrace?.traceId,
|
||||
conversationId: evidence.agent?.conversationId ?? run.agent?.conversationId,
|
||||
sessionId: evidence.agent?.sessionId ?? run.agent?.sessionId,
|
||||
threadId: evidence.agent?.threadId ?? run.agent?.threadId,
|
||||
status: traceBody?.traceStatus ?? traceBody?.status ?? run.agentTrace?.status ?? evidence.agentTrace?.status,
|
||||
sourceEventCount: traceBody?.eventCount ?? traceBody?.sourceEventCount ?? events.length,
|
||||
renderedRowCount: rows.length,
|
||||
noiseEventCount: traceNoiseEventCount(events),
|
||||
finalResponse,
|
||||
rows: rows.map(agentMessageRowForArchive),
|
||||
diff: clean({ path: "agent-diff.patch", sha256: evidence.agentDiff?.diffPatchSha256 ?? run.agentDiff?.diffPatchSha256, statusShort: evidence.agentDiff?.statusShort ?? run.agentDiff?.statusShort, diffStat: evidence.agentDiff?.diffStat ?? run.agentDiff?.diffStat, patchIncludedInTranscript: true })
|
||||
});
|
||||
const transcript = renderAgentTranscript(run, evidence, messages, rows, diffPatch);
|
||||
await writeJson(path.join(caseRepoRunDir, messagesRel), messages);
|
||||
await writeFile(path.join(caseRepoRunDir, traceRel), transcript, "utf8");
|
||||
await writeFile(path.join(caseRepoRunDir, transcriptRel), transcript, "utf8");
|
||||
await writeFile(path.join(caseRepoRunDir, finalResponseRel), renderFinalResponseArtifact(run, evidence, finalResponse, rows), "utf8");
|
||||
return {
|
||||
messagesRel,
|
||||
traceRel,
|
||||
transcriptRel,
|
||||
finalResponseRel,
|
||||
generatedFiles: [messagesRel, traceRel, transcriptRel, finalResponseRel],
|
||||
traceRender: clean({ renderer: CASE_TRACE_RENDERER, status: messages.status, sourceEventCount: messages.sourceEventCount, renderedRowCount: messages.renderedRowCount, noiseEventCount: messages.noiseEventCount }),
|
||||
finalResponse
|
||||
};
|
||||
}
|
||||
|
||||
async function archivedAgentTraceBody(run: PreparedCaseRun, evidence: any) {
|
||||
const rawTrace = await readJsonIfExists(path.join(run.runDir, "agent-trace.json"));
|
||||
return rawTrace ?? agentTraceBody(run, evidence);
|
||||
}
|
||||
|
||||
function agentTraceBody(run: PreparedCaseRun, evidence: any) {
|
||||
const result = run.agent?.result ?? evidence.agent?.result ?? null;
|
||||
return result?.body && typeof result.body === "object" ? result.body : result;
|
||||
}
|
||||
|
||||
function agentFinalResponse(run: PreparedCaseRun, evidence: any, traceBody: any, rows: TraceEventRow[]) {
|
||||
const candidates = [
|
||||
traceBody?.finalResponse,
|
||||
traceBody?.terminalEvidence?.finalResponse,
|
||||
run.agent?.result?.finalResponse,
|
||||
run.agent?.result?.reply,
|
||||
evidence.agent?.finalResponse
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const body = text((candidate as any)?.text ?? (candidate as any)?.content ?? (candidate as any)?.message);
|
||||
if (body) return clean({ present: true, status: text((candidate as any)?.status) || "completed", source: text((candidate as any)?.source) || "agent-result", text: body, textChars: body.length, traceId: text((candidate as any)?.traceId ?? traceBody?.traceId ?? run.agent?.traceId) });
|
||||
}
|
||||
const terminal = rows.filter((row) => row.terminal === true || row.tone === "blocked").slice(-5).map(agentMessageRowForArchive);
|
||||
return clean({
|
||||
present: false,
|
||||
value: null,
|
||||
reason: finalResponseMissingReason(traceBody, rows),
|
||||
status: text(traceBody?.status ?? traceBody?.traceStatus ?? run.agent?.stageStatus),
|
||||
terminalRows: terminal,
|
||||
error: compactObject(traceBody?.error ?? run.agent?.error ?? null)
|
||||
});
|
||||
}
|
||||
|
||||
function finalResponseMissingReason(traceBody: any, rows: TraceEventRow[]) {
|
||||
const errorText = text(traceBody?.error?.message ?? traceBody?.terminalEvidence?.error?.message);
|
||||
if (errorText) return errorText;
|
||||
const terminalBody = rows.slice().reverse().map((row) => text(row.body)).find((body) => /error|failed|404|not found|blocked|timeout/iu.test(body));
|
||||
return terminalBody ? clipText(terminalBody, 1200) : "finalResponse=null; no authoritative final assistant response was returned by the trace/result payload";
|
||||
}
|
||||
|
||||
function agentMessageRowForArchive(row: TraceEventRow) {
|
||||
return clean({ rowId: row.rowId, seq: row.seq, tone: row.tone, header: row.header, terminal: row.terminal === true ? true : undefined, bodyFormat: row.bodyFormat, body: row.body });
|
||||
}
|
||||
|
||||
function renderAgentTranscript(run: PreparedCaseRun, evidence: any, messages: any, rows: TraceEventRow[], diffPatch: string) {
|
||||
const sections = [
|
||||
`# CaseRun Agent Transcript`,
|
||||
``,
|
||||
`- caseId: ${run.caseId}`,
|
||||
`- runId: ${run.runId}`,
|
||||
`- traceId: ${messages.traceId ?? ""}`,
|
||||
`- conversationId: ${messages.conversationId ?? ""}`,
|
||||
`- sessionId: ${messages.sessionId ?? ""}`,
|
||||
`- threadId: ${messages.threadId ?? ""}`,
|
||||
`- renderer: ${CASE_TRACE_RENDERER}`,
|
||||
`- finalResponse: ${messages.finalResponse?.present === true ? "present" : "null"}`,
|
||||
`- autoEvaluation: false`,
|
||||
``,
|
||||
`## Messages`,
|
||||
rows.length > 0 ? rows.map(renderTranscriptRow).join("\n\n") : `_No rendered trace rows were returned._`,
|
||||
``,
|
||||
`## Final Response`,
|
||||
messages.finalResponse?.present === true ? text(messages.finalResponse.text) : [`finalResponse=null`, `reason: ${text(messages.finalResponse?.reason)}`].join("\n"),
|
||||
``,
|
||||
`## Subject Diff`,
|
||||
``,
|
||||
`statusShort:`,
|
||||
`\`\`\`text`,
|
||||
text(evidence.agentDiff?.statusShort ?? run.agentDiff?.statusShort) || "(empty)",
|
||||
`\`\`\``,
|
||||
``,
|
||||
`diffStat:`,
|
||||
`\`\`\`text`,
|
||||
text(evidence.agentDiff?.diffStat ?? run.agentDiff?.diffStat) || "(empty)",
|
||||
`\`\`\``,
|
||||
``,
|
||||
`patch:`,
|
||||
`\`\`\`diff`,
|
||||
diffPatch || "(empty)",
|
||||
`\`\`\``
|
||||
];
|
||||
return `${sections.join("\n")}\n`;
|
||||
}
|
||||
|
||||
function renderTranscriptRow(row: TraceEventRow) {
|
||||
const body = text(row.body);
|
||||
return [`### ${row.header}`, ``, `- rowId: ${row.rowId}`, row.terminal === true ? `- terminal: true` : null, ``, body ? body : `_No body._`].filter((item) => item !== null).join("\n");
|
||||
}
|
||||
|
||||
function renderFinalResponseArtifact(run: PreparedCaseRun, evidence: any, finalResponse: any, rows: TraceEventRow[]) {
|
||||
const lines = [
|
||||
`# CaseRun Final Response`,
|
||||
``,
|
||||
`- caseId: ${run.caseId}`,
|
||||
`- runId: ${run.runId}`,
|
||||
`- traceId: ${evidence.agent?.traceId ?? run.agent?.traceId ?? ""}`,
|
||||
`- present: ${finalResponse.present === true}`,
|
||||
``
|
||||
];
|
||||
if (finalResponse.present === true) return `${[...lines, text(finalResponse.text)].join("\n")}\n`;
|
||||
const terminalRows = rows.filter((row) => row.terminal === true || row.tone === "blocked").slice(-5);
|
||||
return `${[
|
||||
...lines,
|
||||
`finalResponse=null`,
|
||||
``,
|
||||
`reason: ${text(finalResponse.reason)}`,
|
||||
``,
|
||||
`## Terminal/Error Rows`,
|
||||
terminalRows.length > 0 ? terminalRows.map(renderTranscriptRow).join("\n\n") : `_No terminal/error rows were rendered._`
|
||||
].join("\n")}\n`;
|
||||
}
|
||||
|
||||
async function refreshCompletedRunArchive(context: CaseContext, run: PreparedCaseRun, evidence?: any) {
|
||||
if (context.parsed.noCaseRepoRecord === true) return null;
|
||||
if (!text(run.caseRepo)) return null;
|
||||
@@ -1433,6 +1601,7 @@ function caseRunArtifactEntries(run: PreparedCaseRun) {
|
||||
return [
|
||||
{ rel: "run.json", source: path.join(run.runDir, "run.json") },
|
||||
{ rel: "result.json", source: path.join(run.runDir, "result.json") },
|
||||
{ rel: "agent-trace.json", source: path.join(run.runDir, "agent-trace.json") },
|
||||
{ rel: "agent-prompt.md", source: path.join(run.runDir, "agent-prompt.md") },
|
||||
{ rel: "agent-diff.patch", source: path.join(run.runDir, "agent-diff.patch") },
|
||||
{ rel: path.join(".hwlab", "hwpod-spec.yaml"), source: run.specPath },
|
||||
@@ -1464,7 +1633,7 @@ async function registryArtifactFileRecord(run: PreparedCaseRun, caseRepoRunDir:
|
||||
});
|
||||
}
|
||||
|
||||
function caseRunArtifactManifest(context: CaseContext, run: PreparedCaseRun, evidence: any, files: CaseRegistryArchiveFile[], skippedFiles: string[]) {
|
||||
function caseRunArtifactManifest(context: CaseContext, run: PreparedCaseRun, evidence: any, files: CaseRegistryArchiveFile[], skippedFiles: string[], readableAgent: CaseReadableAgentArchive) {
|
||||
return clean({
|
||||
contractVersion: "hwpod-case-run-artifact-manifest-v1",
|
||||
caseId: run.caseId,
|
||||
@@ -1482,6 +1651,15 @@ function caseRunArtifactManifest(context: CaseContext, run: PreparedCaseRun, evi
|
||||
threadId: evidence.agent?.threadId ?? run.agent?.threadId,
|
||||
traceId: evidence.agent?.traceId ?? run.agent?.traceId
|
||||
}),
|
||||
readableAgent: clean({
|
||||
renderer: CASE_TRACE_RENDERER,
|
||||
messagesPath: readableAgent.messagesRel,
|
||||
tracePath: readableAgent.traceRel,
|
||||
transcriptPath: readableAgent.transcriptRel,
|
||||
finalResponsePath: readableAgent.finalResponseRel,
|
||||
traceRender: readableAgent.traceRender,
|
||||
finalResponse: readableAgent.finalResponse
|
||||
}),
|
||||
prompt: clean({ path: "agent-prompt.md", sha256: evidence.agent?.promptSha256 ?? run.agent?.promptSha256 }),
|
||||
diff: clean({ path: "agent-diff.patch", sha256: evidence.agentDiff?.diffPatchSha256 ?? run.agentDiff?.diffPatchSha256, statusShort: evidence.agentDiff?.statusShort ?? run.agentDiff?.statusShort, diffStat: evidence.agentDiff?.diffStat ?? run.agentDiff?.diffStat }),
|
||||
keilJob: evidence.keilJob ?? null,
|
||||
@@ -1586,6 +1764,10 @@ async function readJsonIfExists(file: string) {
|
||||
try { return JSON.parse(await readFile(file, "utf8")); } catch { return null; }
|
||||
}
|
||||
|
||||
async function readTextIfExists(file: string) {
|
||||
try { return await readFile(file, "utf8"); } catch { return ""; }
|
||||
}
|
||||
|
||||
function controlFromRun(run: any) {
|
||||
return run?._control && typeof run._control === "object" ? run._control : {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user