openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
314 lines (313 loc) • 13.2 kB
JavaScript
import { a as getNodeSqliteKysely, i as executeSqliteQueryTakeFirstSync, r as executeSqliteQuerySync } from "./kysely-sync-COmh4HWh.js";
import { S as unclaimedTurnForState, c as nextGeneration, f as normalizeTimestamp, i as assertRecordShape, l as normalizeCursor, m as nullableRequired, n as activeTurnClaimForState, p as normalizeWorkerPlacementExecutionMode, s as localTurnClaimForState, u as normalizeEpoch, v as required } from "./placement-record-BOfTzlFO.js";
//#region src/gateway/worker-environments/placement-state.ts
const WORKER_SESSION_PLACEMENT_STATES = [
"local",
"requested",
"provisioning",
"syncing",
"starting",
"active",
"draining",
"reconciling",
"reclaimed",
"failed"
];
const WORKER_SESSION_PLACEMENT_TRANSITIONS = {
local: ["requested", "failed"],
requested: ["provisioning", "failed"],
provisioning: ["syncing", "failed"],
syncing: ["starting", "failed"],
starting: ["active", "failed"],
active: ["draining"],
draining: ["reconciling"],
reconciling: [
"local",
"reclaimed",
"failed"
],
reclaimed: ["requested"],
failed: ["local", "requested"]
};
function parseWorkerSessionPlacementState(value) {
if (WORKER_SESSION_PLACEMENT_STATES.includes(value)) return value;
throw new Error(`Invalid worker session placement state: ${value}`);
}
function canTransitionWorkerSessionPlacement(from, to) {
return WORKER_SESSION_PLACEMENT_TRANSITIONS[from].includes(to);
}
//#endregion
//#region src/gateway/worker-environments/placement-row-codec.ts
const query = (db) => getNodeSqliteKysely(db);
const EMPTY_WORKER_METADATA = {
environmentId: null,
activeOwnerEpoch: null,
workspaceBaseManifestRef: null,
remoteWorkspaceDir: null,
workerBundleHash: null,
lastTranscriptAckCursor: null,
lastLiveEventAckCursor: null,
recoveryError: null,
terminalReason: null,
terminalAtMs: null
};
function parseTurnClaim(row) {
if (row.turn_claim_owner === null) return null;
const claimId = required(row.turn_claim_id ?? "", "turn claim id");
const runId = required(row.turn_claim_run_id ?? "", "turn claim run id");
const generation = row.turn_claim_generation;
if (generation === null || !Number.isSafeInteger(generation) || generation < 0) throw new Error("Worker session placement turn claim generation is invalid");
if (row.turn_claim_owner === "local") {
if (row.turn_claim_owner_epoch !== null) throw new Error("Local turn claim cannot retain a worker owner epoch");
return {
owner: "local",
claimId,
runId,
generation,
ownerEpoch: null
};
}
if (row.turn_claim_owner === "worker") return {
owner: "worker",
claimId,
runId,
generation,
ownerEpoch: normalizeEpoch(row.turn_claim_owner_epoch ?? 0, "turn claim owner epoch")
};
throw new Error(`Invalid worker session turn claim owner: ${row.turn_claim_owner}`);
}
function ownedWorkerMetadata(parsed, state) {
if (parsed.environmentId === null || parsed.activeOwnerEpoch === null || parsed.workspaceBaseManifestRef === null || parsed.remoteWorkspaceDir === null || parsed.workerBundleHash === null) throw new Error(`Worker session placement ${state} requires complete worker ownership`);
return {
environmentId: parsed.environmentId,
activeOwnerEpoch: parsed.activeOwnerEpoch,
workspaceBaseManifestRef: parsed.workspaceBaseManifestRef,
remoteWorkspaceDir: parsed.remoteWorkspaceDir,
workerBundleHash: parsed.workerBundleHash,
lastTranscriptAckCursor: parsed.lastTranscriptAckCursor,
lastLiveEventAckCursor: parsed.lastLiveEventAckCursor,
recoveryError: null,
terminalReason: null,
terminalAtMs: null
};
}
function fromRow(row) {
const state = parseWorkerSessionPlacementState(row.state);
const executionMode = normalizeWorkerPlacementExecutionMode(row.execution_mode);
const parsed = {
environmentId: row.environment_id === null ? null : required(row.environment_id, "environment id"),
activeOwnerEpoch: row.active_owner_epoch === null ? null : normalizeEpoch(row.active_owner_epoch, "active owner epoch"),
workspaceBaseManifestRef: nullableRequired(row.workspace_base_manifest_ref, "workspace base manifest ref"),
remoteWorkspaceDir: nullableRequired(row.remote_workspace_dir, "remote workspace directory"),
workerBundleHash: nullableRequired(row.worker_bundle_hash, "worker bundle hash"),
lastTranscriptAckCursor: normalizeCursor(row.last_transcript_ack_cursor, "transcript ACK cursor"),
lastLiveEventAckCursor: normalizeCursor(row.last_live_event_ack_cursor, "live ACK cursor"),
terminalReason: nullableRequired(row.terminal_reason, "terminal reason"),
terminalAtMs: normalizeTimestamp(row.terminal_at_ms, "terminal timestamp")
};
const recoveryError = nullableRequired(row.recovery_error, "recovery error");
const turnClaim = parseTurnClaim(row);
const base = {
sessionId: row.session_id,
agentId: row.agent_id,
sessionKey: row.session_key,
executionMode,
generation: row.transition_generation,
createdAtMs: row.created_at_ms,
updatedAtMs: row.updated_at_ms,
stateChangedAtMs: row.state_changed_at_ms
};
assertRecordShape({
state,
executionMode,
...parsed,
recoveryError,
turnClaim
});
switch (state) {
case "local": return {
...base,
state,
turnClaim: localTurnClaimForState(turnClaim, state),
...EMPTY_WORKER_METADATA
};
case "requested": return {
...base,
state,
turnClaim: localTurnClaimForState(turnClaim, state),
...EMPTY_WORKER_METADATA
};
case "provisioning": return {
...base,
state,
turnClaim: unclaimedTurnForState(turnClaim, state),
...EMPTY_WORKER_METADATA,
environmentId: parsed.environmentId
};
case "syncing":
if (parsed.environmentId === null || parsed.workerBundleHash === null) throw new Error("Syncing worker session placement requires an environment and bundle");
return {
...base,
state,
turnClaim: unclaimedTurnForState(turnClaim, state),
...EMPTY_WORKER_METADATA,
environmentId: parsed.environmentId,
workerBundleHash: parsed.workerBundleHash
};
case "starting":
if (parsed.environmentId === null || parsed.workspaceBaseManifestRef === null || parsed.remoteWorkspaceDir === null || parsed.workerBundleHash === null) throw new Error("Starting worker session placement requires complete workspace metadata");
return {
...base,
state,
turnClaim: unclaimedTurnForState(turnClaim, state),
...EMPTY_WORKER_METADATA,
environmentId: parsed.environmentId,
workspaceBaseManifestRef: parsed.workspaceBaseManifestRef,
remoteWorkspaceDir: parsed.remoteWorkspaceDir,
workerBundleHash: parsed.workerBundleHash
};
case "active": return {
...base,
state,
turnClaim: activeTurnClaimForState(turnClaim, state, executionMode),
...ownedWorkerMetadata(parsed, state)
};
case "draining": return {
...base,
state,
turnClaim: activeTurnClaimForState(turnClaim, state, executionMode),
...ownedWorkerMetadata(parsed, state)
};
case "reconciling": return {
...base,
state,
turnClaim: unclaimedTurnForState(turnClaim, state),
...ownedWorkerMetadata(parsed, state)
};
case "reclaimed": return {
...base,
state,
turnClaim: unclaimedTurnForState(turnClaim, state),
...ownedWorkerMetadata(parsed, state),
terminalReason: parsed.terminalReason,
terminalAtMs: parsed.terminalAtMs
};
case "failed":
if (recoveryError === null) throw new Error("Failed worker session placement requires a recovery error");
return {
...base,
state,
turnClaim: localTurnClaimForState(turnClaim, state),
environmentId: parsed.environmentId,
activeOwnerEpoch: parsed.activeOwnerEpoch,
workspaceBaseManifestRef: parsed.workspaceBaseManifestRef,
remoteWorkspaceDir: parsed.remoteWorkspaceDir,
workerBundleHash: parsed.workerBundleHash,
lastTranscriptAckCursor: parsed.lastTranscriptAckCursor,
lastLiveEventAckCursor: parsed.lastLiveEventAckCursor,
recoveryError,
terminalReason: parsed.terminalReason,
terminalAtMs: parsed.terminalAtMs
};
}
return state;
}
function find(db, sessionId) {
const row = executeSqliteQueryTakeFirstSync(db, query(db).selectFrom("worker_session_placements").selectAll().where("session_id", "=", sessionId));
return row ? fromRow(row) : void 0;
}
function getRequired(db, sessionId) {
const record = find(db, sessionId);
if (!record) throw new Error(`Unknown worker session placement: ${sessionId}`);
return record;
}
function assertIdentity(record, identity) {
if (record.agentId !== identity.agentId || record.sessionKey !== identity.sessionKey) throw new Error(`Worker session placement identity changed for ${identity.sessionId}`);
}
function insertLocal(db, identity, nowMs) {
executeSqliteQuerySync(db, query(db).insertInto("worker_session_placements").values({
session_id: identity.sessionId,
agent_id: identity.agentId,
session_key: identity.sessionKey,
execution_mode: null,
state: "local",
environment_id: null,
transition_generation: 0,
active_owner_epoch: null,
workspace_base_manifest_ref: null,
remote_workspace_dir: null,
worker_bundle_hash: null,
last_transcript_ack_cursor: null,
last_live_event_ack_cursor: null,
recovery_error: null,
terminal_reason: null,
terminal_at_ms: null,
turn_claim_owner: null,
turn_claim_id: null,
turn_claim_run_id: null,
turn_claim_generation: null,
turn_claim_owner_epoch: null,
created_at_ms: nowMs,
updated_at_ms: nowMs,
state_changed_at_ms: nowMs
}));
return getRequired(db, identity.sessionId);
}
function ensureLocal(db, identity, nowMs) {
const current = find(db, identity.sessionId);
if (current) {
assertIdentity(current, identity);
return current;
}
return insertLocal(db, identity, nowMs);
}
function transitionValues(current, to, patch, nowMs) {
const environmentId = to === "local" || to === "requested" ? null : patch.environmentId === void 0 ? current.environmentId : patch.environmentId === null ? null : required(patch.environmentId, "environment id");
const activeOwnerEpoch = to === "local" || to === "requested" || to === "provisioning" || to === "syncing" || to === "starting" ? null : patch.activeOwnerEpoch === void 0 ? current.activeOwnerEpoch : patch.activeOwnerEpoch === null ? null : normalizeEpoch(patch.activeOwnerEpoch, "active owner epoch");
const generation = nextGeneration(current.generation);
const clearsWorkerMetadata = to === "local" || to === "requested";
const values = {
session_id: current.sessionId,
agent_id: current.agentId,
session_key: current.sessionKey,
execution_mode: current.executionMode,
state: to,
environment_id: environmentId,
transition_generation: generation,
active_owner_epoch: activeOwnerEpoch,
workspace_base_manifest_ref: clearsWorkerMetadata ? null : patch.workspaceBaseManifestRef === void 0 ? current.workspaceBaseManifestRef : patch.workspaceBaseManifestRef === null ? null : required(patch.workspaceBaseManifestRef, "workspace base manifest ref"),
remote_workspace_dir: clearsWorkerMetadata ? null : patch.remoteWorkspaceDir === void 0 ? current.remoteWorkspaceDir : patch.remoteWorkspaceDir === null ? null : required(patch.remoteWorkspaceDir, "remote workspace directory"),
worker_bundle_hash: clearsWorkerMetadata ? null : patch.workerBundleHash === void 0 ? current.workerBundleHash : patch.workerBundleHash === null ? null : required(patch.workerBundleHash, "worker bundle hash"),
last_transcript_ack_cursor: clearsWorkerMetadata ? null : patch.lastTranscriptAckCursor === void 0 ? current.lastTranscriptAckCursor : normalizeCursor(patch.lastTranscriptAckCursor, "transcript ACK cursor"),
last_live_event_ack_cursor: clearsWorkerMetadata ? null : patch.lastLiveEventAckCursor === void 0 ? current.lastLiveEventAckCursor : normalizeCursor(patch.lastLiveEventAckCursor, "live ACK cursor"),
recovery_error: clearsWorkerMetadata ? null : patch.recoveryError === void 0 ? current.recoveryError : patch.recoveryError === null ? null : required(patch.recoveryError, "recovery error"),
terminal_reason: to === "failed" ? patch.terminalReason === void 0 ? current.terminalReason : patch.terminalReason === null ? null : required(patch.terminalReason, "terminal reason") : null,
terminal_at_ms: to === "reclaimed" || to === "failed" ? current.terminalAtMs ?? nowMs : null,
turn_claim_owner: null,
turn_claim_id: null,
turn_claim_run_id: null,
turn_claim_generation: null,
turn_claim_owner_epoch: null,
created_at_ms: current.createdAtMs,
updated_at_ms: nowMs,
state_changed_at_ms: nowMs
};
assertRecordShape({
state: to,
executionMode: current.executionMode,
environmentId,
activeOwnerEpoch,
workspaceBaseManifestRef: values.workspace_base_manifest_ref,
remoteWorkspaceDir: values.remote_workspace_dir,
workerBundleHash: values.worker_bundle_hash,
lastTranscriptAckCursor: values.last_transcript_ack_cursor,
lastLiveEventAckCursor: values.last_live_event_ack_cursor,
recoveryError: values.recovery_error,
terminalReason: values.terminal_reason,
terminalAtMs: values.terminal_at_ms,
turnClaim: null
});
return values;
}
//#endregion
export { query as a, getRequired as i, find as n, transitionValues as o, fromRow as r, canTransitionWorkerSessionPlacement as s, ensureLocal as t };