122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import { createServer } from "node:http";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { promisify } from "node:util";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const bunCommand = process.env.HWLAB_TEST_BUN_COMMAND || process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun");
|
|
|
|
test("agent manager CLI reports degraded skills and manages local session lifecycle", async () => {
|
|
const stateDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-mgr-"));
|
|
try {
|
|
const health = await runManagerJson(["health", "--state-dir", stateDir]);
|
|
assert.equal(health.serviceId, "hwlab-agent-mgr");
|
|
assert.equal(health.ok, true);
|
|
assert.equal(health.status, "degraded");
|
|
assert.deepEqual(health.health.skills.manager.missing, ["skillCommitId", "skillVersion"]);
|
|
|
|
const created = await runManagerJson([
|
|
"create",
|
|
"--agent-session-id", "ags_test",
|
|
"--project-id", "prj_test",
|
|
"--goal", "keep behavior",
|
|
"--prompt", "run dry",
|
|
"--skill-commit-id", "commit-a",
|
|
"--skill-version", "1.0.0",
|
|
"--state-dir", stateDir
|
|
]);
|
|
assert.equal(created.agentSessionId, "ags_test");
|
|
assert.equal(created.status, "ok");
|
|
assert.equal(created.task.status, "queued");
|
|
assert.equal(created.traceCount, 1);
|
|
|
|
const status = await runManagerJson(["status", "--agent-session-id", "ags_test", "--state-dir", stateDir]);
|
|
assert.equal(status.agentSession.metadata.skills.commitId, "commit-a");
|
|
assert.equal(status.workerSession.metadata.expectedSkills.version, "1.0.0");
|
|
|
|
const trace = await runManagerJson(["trace", "--agent-session-id", "ags_test", "--state-dir", stateDir]);
|
|
assert.equal(trace.traceEvents.length, 1);
|
|
assert.equal(trace.traceEvents[0].metadata.state, "create");
|
|
|
|
const evidence = await runManagerJson(["evidence", "--agent-session-id", "ags_test", "--state-dir", stateDir]);
|
|
assert.equal(evidence.evidenceRecords.length, 0);
|
|
|
|
const cleanup = await runManagerJson(["cleanup", "--agent-session-id", "ags_test", "--state-dir", stateDir]);
|
|
assert.equal(cleanup.serviceId, "hwlab-agent-mgr");
|
|
assert.equal(cleanup.cleanup.completed, true);
|
|
assert.equal(cleanup.workspace.cleaned, true);
|
|
} finally {
|
|
await rm(stateDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("agent manager server returns health JSON", async () => {
|
|
const port = await freePort();
|
|
const server = await startManagerServer(port);
|
|
try {
|
|
const response = await fetch(`http://127.0.0.1:${port}/health/live`);
|
|
assert.equal(response.status, 200);
|
|
const health = await response.json();
|
|
assert.equal(health.serviceId, "hwlab-agent-mgr");
|
|
assert.equal(health.ok, true);
|
|
assert.equal(health.status, "degraded");
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
|
|
async function runManagerJson(args) {
|
|
const { stdout } = await execFileAsync(bunCommand, ["run", "cmd/hwlab-agent-mgr/main.ts", ...args], {
|
|
cwd: process.cwd(),
|
|
maxBuffer: 1024 * 1024
|
|
});
|
|
return JSON.parse(stdout);
|
|
}
|
|
|
|
async function startManagerServer(port) {
|
|
const child = execFile(bunCommand, ["run", "cmd/hwlab-agent-mgr/main.ts", "server"], {
|
|
cwd: process.cwd(),
|
|
env: {
|
|
...process.env,
|
|
PORT: String(port)
|
|
},
|
|
maxBuffer: 1024 * 1024
|
|
});
|
|
await new Promise((resolve, reject) => {
|
|
let stderr = "";
|
|
const timer = setTimeout(() => reject(new Error(`manager server did not start: ${stderr}`)), 5000);
|
|
child.stderr?.on("data", (chunk) => {
|
|
stderr += chunk;
|
|
});
|
|
child.stdout?.on("data", (chunk) => {
|
|
if (chunk.toString().includes("listening")) {
|
|
clearTimeout(timer);
|
|
resolve();
|
|
}
|
|
});
|
|
child.on("exit", (code) => {
|
|
clearTimeout(timer);
|
|
reject(new Error(`manager server exited early code=${code}: ${stderr}`));
|
|
});
|
|
});
|
|
return {
|
|
stop: async () => {
|
|
if (child.exitCode !== null) return;
|
|
child.kill("SIGTERM");
|
|
await new Promise((resolve) => child.once("exit", resolve));
|
|
}
|
|
};
|
|
}
|
|
|
|
async function freePort() {
|
|
const server = createServer();
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
const port = server.address().port;
|
|
await new Promise((resolve) => server.close(resolve));
|
|
return port;
|
|
}
|