UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

122 lines (121 loc) 6.82 kB
//#region src/gateway/worker-environments/session-placement-lifecycle.ts var SessionWorkerPlacementMutationError = class extends Error { constructor(state, action, key) { super(`Session ${key} cannot ${action} while cloud worker placement is ${state}.`); } }; function isFailedWorkerPlacementEnvironmentGone(params) { if (params.placement.environmentId === null) return true; if (!params.environmentService) return false; try { const environment = params.environmentService.get(params.placement.environmentId); return environment === void 0 || environment.state === "destroyed" || environment.state === "failed" && environment.leaseId === null; } catch { return false; } } function isWorkerPlacementSafeForMutation(context, placement) { if (placement.state === "failed") return isFailedWorkerPlacementEnvironmentGone({ environmentService: context.workerEnvironmentService, placement }); return placement.state === "local" || placement.state === "reclaimed"; } function resolveWorkerPlacementArchiveRestoreError(params) { if (!params.placement || isWorkerPlacementSafeForMutation(params.context, params.placement)) return; return `Session ${params.key} cannot change archive state while cloud worker placement is ${params.placement.state}.`; } function retirementGuard(placement) { return { status: "retirement-required", sessionId: placement.sessionId, expectedState: placement.state, expectedGeneration: placement.generation }; } function resolveSessionWorkerPlacementMutationGuard(params) { const placement = readSessionWorkerPlacement(params); if (!placement) return { status: "allowed" }; if (isWorkerPlacementSafeForMutation(params.context, placement)) { if (params.action === "reset") return retirementGuard(placement); if (placement.state === "local" || params.action === "fork") return { status: "allowed" }; } return { status: "blocked", error: new SessionWorkerPlacementMutationError(placement.state, params.action, params.key) }; } function retireSessionWorkerPlacementBeforeMutation(params) { const guard = resolveSessionWorkerPlacementMutationGuard(params); if (guard.status !== "retirement-required") return guard.status === "blocked" ? guard.error : void 0; const retirementService = params.context.workerSessionPlacementService; if (!retirementService?.retireSessionPlacement) throw new Error("Worker session placement retirement service is unavailable"); retirementService.retireSessionPlacement(guard); } function resolveSessionWorkerPlacementMutationError(params) { const guard = resolveSessionWorkerPlacementMutationGuard(params); return guard.status === "blocked" ? guard.error : void 0; } function readSessionWorkerPlacement(params) { return params.sessionId ? params.context.workerSessionPlacementService?.getMany([params.sessionId]).get(params.sessionId) : void 0; } function samePlacementOwner(expected, current) { return current?.sessionId === expected?.sessionId && current?.sessionKey === expected?.sessionKey && current?.agentId === expected?.agentId && current?.state === expected?.state && current?.generation === expected?.generation && current?.environmentId === expected?.environmentId && current?.activeOwnerEpoch === expected?.activeOwnerEpoch && current?.executionMode === expected?.executionMode; } /** Retain the exact stopped placement across fallible workspace or session mutations. */ function prepareSessionWorkerPlacementMutationCheck(params, operation = "mutation") { const expected = readSessionWorkerPlacement(params); const assertCurrent = () => { const current = readSessionWorkerPlacement(params); if (!samePlacementOwner(expected, current) || current?.turnClaim || current && !isWorkerPlacementSafeForMutation(params.context, current)) throw new Error(`Worker session placement ${params.sessionId} changed before ${operation}`); }; assertCurrent(); return assertCurrent; } /** Capture retirement without erasing cloud affinity before fallible session cleanup. */ function prepareSessionWorkerPlacementRetirement(params) { const expected = readSessionWorkerPlacement(params); const assertCurrent = prepareSessionWorkerPlacementMutationCheck(params, "retirement"); const retire = params.context.workerSessionPlacementService?.retireSessionPlacement; if (expected && !retire) throw new Error("Worker session placement retirement service is unavailable"); return { assertCurrent, retire: () => { if (!readSessionWorkerPlacement(params)) return; assertCurrent(); if (expected && retire && isWorkerPlacementSafeForMutation(params.context, expected)) retire({ sessionId: expected.sessionId, expectedState: expected.state, expectedGeneration: expected.generation }); } }; } /** Validate before cancellation; the returned stop remains bound to this placement across drains. */ function prepareSessionWorkerPlacementStop(params) { const { agentId, context, sessionId, sessionKey } = params; const expected = readSessionWorkerPlacement(params); const matches = (candidate) => candidate.sessionId === sessionId && candidate.sessionKey === sessionKey && candidate.agentId === agentId; if (expected && !matches(expected)) throw new Error(`Session ${sessionKey} cloud worker placement identity changed.`); if (expected && !isWorkerPlacementSafeForMutation(context, expected) && expected.state !== "active") throw new Error(`Session ${sessionKey} cannot ${params.action} while cloud worker placement is ${expected.state}.`); const beforeDrain = () => { params.authorize?.(); const current = readSessionWorkerPlacement(params); if (!samePlacementOwner(expected, current) || current && current.state !== "active" && !isWorkerPlacementSafeForMutation(context, current)) throw new Error(`Session ${sessionKey} cloud worker placement identity changed.`); }; return async () => { beforeDrain(); if (!expected || expected.state !== "active" || !sessionId) return; if (!context.workerPlacementDispatchService?.reclaim) throw new Error(`Session ${sessionKey} cloud worker reclaim is unavailable.`); const reclaimed = await context.workerPlacementDispatchService.reclaim({ agentId, sessionId, sessionKey }, params.authorize, beforeDrain); params.authorize?.(); const settled = readSessionWorkerPlacement(params); if (reclaimed.state !== "reclaimed" || !matches(reclaimed) || !samePlacementOwner(reclaimed, settled)) throw new Error(`Session ${sessionKey} cloud worker reclaim identity changed.`); }; } //#endregion export { resolveSessionWorkerPlacementMutationError as a, prepareSessionWorkerPlacementStop as i, prepareSessionWorkerPlacementMutationCheck as n, resolveWorkerPlacementArchiveRestoreError as o, prepareSessionWorkerPlacementRetirement as r, retireSessionWorkerPlacementBeforeMutation as s, isFailedWorkerPlacementEnvironmentGone as t };