UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

130 lines (129 loc) • 6.8 kB
import { a as getNodeSqliteKysely, r as executeSqliteQuerySync } from "./kysely-sync-K7ftY-g4.mjs"; import "./sqlite-transaction-DsZ3X6nD.mjs"; import { n as sessionChanges } from "./session-row-changes-BcJIdta8.mjs"; import "./placement-record-BfcBeilz.mjs"; import { i as getRequired, n as find } from "./placement-row-codec-DzKFQEtn.mjs"; import { l as serializeWorkerWorkspaceReconciliationPlan } from "./workspace-manifest-q-txD9ZU.mjs"; import "./workspace-reconcile-DR3VpdSr.mjs"; import "node:crypto"; //#region src/gateway/worker-environments/placement-workspace-journal.ts const query = (db) => getNodeSqliteKysely(db); function isCurrentJournalOwner(db, placement, owner) { if (placement?.state !== "active" && placement?.state !== "draining" || placement.environmentId !== owner.environmentId || placement.activeOwnerEpoch !== owner.ownerEpoch) return false; if (placement.generation === owner.placementGeneration) return true; if (placement.state !== "draining" || placement.generation !== owner.placementGeneration + 1) return false; return executeSqliteQuerySync(db, query(db).selectFrom("worker_workspace_pending_results").select("session_id").where("session_id", "=", owner.sessionId).where("environment_id", "=", owner.environmentId).where("owner_epoch", "=", owner.ownerEpoch).where("placement_generation", "=", owner.placementGeneration)).rows[0] !== void 0; } function assertJournalOwner(db, owner, options = {}) { const placement = getRequired(db, owner.sessionId); const isCurrentOwner = isCurrentJournalOwner(db, placement, owner); const isAllowedFailedOwner = options.allowFailedOwner === true && placement.state === "failed" && placement.generation > owner.placementGeneration && placement.environmentId === owner.environmentId && placement.activeOwnerEpoch === owner.ownerEpoch; if (!isCurrentOwner && !isAllowedFailedOwner) throw new Error(`Cannot reconcile stale worker workspace for session ${owner.sessionId}`); return placement; } function clearWorkerWorkspaceReconciliation(db, sessionId, currentManifestRef) { const existing = executeSqliteQuerySync(db, query(db).selectFrom("worker_workspace_reconciliations").select("current_manifest_ref").where("session_id", "=", sessionId)).rows[0]; if (existing && currentManifestRef && existing.current_manifest_ref !== currentManifestRef) throw new Error(`Worker workspace journal result changed for session ${sessionId}`); executeSqliteQuerySync(db, query(db).deleteFrom("worker_workspace_reconciliations").where("session_id", "=", sessionId)); } function listWorkspaceReconciliationOwners(db) { return executeSqliteQuerySync(db, query(db).selectFrom("worker_workspace_reconciliations").select([ "session_id", "environment_id", "owner_epoch", "placement_generation" ]).orderBy("session_id")).rows.map((row) => ({ sessionId: row.session_id, environmentId: row.environment_id, ownerEpoch: row.owner_epoch, placementGeneration: row.placement_generation })); } function createPlacementWorkspaceJournalOps(runtime) { const { now, write } = runtime; return { pruneOrphanedWorkspaceReconciliations() { return write((db) => { const pruned = []; const changes = []; for (const owner of listWorkspaceReconciliationOwners(db)) { const placement = find(db, owner.sessionId); const stillOwned = isCurrentJournalOwner(db, placement, owner); const retainedFailedOwner = placement?.state === "failed" && placement.environmentId === owner.environmentId && placement.activeOwnerEpoch === owner.ownerEpoch && placement.generation > owner.placementGeneration && placement.recoveryError.startsWith("Worker result abandoned by forced operator teardown"); if (stillOwned || retainedFailedOwner) continue; if (executeSqliteQuerySync(db, query(db).deleteFrom("worker_workspace_reconciliations").where("session_id", "=", owner.sessionId).where("environment_id", "=", owner.environmentId).where("owner_epoch", "=", owner.ownerEpoch).where("placement_generation", "=", owner.placementGeneration)).numAffectedRows === 1n) { pruned.push(owner); if (placement) { const change = { agentId: placement.agentId, sessionKey: placement.sessionKey }; changes.push(change); sessionChanges.emit(change, db); } } } return { owners: pruned, changes }; }); }, beginWorkspaceReconciliation(owner, journal) { if (journal.appliedManifestRef) throw new Error("Worker workspace reconciliation cannot begin as already applied"); return write((db) => { const placement = assertJournalOwner(db, owner); if (placement.workspaceBaseManifestRef !== journal.baseManifestRef) throw new Error(`Worker workspace base changed for session ${owner.sessionId}`); if (executeSqliteQuerySync(db, query(db).insertInto("worker_workspace_reconciliations").values({ session_id: owner.sessionId, environment_id: owner.environmentId, owner_epoch: owner.ownerEpoch, placement_generation: owner.placementGeneration, base_manifest_ref: journal.baseManifestRef, current_manifest_ref: journal.currentManifestRef, plan_json: serializeWorkerWorkspaceReconciliationPlan(journal), base_pack: journal.basePack, created_at_ms: now() }).onConflict((conflict) => conflict.column("session_id").doNothing())).numAffectedRows !== 1n) throw new Error(`Worker workspace reconciliation is already pending for ${owner.sessionId}`); const change = { agentId: placement.agentId, sessionKey: placement.sessionKey }; sessionChanges.emit(change, db); return { owners: [owner], changes: [change] }; }); }, abortWorkspaceReconciliation(owner, options = {}) { return write((db) => { if (!options.force) { const placement = assertJournalOwner(db, owner); clearWorkerWorkspaceReconciliation(db, owner.sessionId); const change = { agentId: placement.agentId, sessionKey: placement.sessionKey }; sessionChanges.emit(change, db); return { owners: [owner], changes: [change] }; } if (executeSqliteQuerySync(db, query(db).deleteFrom("worker_workspace_reconciliations").where("session_id", "=", owner.sessionId).where("environment_id", "=", owner.environmentId).where("owner_epoch", "=", owner.ownerEpoch).where("placement_generation", "=", owner.placementGeneration)).numAffectedRows !== 1n) throw new Error(`Worker workspace journal changed for ${owner.sessionId}`); const change = { all: true, scope: "worker-placements" }; sessionChanges.emit(change, db); return { owners: [owner], changes: [change] }; }); } }; } //#endregion export { createPlacementWorkspaceJournalOps as n, clearWorkerWorkspaceReconciliation as t };