openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
122 lines (121 loc) • 6.82 kB
JavaScript
//#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 };