import assert from "node:assert/strict"; import { mkdtemp, rm } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { AGENT_SESSION_STATUSES, WORKER_SESSION_STATUSES, advanceAgentSession, advanceWorkerSession, createAgentRunPlan, createSkillsManifest, finalizeAgentRun } from "./index.mjs"; import { createLocalAgentSession, getLocalAgentEvidence, getLocalAgentStatus, getLocalAgentTrace, runLocalWorkerDryRun } from "./runtime.mjs"; test("agent runtime run plan exposes create/start/trace/finish/cleanup", () => { const plan = createAgentRunPlan({ agentSessionId: "agt_01J00000000000000000000000", projectId: "prj_01J00000000000000000000000", goal: "verify runtime skeleton", prompt: "dry run" }); assert.deepEqual(AGENT_SESSION_STATUSES, ["create", "start", "trace", "finish", "cleanup"]); assert.deepEqual(WORKER_SESSION_STATUSES, ["create", "start", "trace", "finish", "cleanup"]); assert.equal(plan.agentSession.status, "start"); assert.equal(plan.workerSession.status, "start"); assert.equal(plan.events.map((event) => event.state).join(","), "create,start,trace,finish,cleanup"); assert.equal(plan.traceEvent.message, "dry run"); assert.equal(plan.evidenceRecord.projectId, "prj_01J00000000000000000000000"); assert.equal(plan.evidenceRecord.metadata.workspaceVolumeId, "vol_agt_01J00000000000000000000000"); }); test("finalizeAgentRun marks cleanup and keeps evidence visible", () => { const plan = createAgentRunPlan({ agentSessionId: "agt_01J00000000000000000000001", projectId: "prj_01J00000000000000000000001", goal: "cleanup coverage" }); const finished = finalizeAgentRun(plan); assert.equal(finished.agentSession.status, "cleanup"); assert.equal(finished.workerSession.status, "cleanup"); assert.equal(finished.cleanup.removed, true); assert.ok(finished.evidenceRecord.sha256); }); test("skills manifest requires explicit commitId", () => { assert.throws(() => createSkillsManifest({}), /explicit commitId/); assert.throws(() => createSkillsManifest({ commitId: "6509a35" }), /explicit version/); const manifest = createSkillsManifest({ commitId: "6509a35", version: "v1" }); assert.equal(manifest.commitId, "6509a35"); assert.equal(manifest.version, "v1"); assert.equal(manifest.services[0].serviceId, "hwlab-agent-skills"); assert.equal(manifest.services[0].env.HWLAB_SKILLS_VERSION, "v1"); }); test("advance helpers preserve completedAt on non-finish states", () => { const session = { status: "start", completedAt: undefined, metadata: {} }; const agent = advanceAgentSession(session, "trace"); const worker = advanceWorkerSession(session, "trace"); assert.equal(agent.status, "trace"); assert.equal(worker.status, "trace"); assert.equal(agent.completedAt, undefined); assert.equal(worker.completedAt, undefined); }); test("local runtime degrades health and evidence when skills fields are missing", async () => { const stateDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-runtime-missing-")); try { const created = await createLocalAgentSession({ stateDir, agentSessionId: "agt_runtime_missing_skills", projectId: "prj_runtime_missing_skills", goal: "verify degraded skills evidence", prompt: "missing skills smoke" }); assert.equal(created.health.status, "degraded"); assert.deepEqual(created.health.skills.manager.missing, ["skillCommitId", "skillVersion"]); const worker = await runLocalWorkerDryRun({ stateDir, agentSessionId: "agt_runtime_missing_skills" }); assert.equal(worker.health.status, "degraded"); assert.equal(worker.evidenceRecord.metadata.health.status, "degraded"); assert.deepEqual(worker.evidenceRecord.metadata.health.skills.worker.missing, [ "skillCommitId", "skillVersion" ]); assert.equal(worker.cleanup.removed, true); assert.equal(worker.workspace.existsAfterCleanup, false); const evidence = await getLocalAgentEvidence({ stateDir, agentSessionId: "agt_runtime_missing_skills" }); assert.equal(evidence.status, "degraded"); assert.equal(evidence.evidenceRecords.length, 1); } finally { await rm(stateDir, { recursive: true, force: true }); } }); test("local runtime closes create/status/trace/evidence/cleanup loop", async () => { const stateDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-runtime-loop-")); try { const created = await createLocalAgentSession({ stateDir, agentSessionId: "agt_runtime_loop", projectId: "prj_runtime_loop", goal: "verify local worker bootstrap", prompt: "closed loop smoke", skillCommitId: "abc1234", skillVersion: "v1" }); assert.equal(created.health.status, "ok"); assert.equal(created.task.status, "queued"); assert.equal(created.traceCount, 1); const worker = await runLocalWorkerDryRun({ stateDir, agentSessionId: "agt_runtime_loop", skillCommitId: "abc1234", skillVersion: "v1" }); assert.equal(worker.health.status, "ok"); assert.equal(worker.agentSession.status, "cleanup"); assert.equal(worker.workerSession.status, "cleanup"); assert.equal(worker.task.status, "completed"); assert.equal(worker.evidenceRecord.metadata.skills.worker.commitId, "abc1234"); assert.equal(worker.evidenceRecord.metadata.skills.worker.version, "v1"); assert.equal(worker.workspace.cleaned, true); assert.equal(worker.workspace.existsAfterCleanup, false); const status = await getLocalAgentStatus({ stateDir, agentSessionId: "agt_runtime_loop" }); assert.equal(status.health.status, "ok"); assert.equal(status.evidenceCount, 1); assert.equal(status.cleanup.removed, true); const trace = await getLocalAgentTrace({ stateDir, agentSessionId: "agt_runtime_loop" }); assert.deepEqual( trace.traceEvents.map((event) => event.metadata.state), ["create", "start", "trace", "finish", "cleanup"] ); const evidence = await getLocalAgentEvidence({ stateDir, agentSessionId: "agt_runtime_loop" }); assert.equal(evidence.status, "ok"); assert.equal(evidence.evidenceRecords[0].kind, "report"); assert.equal(evidence.evidenceRecords[0].sha256.length, 64); } finally { await rm(stateDir, { recursive: true, force: true }); } });