235 lines
7.0 KiB
JavaScript
235 lines
7.0 KiB
JavaScript
#!/usr/bin/env node
|
|
import { createServer } from "node:http";
|
|
|
|
import { buildMetadataFromEnv } from "../../internal/build-metadata.mjs";
|
|
import {
|
|
AGENT_MGR_SERVICE_ID,
|
|
createSkillsManifest
|
|
} from "../../internal/agent/index.mjs";
|
|
import {
|
|
assessSkillsInjection,
|
|
cleanupLocalAgentSession,
|
|
createLocalAgentSession,
|
|
createRuntimeHealth,
|
|
getLocalAgentEvidence,
|
|
getLocalAgentStatus,
|
|
getLocalAgentTrace,
|
|
resolveAgentRuntimeStateDir
|
|
} from "../../internal/agent/runtime.mjs";
|
|
|
|
const command = process.argv[2] || process.env.HWLAB_AGENT_MGR_DEFAULT_COMMAND || "server";
|
|
const options = parseOptions(process.argv.slice(3));
|
|
|
|
try {
|
|
const result = await dispatch(command, options);
|
|
printJSON(result);
|
|
} catch (error) {
|
|
printJSON(
|
|
{
|
|
serviceId: AGENT_MGR_SERVICE_ID,
|
|
ok: false,
|
|
error: {
|
|
message: error.message
|
|
}
|
|
},
|
|
{ stderr: true }
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
async function dispatch(name, opts) {
|
|
if (name === "help" || opts.help) {
|
|
return help();
|
|
}
|
|
|
|
if (name === "health") {
|
|
return createHealthResponse(opts);
|
|
}
|
|
|
|
if (name === "server") {
|
|
return serveHealth(opts);
|
|
}
|
|
|
|
if (name === "create") {
|
|
return {
|
|
ok: true,
|
|
...(await createLocalAgentSession({
|
|
stateDir: opts.stateDir,
|
|
agentSessionId: requireOption(opts, "agentSessionId", "--agent-session-id"),
|
|
projectId: requireOption(opts, "projectId", "--project-id"),
|
|
goal: opts.goal || "",
|
|
prompt: opts.prompt || "",
|
|
requestedBy: opts.requestedBy || "usr_local",
|
|
skillCommitId: opts.skillCommitId || null,
|
|
skillVersion: opts.skillVersion || null
|
|
}))
|
|
};
|
|
}
|
|
|
|
if (name === "status") {
|
|
return {
|
|
ok: true,
|
|
...(await getLocalAgentStatus({
|
|
stateDir: opts.stateDir,
|
|
agentSessionId: requireOption(opts, "agentSessionId", "--agent-session-id")
|
|
}))
|
|
};
|
|
}
|
|
|
|
if (name === "trace") {
|
|
return {
|
|
ok: true,
|
|
...(await getLocalAgentTrace({
|
|
stateDir: opts.stateDir,
|
|
agentSessionId: requireOption(opts, "agentSessionId", "--agent-session-id")
|
|
}))
|
|
};
|
|
}
|
|
|
|
if (name === "evidence") {
|
|
return {
|
|
ok: true,
|
|
...(await getLocalAgentEvidence({
|
|
stateDir: opts.stateDir,
|
|
agentSessionId: requireOption(opts, "agentSessionId", "--agent-session-id")
|
|
}))
|
|
};
|
|
}
|
|
|
|
if (name === "cleanup") {
|
|
return {
|
|
ok: true,
|
|
...(await cleanupLocalAgentSession({
|
|
stateDir: opts.stateDir,
|
|
agentSessionId: requireOption(opts, "agentSessionId", "--agent-session-id"),
|
|
actor: AGENT_MGR_SERVICE_ID
|
|
}))
|
|
};
|
|
}
|
|
|
|
throw new Error(`unknown command ${JSON.stringify(name)}`);
|
|
}
|
|
|
|
function parseOptions(argv) {
|
|
const opts = {
|
|
stateDir: process.env.HWLAB_AGENT_RUNTIME_STATE_DIR || ""
|
|
};
|
|
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const arg = argv[i];
|
|
if (arg === "--agent-session-id") opts.agentSessionId = argv[++i] || "";
|
|
else if (arg === "--project-id") opts.projectId = argv[++i] || "";
|
|
else if (arg === "--goal") opts.goal = argv[++i] || "";
|
|
else if (arg === "--prompt") opts.prompt = argv[++i] || "";
|
|
else if (arg === "--requested-by") opts.requestedBy = argv[++i] || "";
|
|
else if (arg === "--skill-commit-id") opts.skillCommitId = argv[++i] || "";
|
|
else if (arg === "--skill-version") opts.skillVersion = argv[++i] || "";
|
|
else if (arg === "--state-dir") opts.stateDir = argv[++i] || "";
|
|
else if (arg === "--help" || arg === "-h") opts.help = true;
|
|
else throw new Error(`unknown option ${JSON.stringify(arg)}`);
|
|
}
|
|
|
|
if (opts.stateDir) {
|
|
opts.stateDir = resolveAgentRuntimeStateDir(opts.stateDir);
|
|
}
|
|
|
|
return opts;
|
|
}
|
|
|
|
function requireOption(opts, key, flag) {
|
|
if (!opts[key]) {
|
|
throw new Error(`${flag} is required`);
|
|
}
|
|
return opts[key];
|
|
}
|
|
|
|
function createHealthResponse(opts) {
|
|
const managerSkills = assessSkillsInjection({
|
|
skillCommitId: opts.skillCommitId,
|
|
skillVersion: opts.skillVersion,
|
|
role: "manager"
|
|
});
|
|
const health = createRuntimeHealth({
|
|
stateDir: opts.stateDir,
|
|
managerSkills
|
|
});
|
|
return {
|
|
serviceId: AGENT_MGR_SERVICE_ID,
|
|
ok: true,
|
|
status: health.status,
|
|
environment: process.env.HWLAB_ENVIRONMENT || "dev",
|
|
...buildMetadataFromEnv(process.env, { serviceId: AGENT_MGR_SERVICE_ID }),
|
|
health,
|
|
skillsManifest: managerSkills.status === "ready"
|
|
? createSkillsManifest({ commitId: managerSkills.commitId, version: managerSkills.version })
|
|
: null
|
|
};
|
|
}
|
|
|
|
async function serveHealth(opts) {
|
|
const port = Number.parseInt(process.env.PORT || process.env.HWLAB_PORT || "7410", 10);
|
|
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
throw new Error(`invalid PORT/HWLAB_PORT ${JSON.stringify(process.env.PORT || process.env.HWLAB_PORT)}`);
|
|
}
|
|
|
|
const server = createServer((request, response) => {
|
|
const url = new URL(request.url || "/", "http://hwlab-agent-mgr.local");
|
|
if (url.pathname === "/health" || url.pathname === "/health/live") {
|
|
sendJson(response, 200, createHealthResponse(opts));
|
|
return;
|
|
}
|
|
if (url.pathname === "/" || url.pathname === "/help") {
|
|
sendJson(response, 200, help());
|
|
return;
|
|
}
|
|
sendJson(response, 404, { serviceId: AGENT_MGR_SERVICE_ID, ok: false, error: "not_found", path: url.pathname });
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(port, "0.0.0.0", resolve);
|
|
});
|
|
printJSON({ serviceId: AGENT_MGR_SERVICE_ID, ok: true, status: "listening", port });
|
|
|
|
return new Promise((resolve, reject) => {
|
|
server.once("close", resolve);
|
|
server.once("error", reject);
|
|
});
|
|
}
|
|
|
|
function sendJson(response, statusCode, body) {
|
|
const payload = JSON.stringify(body, null, 2);
|
|
response.writeHead(statusCode, {
|
|
"content-type": "application/json; charset=utf-8",
|
|
"content-length": Buffer.byteLength(payload)
|
|
});
|
|
response.end(payload);
|
|
}
|
|
|
|
function help() {
|
|
return {
|
|
serviceId: AGENT_MGR_SERVICE_ID,
|
|
ok: true,
|
|
usage: [
|
|
"bun run cmd/hwlab-agent-mgr/main.ts server [--skill-commit-id COMMIT --skill-version VERSION] [--state-dir DIR]",
|
|
"bun run cmd/hwlab-agent-mgr/main.ts health [--skill-commit-id COMMIT --skill-version VERSION] [--state-dir DIR]",
|
|
"bun run cmd/hwlab-agent-mgr/main.ts create --agent-session-id ID --project-id ID [--goal TEXT --prompt TEXT] [--skill-commit-id COMMIT --skill-version VERSION] [--state-dir DIR]",
|
|
"bun run cmd/hwlab-agent-mgr/main.ts status --agent-session-id ID [--state-dir DIR]",
|
|
"bun run cmd/hwlab-agent-mgr/main.ts trace --agent-session-id ID [--state-dir DIR]",
|
|
"bun run cmd/hwlab-agent-mgr/main.ts evidence --agent-session-id ID [--state-dir DIR]",
|
|
"bun run cmd/hwlab-agent-mgr/main.ts cleanup --agent-session-id ID [--state-dir DIR]"
|
|
],
|
|
contract: {
|
|
stateDir: resolveAgentRuntimeStateDir(),
|
|
output: "json",
|
|
runtimeMode: "local-dry-run",
|
|
prodDisabled: true
|
|
}
|
|
};
|
|
}
|
|
|
|
function printJSON(value, { stderr = false } = {}) {
|
|
const target = stderr ? process.stderr : process.stdout;
|
|
target.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
}
|