362 lines
8.4 KiB
JavaScript
362 lines
8.4 KiB
JavaScript
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,
|
|
skillVersion = 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,
|
|
skillVersion,
|
|
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,
|
|
skillVersion
|
|
};
|
|
}
|
|
|
|
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,
|
|
version,
|
|
skillName = "hwlab-agent-runtime",
|
|
serviceId = AGENT_SKILLS_SERVICE_ID,
|
|
environment = ENVIRONMENT_DEV,
|
|
skills = []
|
|
}) {
|
|
if (!commitId) {
|
|
throw new Error("skills manifest requires explicit commitId");
|
|
}
|
|
if (!version) {
|
|
throw new Error("skills manifest requires explicit version");
|
|
}
|
|
return {
|
|
manifestVersion: "v1",
|
|
environment,
|
|
commitId,
|
|
version,
|
|
namespace: "hwlab",
|
|
endpoint: "http://74.48.78.17:16667",
|
|
profiles: {
|
|
dev: {
|
|
name: "dev",
|
|
enabled: true,
|
|
namespace: "hwlab-dev",
|
|
endpoint: "http://74.48.78.17:16667",
|
|
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,
|
|
HWLAB_SKILLS_VERSION: version
|
|
}
|
|
}
|
|
],
|
|
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");
|
|
}
|