UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

513 lines (512 loc) • 18.2 kB
import { l as resolveSessionStorePathCore } from "./paths-CAzSovN1.mjs"; import { c as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-BuCmtpuj.mjs"; import "./session-accessor-BM3Wa1Bx.mjs"; import { a as upsertSessionUpstreamLink, t as deleteSessionUpstreamLink } from "./session-upstream-links-DR1SolSc.mjs"; import { AsyncLocalStorage } from "node:async_hooks"; import { isDeepStrictEqual } from "node:util"; import { randomUUID } from "node:crypto"; //#region src/agents/harness/native-session/binding-leases.ts /** Serializes native binding changes across processes without owning backend policy. */ function createNativeSessionBindingLeases(state, options) { if (!state.withCurrent) throw new Error(options.errors.atomicUpdatesRequired); const context = new AsyncLocalStorage(); const update = async (key, prepare, assertCurrent) => { let assertPreparedCurrent; const store = state.withCurrent({ assertCurrent: () => { assertCurrent?.(); assertPreparedCurrent?.(); } }); let observed = await store.observe(key); while (true) { const prepared = prepare(observed.value); assertPreparedCurrent = prepared.assertCurrent; const outcome = await store.compareAndApply(key, observed.comparison, prepared.next === void 0 ? { operation: "update", action: "keep" } : { operation: "update", action: "set", value: prepared.next, ...prepared.ttlMs === void 0 ? {} : { ttlMs: prepared.ttlMs } }); if (outcome.status !== "conflict") return prepared.next !== void 0; assertPreparedCurrent = void 0; observed = outcome.current; } }; const transact = async (key, apply, ttlMs, assertCurrent) => { const deadline = Date.now() + options.lease.waitMs; while (true) { let busy = false; let leaseLost = false; let result; const owner = context.getStore()?.get(key); if (owner && owner.phase !== "held") throw options.errors.lostLease(key); if (owner?.failure) throw owner.failure; const ownedToken = owner?.token; const assertOwnerCurrent = () => { assertCurrent?.(); owner?.assertCurrent?.(); if (owner && (owner.phase !== "held" || owner.failure)) throw owner.failure ?? options.errors.lostLease(key); }; assertOwnerCurrent(); await update(key, (raw) => { busy = false; leaseLost = false; const current = readRecord(key, raw); const lease = current?.lease; const now = Date.now(); if (ownedToken && (!lease || lease.token !== ownedToken || lease.expiresAt <= now)) { leaseLost = true; return {}; } if (lease && lease.token !== ownedToken && lease.expiresAt > now) { busy = true; return {}; } const applied = apply(current, ownedToken); const nextLease = applied.next?.lease; result = applied.result; return { next: applied.next, ttlMs: typeof ttlMs === "function" ? applied.next && ttlMs(applied.next) : ttlMs, assertCurrent: () => { if (ownedToken && lease.expiresAt <= Date.now() || nextLease && (nextLease.token !== lease?.token || nextLease.expiresAt !== lease?.expiresAt) && nextLease.expiresAt <= Date.now()) { const failure = options.errors.lostLease(key); if (owner) owner.failure = failure; throw failure; } } }; }, assertOwnerCurrent); if (leaseLost) { const failure = options.errors.lostLease(key); if (owner) owner.failure = failure; throw failure; } if (!busy) return result; if (Date.now() >= deadline) throw options.errors.leaseTimeout(key); await sleep(options.lease.retryIntervalMs); } }; const withLease = async (key, run, acquisition) => { acquisition.assertCurrent?.(); const owned = context.getStore(); const existing = owned?.get(key); if (existing) { if (existing.phase !== "held") throw options.errors.lostLease(key); const failureBeforeRun = existing.failure; if (failureBeforeRun) throw failureBeforeRun; const result = await run(); acquisition.assertCurrent?.(); const failureAfterRun = existing.failure; if (failureAfterRun) throw failureAfterRun; return result; } const token = randomUUID(); const acquired = await transact(key, (current) => { const next = acquisition.prepareLease(current, { token, expiresAt: Date.now() + options.lease.staleMs }); return { result: next !== void 0, next }; }, void 0, acquisition.assertCurrent); acquisition.assertCurrent?.(); if (!acquired) throw options.errors.acquisitionRejected(key); const owner = { token, phase: "held", assertCurrent: acquisition.assertCurrent }; const nested = new Map(owned); nested.set(key, owner); let renewal; const heartbeat = setInterval(() => { if (!renewal) renewal = renew(key, owner).finally(() => { renewal = void 0; }); }, options.lease.renewIntervalMs); heartbeat.unref(); try { const result = await context.run(nested, run); clearInterval(heartbeat); await renewal; acquisition.assertCurrent?.(); if (owner.failure) throw owner.failure; return result; } finally { clearInterval(heartbeat); owner.phase = "closed"; await renewal; acquisition.assertCurrent?.(); try { await update(key, (raw) => { const stored = readRecord(key, raw); if (stored?.lease?.token !== token) return {}; const { lease: _lease, ...released } = stored; return { next: readRecord(key, released), ttlMs: options.releaseTtlMs(key, stored) }; }, acquisition.assertCurrent); } catch (error) { acquisition.assertCurrent?.(); options.onReleaseFailure?.(key, error); } } }; const readRecord = (key, raw) => { const current = options.readRecord(raw); if (raw !== void 0 && !current) throw options.errors.invalidRow(key); return current; }; const renew = async (key, owner) => { if (owner.failure || owner.phase !== "held") return; try { if (!await update(key, (raw) => { const current = readRecord(key, raw); const lease = current?.lease; const now = Date.now(); if (!current || !lease || lease.token !== owner.token || lease.expiresAt <= now) return {}; return { next: { ...current, lease: { token: owner.token, expiresAt: now + options.lease.staleMs } }, assertCurrent: () => { if (lease.expiresAt <= Date.now()) throw options.errors.lostLease(key); } }; }, () => { owner.assertCurrent?.(); if (owner.phase !== "held" || owner.failure) throw owner.failure ?? options.errors.lostLease(key); }) && owner.phase === "held") owner.failure = options.errors.lostLease(key); } catch (error) { if (owner.phase === "held") owner.failure = options.errors.lostLease(key, error); } }; const captureLeaseAssertion = (key) => { const owner = context.getStore()?.get(key); const assertCurrent = () => { if (!owner || owner.phase !== "held") throw options.errors.lostLease(key); const lease = readRecord(key, state.lookup(key))?.lease; if (!lease || lease.token !== owner.token || lease.expiresAt <= Date.now()) throw options.errors.lostLease(key); }; assertCurrent(); return assertCurrent; }; return { captureLeaseAssertion, transact, withLease, hasLease: (key) => context.getStore()?.has(key) === true, owner: (key) => context.getStore()?.get(key) }; } function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } //#endregion //#region src/agents/harness/native-session/binding-lifecycle.ts /** Shared binding coordination; backend callbacks retain native ownership and retention policy. */ function createNativeSessionBindingLifecycle(state, options) { const leases = createNativeSessionBindingLeases(state, options); const exclusiveContext = new AsyncLocalStorage(); let activeMutations = 0; let pendingExclusiveOperations = 0; let exclusiveTail = Promise.resolve(); let mutationsDrained = []; const waitForMutations = async () => { if (activeMutations === 0) return; await new Promise((resolve) => { mutationsDrained.push(resolve); }); }; const withMutation = async (run) => { if (exclusiveContext.getStore() === true) return await run(); if (pendingExclusiveOperations > 0) throw new Error(options.errors.mutationBlocked); activeMutations += 1; try { return await run(); } finally { activeMutations -= 1; if (activeMutations === 0) { const drained = mutationsDrained; mutationsDrained = []; for (const resolve of drained) resolve(); } } }; const withExclusiveMutationFence = async (run) => { pendingExclusiveOperations += 1; const operation = exclusiveTail.then(async () => { await waitForMutations(); return await exclusiveContext.run(true, run); }); exclusiveTail = operation.then(() => void 0, () => void 0); try { return await operation; } finally { pendingExclusiveOperations -= 1; } }; const withDeletion = async (key, deletion, run) => { const deleteIf = state.deleteIf?.bind(state); if (!deleteIf) throw new Error(options.errors.conditionalDeletionRequired); return await withMutation(async () => { deletion.assertCurrent(); if (state.lookup(key) === void 0) { let active = true; try { return await run(void 0, { commit() { deletion.assertCurrent(); if (!active || state.lookup(key) !== void 0) throw new Error(options.errors.deletionChanged); }, rollback() {} }); } finally { active = false; } } return await leases.withLease(key, async () => { const owner = leases.owner(key); const stored = options.readRecord(state.lookup(key)); deletion.assertRecordCurrent(stored); if (!stored) throw new Error(options.errors.deletionChanged); const { lease: _lease, ...expectedValue } = stored; let deleted; let active = true; const assertActive = () => { deletion.assertCurrent(); if (!active || owner.phase === "closed" || owner.failure) throw owner.failure ?? options.errors.lostLease(key); }; try { return await run(stored, { commit() { assertActive(); if (deleted) return; let removed; if (!deleteIf(key, (raw) => { const { lease, ...value } = options.readRecord(raw) ?? {}; if (lease?.token !== owner.token || lease.expiresAt <= Date.now() || !isDeepStrictEqual(value, expectedValue)) return false; removed = raw; return true; }) || !removed) throw new Error(options.errors.deletionChanged); deleted = removed; owner.phase = "deleted"; }, rollback() { assertActive(); if (!deleted) return; const restored = { ...deleted, lease: { token: owner.token, expiresAt: Date.now() + options.lease.staleMs } }; if (!state.registerIfAbsent(key, restored)) throw new Error(options.errors.rollbackChanged); deleted = void 0; owner.phase = "held"; } }); } finally { active = false; } }, deletion); }); }; return { captureLeaseAssertion: leases.captureLeaseAssertion, transact: leases.transact, withLease: leases.withLease, hasLease: leases.hasLease, withMutation, withExclusiveMutationFence, withDeletion }; } //#endregion //#region src/agents/harness/native-session/binding-generation.ts /** Resolve host lineage before selecting a native queue, catalog, or connection. */ async function resolveNativeSessionBinding(params) { let assertCurrent = params.assertCurrent ?? (() => {}); const assertAdmissionCurrent = () => { assertCurrent(); params.signal?.throwIfAborted(); }; assertAdmissionCurrent(); params.assertBinding?.(readOwnershipBinding(params)); const authority = params.target?.sessionKey?.trim() ? captureNativeSessionGenerationAuthority({ ...params, target: params.target, assertCurrent }) : void 0; assertCurrent = authority?.assertCurrent ?? assertCurrent; assertAdmissionCurrent(); let binding = params.readBinding(); if (!binding && authority && params.target && params.generation) { if (!await reclaimPreparedGeneration({ ...params, generation: params.generation, reclaimStale: params.reclaimStale === true }, authority, assertAdmissionCurrent) && params.reclaimStale) throw params.createSupersededError(params.target.sessionId); binding = params.readBinding(); } assertAdmissionCurrent(); params.assertBinding?.(binding); return { binding, assertCurrent }; } /** Let the authoritative OpenClaw generation adopt its predecessor or reclaim a stale row. */ async function reclaimNativeSessionGeneration(params) { params.assertCurrent?.(); if (!params.target.sessionKey?.trim()) return true; const authority = captureNativeSessionGenerationAuthority(params); if (authority.state === "superseded") return false; return reclaimPreparedGeneration(params, authority); } /** Capture the host generation and predecessor together, then revalidate both after waits. */ function captureNativeSessionGenerationAuthority(params) { const readEntry = () => { try { return readBindingSessionEntry(params); } catch { return null; } }; const entry = readEntry(); const current = entry?.sessionId === params.target.sessionId; const state = entry === void 0 ? "ephemeral" : current ? "current" : "superseded"; const previousSessionId = current ? entry.previousSessionId : void 0; const assertHostCurrent = () => { if (state === "ephemeral") return; const latest = readEntry(); if (state !== "current" || !latest || latest.sessionId !== params.target.sessionId || latest.previousSessionId !== previousSessionId) throw params.createSupersededError(params.target.sessionId); }; return { state, previousSessionId, assertHostCurrent, assertCurrent() { params.assertCurrent?.(); assertHostCurrent(); } }; } async function reclaimPreparedGeneration(params, authority, assertCurrent = authority.assertCurrent) { const plan = await params.generation.prepareReclaim(); assertCurrent(); if (plan.kind === "resolved") return plan.result; if (authority.state !== "current") return false; params.onHostGenerationVerified?.(authority.assertHostCurrent); if (authority.previousSessionId === plan.expectedPreviousSessionId) { const adopted = await params.generation.adopt(authority.previousSessionId, assertCurrent); if (adopted !== "absent") return adopted !== "conflict"; } if (params.reclaimStale === false) return false; return params.generation.reclaim(plan.expectedPreviousSessionId, assertCurrent); } function readOwnershipBinding(params) { const binding = params.readBinding(); if (binding || !params.target) return binding; const entry = readBindingSessionEntry({ ...params, target: params.target }); return entry?.sessionId === params.target.sessionId && entry.previousSessionId ? params.readBinding(entry.previousSessionId) : void 0; } function readBindingSessionEntry(params) { const { target } = params; return target.sessionKey?.trim() ? loadSessionEntryReadOnly({ agentId: target.agentId, sessionKey: target.sessionKey.trim(), storePath: params.storePath?.trim() || resolveSessionStorePathCore(params.config?.session?.store, { agentId: target.agentId }), hydrateSkillPromptRefs: false, readConsistency: "latest" }) : void 0; } //#endregion //#region src/agents/harness/native-session/initialization.ts /** Backend binding ownership redeems the existing host initializer's exact creation handle. */ function createNativeSessionInitializationOwner(options) { const initializations = /* @__PURE__ */ new WeakMap(); return { /** Capture cleanup ownership before any potentially committing binding or link write. */ prepare(params) { const { initialization, bindingStore, identity } = params; initialization.assertCurrent(); let link; const ownership = { store: bindingStore, identity: structuredClone(identity), assertCleanupAllowed: params.assertCleanupAllowed, cleanup: async () => { initialization.assertRollbackCurrent(); if (link && deleteSessionUpstreamLink(link.sessionKey, link.agentId, { expected: link, assertCommitAllowed: initialization.assertRollbackCurrent }) === "changed") throw new Error(options.errors.linkChanged); initialization.assertRollbackCurrent(); await cleanup?.(initialization.assertRollbackCurrent); initialization.assertRollbackCurrent(); } }; initializations.set(initialization, ownership); const cleanup = params.prepareCleanup?.(); return { assertCurrent: initialization.assertCurrent, async bind(binding) { initialization.assertCurrent(); ownership.binding = options.validateBinding(binding); if (!await options.writeBinding(bindingStore, identity, ownership.binding, initialization.assertCurrent)) { ownership.binding = void 0; throw new Error(options.errors.bindingChanged); } initialization.assertCurrent(); }, link(input) { initialization.assertCurrent(); const now = Date.now(); link = structuredClone({ ...input, createdAt: now, updatedAt: now }); if (!upsertSessionUpstreamLink(input, { now, ifAbsent: true, assertCommitAllowed: initialization.assertCurrent })) { link = void 0; throw new Error(options.errors.linkWriteFailed); } initialization.assertCurrent(); } }; }, getRollback(store, params, identity, binding) { const handle = params.initialization; if (!handle) return; handle.assertRollbackCurrent(); const ownership = initializations.get(handle); if (!ownership && !binding) return; if (!ownership || ownership.store !== store || !isDeepStrictEqual(ownership.identity, identity) || binding && !isDeepStrictEqual(ownership.binding, binding)) throw new Error(options.errors.ownerChanged); ownership.assertCleanupAllowed?.(); return ownership.cleanup; } }; } //#endregion export { createNativeSessionBindingLifecycle as a, resolveNativeSessionBinding as i, captureNativeSessionGenerationAuthority as n, reclaimNativeSessionGeneration as r, createNativeSessionInitializationOwner as t };