feat: add local agent runtime loop

This commit is contained in:
HWLAB Code Queue
2026-05-21 18:14:57 +00:00
parent 8807296d7d
commit f1fb088db6
12 changed files with 1180 additions and 96 deletions
+82 -2
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
@@ -15,6 +16,13 @@ import {
createSkillsManifest,
finalizeAgentRun
} from "../internal/agent/index.mjs";
import {
createLocalAgentSession,
getLocalAgentEvidence,
getLocalAgentStatus,
getLocalAgentTrace,
runLocalWorkerDryRun
} from "../internal/agent/runtime.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const fixedNow = () => "2026-05-21T00:00:00.000Z";
@@ -47,6 +55,7 @@ function expectRunPlan(plan, fixture) {
const evidenceUri = `file://${path.posix.join(mountPath, "evidence.txt")}`;
assert.equal(plan.skillCommitId, fixture.skillCommitId);
assert.equal(plan.skillVersion, fixture.skillVersion);
assert.equal(plan.agentSession.serviceId, AGENT_MGR_SERVICE_ID);
assert.equal(plan.workerSession.serviceId, AGENT_WORKER_SERVICE_ID);
assert.equal(plan.agentSession.agentSessionId, fixture.agentSessionId);
@@ -114,6 +123,7 @@ function expectRunPlan(plan, fixture) {
assert.equal(plan.evidenceRecord.environment, fixture.environment);
assert.equal(plan.evidenceRecord.createdAt, fixedNow());
assert.equal(plan.evidenceRecord.metadata.skillCommitId, fixture.skillCommitId);
assert.equal(plan.evidenceRecord.metadata.skillVersion, fixture.skillVersion);
assert.equal(plan.evidenceRecord.metadata.prompt, fixture.prompt);
assert.equal(plan.evidenceRecord.metadata.goal, fixture.goal);
assert.equal(plan.evidenceRecord.metadata.workspaceVolumeId, workspaceVolumeId);
@@ -137,6 +147,7 @@ function expectSkillsManifest(manifest, fixture) {
assert.equal(manifest.manifestVersion, "v1");
assert.equal(manifest.environment, fixture.environment);
assert.equal(manifest.commitId, fixture.skillCommitId);
assert.equal(manifest.version, fixture.skillVersion);
assert.equal(manifest.namespace, "hwlab");
assert.equal(manifest.endpoint, "http://74.48.78.17:6667");
assert.equal(manifest.profiles.dev.enabled, true);
@@ -144,6 +155,7 @@ function expectSkillsManifest(manifest, fixture) {
assert.equal(manifest.services[0].serviceId, AGENT_SKILLS_SERVICE_ID);
assert.equal(manifest.services[0].image, `hwlab-agent-runtime:${fixture.skillCommitId.slice(0, 7)}`);
assert.equal(manifest.services[0].env.HWLAB_SKILLS_COMMIT_ID, fixture.skillCommitId);
assert.equal(manifest.services[0].env.HWLAB_SKILLS_VERSION, fixture.skillVersion);
assert.deepEqual(manifest.skills, []);
}
@@ -167,10 +179,73 @@ function expectEvidenceFixture(text, fixture) {
assert.ok(text.includes(`- Environment: ${fixture.environment}`));
assert.ok(text.includes(`- Project: ${fixture.projectId}`));
assert.ok(text.includes(`- Skill commitId: ${fixture.skillCommitId}`));
assert.ok(text.includes(`- Skill version: ${fixture.skillVersion}`));
assert.ok(text.includes("no secrets"));
assert.ok(text.includes("no live DEV agent run"));
}
async function expectLocalRuntimeClosedLoop(fixture) {
const stateDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-m4-agent-loop-"));
try {
const created = await createLocalAgentSession({
stateDir,
agentSessionId: fixture.agentSessionId,
projectId: fixture.projectId,
goal: fixture.goal,
prompt: fixture.prompt,
skillCommitId: fixture.skillCommitId,
skillVersion: fixture.skillVersion,
now: fixedNow
});
assert.equal(created.health.status, "ok");
assert.equal(created.task.status, "queued");
assert.equal(created.traceCount, 1);
const worker = await runLocalWorkerDryRun({
stateDir,
agentSessionId: fixture.agentSessionId,
skillCommitId: fixture.skillCommitId,
skillVersion: fixture.skillVersion,
now: fixedNow
});
assert.equal(worker.health.status, "ok");
assert.equal(worker.task.status, "completed");
assert.equal(worker.agentSession.status, "cleanup");
assert.equal(worker.workerSession.status, "cleanup");
assert.equal(worker.cleanup.removed, true);
assert.equal(worker.workspace.existsAfterCleanup, false);
assert.equal(worker.evidenceRecord.metadata.health.status, "ok");
assert.equal(worker.evidenceRecord.metadata.skills.manager.commitId, fixture.skillCommitId);
assert.equal(worker.evidenceRecord.metadata.skills.worker.version, fixture.skillVersion);
const status = await getLocalAgentStatus({
stateDir,
agentSessionId: fixture.agentSessionId
});
assert.equal(status.evidenceCount, 1);
assert.equal(status.cleanup.removed, true);
const trace = await getLocalAgentTrace({
stateDir,
agentSessionId: fixture.agentSessionId
});
assert.deepEqual(
trace.traceEvents.map((event) => event.metadata.state),
["create", "start", "trace", "finish", "cleanup"]
);
const evidence = await getLocalAgentEvidence({
stateDir,
agentSessionId: fixture.agentSessionId
});
assert.equal(evidence.status, "ok");
assert.equal(evidence.evidenceRecords.length, 1);
assert.equal(evidence.evidenceRecords[0].metadata.traceId, `trc_${fixture.agentSessionId}`);
} finally {
await rm(stateDir, { recursive: true, force: true });
}
}
const fixture = await readJSON("fixtures/mvp/m4-agent-loop/runtime.json");
const evidenceFixture = await readText(fixture.evidencePath);
@@ -178,6 +253,7 @@ assert.equal(fixture.environment, "dev");
assert.equal(fixture.projectId.startsWith("prj_"), true);
assert.equal(fixture.agentSessionId.startsWith("agt_"), true);
assert.equal(fixture.skillCommitId.length >= 7, true);
assert.equal(fixture.skillVersion.length > 0, true);
assert.equal(fixture.workspaceIsolation.projectId.startsWith("prj_"), true);
assert.equal(fixture.workspaceIsolation.agentSessionId.startsWith("agt_"), true);
@@ -190,6 +266,7 @@ const plan = createAgentRunPlan({
goal: fixture.goal,
prompt: fixture.prompt,
skillCommitId: fixture.skillCommitId,
skillVersion: fixture.skillVersion,
now: fixedNow
});
@@ -200,7 +277,8 @@ const finished = finalizeAgentRun(plan, { now: fixedNow });
expectFinishedRun(finished, plan);
const manifest = createSkillsManifest({
commitId: fixture.skillCommitId
commitId: fixture.skillCommitId,
version: fixture.skillVersion
});
expectSkillsManifest(manifest, fixture);
@@ -211,9 +289,11 @@ const isolationPlan = createAgentRunPlan({
goal: fixture.goal,
prompt: fixture.prompt,
skillCommitId: fixture.skillCommitId,
skillVersion: fixture.skillVersion,
now: fixedNow
});
expectWorkspaceIsolation(plan, isolationPlan, fixture);
await expectLocalRuntimeClosedLoop(fixture);
console.log(`[m4-smoke] ok ${fixture.scenario}`);