import crypto from "node:crypto"; import path from "node:path"; import { ENVIRONMENT_DEV } from "../protocol/index.mjs"; export const AGENT_MGR_SERVICE_ID = "hwlab-agent-mgr"; export const AGENT_WORKER_SERVICE_ID = "hwlab-agent-worker"; export const AGENT_SKILLS_SERVICE_ID = "hwlab-agent-skills"; export const AGENT_SESSION_STATUSES = Object.freeze([ "create", "start", "trace", "finish", "cleanup" ]); export const WORKER_SESSION_STATUSES = Object.freeze([ "create", "start", "trace", "finish", "cleanup" ]); export function createAgentSession({ agentSessionId, projectId, requestedBy = "usr_local", goal = "", environment = ENVIRONMENT_DEV, now = () => new Date().toISOString(), metadata = {} }) { const timestamp = now(); return { agentSessionId, projectId, serviceId: AGENT_MGR_SERVICE_ID, status: "create", environment, requestedBy, goal, startedAt: timestamp, updatedAt: timestamp, metadata: { ...metadata, lifecycle: AGENT_SESSION_STATUSES.slice(), currentState: "create" } }; } export function advanceAgentSession(session, nextStatus, { now = () => new Date().toISOString(), extra = {} } = {}) { assertAgentStatus(nextStatus); const timestamp = now(); const completedAt = nextStatus === "finish" ? timestamp : session.completedAt; return { ...session, status: nextStatus, completedAt, updatedAt: timestamp, metadata: { ...session.metadata, ...extra, currentState: nextStatus } }; } export function createWorkspaceVolume({ agentSessionId, projectId, now = () => new Date().toISOString() }) { return { workspaceVolumeId: `vol_${agentSessionId}`, agentSessionId, projectId, serviceId: AGENT_WORKER_SERVICE_ID, mountPath: path.posix.join("/workspace", projectId, agentSessionId), lifecycle: "create", createdAt: now(), updatedAt: now() }; } export function createWorkerSession({ workerSessionId, agentSessionId, projectId, gatewaySessionId = null, now = () => new Date().toISOString(), metadata = {} }) { const timestamp = now(); return { workerSessionId, agentSessionId, projectId, serviceId: AGENT_WORKER_SERVICE_ID, gatewaySessionId, status: "create", environment: ENVIRONMENT_DEV, startedAt: timestamp, updatedAt: timestamp, metadata: { ...metadata, lifecycle: WORKER_SESSION_STATUSES.slice(), workspaceVolumeId: `vol_${agentSessionId}`, currentState: "create" } }; } export function advanceWorkerSession(session, nextStatus, { now = () => new Date().toISOString(), extra = {} } = {}) { assertWorkerStatus(nextStatus); const timestamp = now(); const completedAt = nextStatus === "finish" ? timestamp : session.completedAt; return { ...session, status: nextStatus, completedAt, updatedAt: timestamp, metadata: { ...session.metadata, ...extra, currentState: nextStatus } }; } export function createTraceEvent({ traceEventId, traceId, projectId, serviceId = AGENT_MGR_SERVICE_ID, level = "info", message, environment = ENVIRONMENT_DEV, now = () => new Date().toISOString(), agentSessionId = null, workerSessionId = null, operationId = null, metadata = {} }) { return { traceEventId, traceId, projectId, serviceId, level, message, environment, occurredAt: now(), agentSessionId, workerSessionId, operationId, metadata: { ...metadata } }; } export function createEvidenceRecord({ evidenceId, projectId, operationId, uri, sha256 = digestHex(uri), serviceId = AGENT_WORKER_SERVICE_ID, environment = ENVIRONMENT_DEV, kind = "artifact", now = () => new Date().toISOString(), agentSessionId = null, workerSessionId = null, mimeType = "text/plain", sizeBytes = 0, metadata = {} }) { return { evidenceId, projectId, operationId, agentSessionId, workerSessionId, kind, uri, mimeType, sha256, sizeBytes, serviceId, environment, metadata: { ...metadata }, createdAt: now() }; } export function createAgentRunPlan({ agentSessionId, projectId, goal = "", prompt = "", skillCommitId = null, now = () => new Date().toISOString() }) { const traceId = `trc_${agentSessionId}`; const workerSessionId = `wkr_${agentSessionId}`; const operationId = `op_${agentSessionId}`; const evidenceId = `evi_${agentSessionId}`; const traceEventId = `tev_${agentSessionId}`; const workspaceVolume = createWorkspaceVolume({ agentSessionId, projectId, now }); const agentSession = createAgentSession({ agentSessionId, projectId, goal, now }); const workerSession = createWorkerSession({ workerSessionId, agentSessionId, projectId, now }); const traceEvent = createTraceEvent({ traceEventId, traceId, projectId, message: prompt || goal || "agent runtime dry run", agentSessionId, workerSessionId, operationId, now }); const evidenceRecord = createEvidenceRecord({ evidenceId, projectId, operationId, uri: `file://${path.posix.join(workspaceVolume.mountPath, "evidence.txt")}`, agentSessionId, workerSessionId, metadata: { skillCommitId, prompt, goal, workspaceVolumeId: workspaceVolume.workspaceVolumeId }, now }); return { agentSession: advanceAgentSession(agentSession, "start", { now, extra: { workerSessionId, workspaceVolumeId: workspaceVolume.workspaceVolumeId } }), workerSession: advanceWorkerSession(workerSession, "start", { now, extra: { workspaceVolumeId: workspaceVolume.workspaceVolumeId } }), workspaceVolume, events: [ { state: "create", actor: AGENT_MGR_SERVICE_ID, agentSessionId }, { state: "start", actor: AGENT_WORKER_SERVICE_ID, workerSessionId }, { state: "trace", actor: AGENT_MGR_SERVICE_ID, traceEvent }, { state: "finish", actor: AGENT_WORKER_SERVICE_ID, evidenceRecord }, { state: "cleanup", actor: AGENT_WORKER_SERVICE_ID, workspaceVolumeId: workspaceVolume.workspaceVolumeId } ], traceEvent, evidenceRecord, skillCommitId }; } export function finalizeAgentRun(plan, { now = () => new Date().toISOString() } = {}) { const finishedAgentSession = advanceAgentSession(plan.agentSession, "finish", { now }); const finishedWorkerSession = advanceWorkerSession(plan.workerSession, "finish", { now }); return { ...plan, agentSession: advanceAgentSession(finishedAgentSession, "cleanup", { now, extra: { cleanedUp: true } }), workerSession: advanceWorkerSession(finishedWorkerSession, "cleanup", { now, extra: { cleanedUp: true } }), cleanup: { workspaceVolumeId: plan.workspaceVolume.workspaceVolumeId, removed: true } }; } export function createSkillsManifest({ commitId, skillName = "hwlab-agent-runtime", serviceId = AGENT_SKILLS_SERVICE_ID, environment = ENVIRONMENT_DEV, skills = [] }) { if (!commitId) { throw new Error("skills manifest requires explicit commitId"); } return { manifestVersion: "v1", environment, commitId, namespace: "hwlab", endpoint: "http://74.48.78.17:6667", profiles: { dev: { name: "dev", enabled: true, namespace: "hwlab-dev", endpoint: "http://74.48.78.17:6667", notes: "explicit repo-local skills artifact" } }, services: [ { serviceId, image: `${skillName}:${commitId.slice(0, 7)}`, namespace: "hwlab-dev", healthPath: "/health", profile: "dev", env: { HWLAB_SKILLS_COMMIT_ID: commitId } } ], skills }; } function assertAgentStatus(status) { if (!AGENT_SESSION_STATUSES.includes(status)) { throw new Error(`unknown agent session status ${JSON.stringify(status)}`); } } function assertWorkerStatus(status) { if (!WORKER_SESSION_STATUSES.includes(status)) { throw new Error(`unknown worker session status ${JSON.stringify(status)}`); } } function digestHex(value) { return crypto.createHash("sha256").update(String(value)).digest("hex"); }