UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

403 lines (402 loc) • 17.8 kB
import { c as isRecord } from "../../record-coerce-TIsquoVR.mjs"; import { d as readDatabasePathIdentitySync } from "../../sqlite-worker-identity-BRNRpRyc.mjs"; import { t as assertTransactionUsable } from "../../sqlite-transaction-DBRLvCDM.mjs"; import { t as encodeOpenClawStateWorkerError } from "../../openclaw-state-worker-error-Bi-BnNrV.mjs"; import { r as SqliteTranscriptMutationConflictError } from "../../session-mutation-conflict-error-CSUFwifz.mjs"; import { n as parseTranscriptAppendRefusal, t as SessionTranscriptWriterClaimReboundError } from "../../session-transcript-writer-claim-error-eP-_sDN9.mjs"; import { l as resolveOpenClawAgentSqlitePath } from "../../openclaw-agent-db.paths-BbmZlEP5.mjs"; import { i as deferSqliteWorkerCommitReceipt, s as requestSqliteWorkerOperationAdmission } from "../../sqlite-worker-operation-admission-C5lBzzTk.mjs"; import { t as captureSessionRowChanges } from "../../session-row-changes-Dj2yVWo-.mjs"; import { i as getSqliteWorkerStateContext } from "../../sqlite-worker-state-context-G3Xbcj1W.mjs"; import { c as runOpenClawAgentWriteTransaction } from "../../openclaw-agent-db-DWI9Bgur.mjs"; import { C as isReadableSessionMessage, S as isIndexedSessionEntry, a as prepareTranscriptPayloadForReuse, w as parseOpaqueLeafEntry, x as findSessionTranscriptHeader } from "../../transcript-payload-K8XYDkGU.mjs"; import { i as toDatabaseOptions } from "../../session-accessor.sqlite-scope-helpers-CfDTAEsL.mjs"; import { n as assertCanonicalSessionKeyWrite } from "../../session-canonical-key-BTldoZ8u.mjs"; import { i as runWithCliHistoryWriter } from "../../cli-history-boundary-CK2yr7Tm.mjs"; import { d as resolveSqliteTranscriptScope } from "../../session-accessor.sqlite-scope-BkgwxatT.mjs"; import { o as readStagedSessionTranscriptAuthority } from "../../session-transcript-authority-DV7l6G42.mjs"; import { i as runWithSessionTranscriptReadFence } from "../../session-transcript-read-fence-DqVaDIch.mjs"; import { y as runWithSessionPendingInputWorkerCustody } from "../../session-accessor.sqlite-pending-inputs-KrOcYFST.mjs"; import { E as validatePreparedAssistantAppendSync } from "../../session-accessor.sqlite-read-UBWmiUlC.mjs"; import { t as readSessionPendingInputAuthorityFacts } from "../../session-pending-input-authority.kernel-DtvS9iEK.mjs"; import { a as appendTranscriptMessageSnapshotSync, c as replaceSessionWithBranchedTranscriptInTransaction, m as replaceTranscriptSuffixEventsSync, n as appendTranscriptEventSnapshotSync } from "../../session-accessor.sqlite-transcript-write-ClP59_lJ.mjs"; import { r as readSessionManagerReload } from "../../session-manager-reload-BXFIyUNy.mjs"; import { t as ensureSessionEntryInTransaction } from "../../session-accessor.sqlite-initial-entry-CnxRFmk1.mjs"; import { i as persistCompactionBoundaryWithSessionEntryInWorker, n as readTranscriptMutationAtSync } from "../../session-accessor.sqlite-metadata-read-BzhXpz0y.mjs"; import { t as captureSessionEntryMetadataReceipts } from "../../session-entry-metadata-receipt-C823yxYo.mjs"; import { t as prepareTranscriptRewriteSync } from "../../session-accessor.sqlite-branch-rewrite-CjiA1JZY.mjs"; import { serialize } from "node:v8"; //#region src/agents/sessions/session-manager-maintenance.worker.ts function executeSessionMaintenance(command, scope, context) { let projectionNeedsReconcile = false; const projection = { scheduleProjectionReconcile: false, onProjectionReconcileNeeded: () => { projectionNeedsReconcile = true; } }; if (command.type === "session.transcript.branch") return runOpenClawAgentWriteTransaction((database) => { if (database.db !== context.database) throw new Error("Session branch lost its borrowed canonical connection"); context.admit("transaction"); const result = replaceSessionWithBranchedTranscriptInTransaction(database, scope, command.input.branch, command.input.expectedLifecycleRevision, void 0, projection); context.admit("commit"); return { ...result, projectionNeedsReconcile }; }, toDatabaseOptions(resolveSqliteTranscriptScope(scope)), { operationLabel: command.type }); if (command.type === "session.transcript.replaceSuffix") { const [expected, next, prefix, mutationAt, startsAtPrefix, retained] = command.input.args; let version; return { replaced: replaceTranscriptSuffixEventsSync(scope, expected, next, prefix, mutationAt, (committed) => { version = committed; }, startsAtPrefix, retained, (stage) => context.admit(stage), projection), version, projectionNeedsReconcile }; } let version; prepareTranscriptRewriteSync(scope, command.input.appendParentId, () => {}, command.input.version, (stage) => context.admit(stage), { messagesAlreadyRedacted: true, ...projection })(command.input.entries, new Map(command.input.sources), (committed) => { version = committed; }); if (!version) throw new Error("Session rewrite did not return its committed version"); return { version, entries: command.input.entries, projectionNeedsReconcile }; } //#endregion //#region src/agents/sessions/session-manager-metadata.worker.ts function runWithMetadataMessageAdmission(context, controls, run) { let transactionFacts; let pendingAuthorityChecked = false; const readAuthority = () => { const source = controls?.pendingInput?.facts; if (source?.preparedAuthority && source.databasePath !== context.databasePath) return null; return source?.preparedAuthority && source.agentId && source.databaseAgentId ? readSessionPendingInputAuthorityFacts({ db: context.database, path: context.databasePath, agentId: source.databaseAgentId }, source.sessionKey, source.agentId) : void 0; }; const requestMessageCheck = (check) => { requestSqliteWorkerOperationAdmission({ stage: "prepare", facts: { kind: "session-message", domainFacts: transactionFacts, check, authority: check === "pending" ? readAuthority() : void 0 } }); if (check === "pending") pendingAuthorityChecked = true; }; const admit = (stage) => context.admit(stage, (request, dispatch) => { transactionFacts = request.facts; dispatch({ ...request, facts: { kind: "session-message", domainFacts: request.facts, ...stage === "commit" && pendingAuthorityChecked ? { check: "pending" } : {}, ...stage === "commit" && pendingAuthorityChecked ? { authority: readAuthority() } : {} } }); }); const write = () => run(admit, () => requestMessageCheck("fresh")); const pending = controls?.pendingInput; if (!pending) return { value: write() }; const result = runWithSessionPendingInputWorkerCustody(pending.facts, pending.relocation, () => requestMessageCheck("pending"), write); return { value: result.value, pendingInputReceipt: result.receipt }; } function decodeMetadataAppendEvent(input) { const event = typeof input.event === "string" ? JSON.parse(input.event) : { ...input.event, message: JSON.parse(input.message?.messageJson ?? "null") }; if (isIndexedSessionEntry(event)) { if (event.type === "message" !== (typeof input.event !== "string")) throw new Error("Session message append requires prepared storage bytes"); return event; } const header = findSessionTranscriptHeader([event]); if (header) return header; const leaf = parseOpaqueLeafEntry(event); if (leaf && isRecord(event) && typeof event.timestamp === "string") return { ...leaf, type: "leaf", timestamp: event.timestamp }; throw new Error("Invalid serialized session transcript entry"); } /** Borrow the canonical actor's connection; this domain never opens or closes a database. */ function bindSqliteWorkerBackend(_input, nativeContext) { let entryChanges = []; const context = { ...nativeContext, admit(stage, restriction) { const transcriptPublication = stage === "commit" ? readStagedSessionTranscriptAuthority({ db: context.database }) ?? [] : []; const entryPublication = stage === "commit" ? captureSessionEntryMetadataReceipts(entryChanges) : []; if (!transcriptPublication.length && !entryPublication.length) { nativeContext.admit(stage, restriction); return; } const publication = { kind: "session-manager-authority", transcriptPublication, entryPublication }; deferSqliteWorkerCommitReceipt(context.database, publication); nativeContext.admit(stage, (request, dispatch) => { const publish = (restricted) => dispatch({ ...restricted, facts: { ...publication, domainFacts: restricted.facts } }); if (restriction) restriction(request, publish); else publish(request); }); } }; let closed = false; const assertOpen = () => { if (closed || !context.database.isOpen) throw new Error("Session metadata domain is closed"); assertTransactionUsable(context.database); }; const execute = (command) => { assertOpen(); const scope = { ...command.input.scope, env: getSqliteWorkerStateContext().environment }; const resolved = resolveSqliteTranscriptScope(scope); const options = toDatabaseOptions(resolved); if (readDatabasePathIdentitySync(resolveOpenClawAgentSqlitePath(options)).canonicalPath !== context.databasePath) throw new Error("Session metadata target changed its database owner"); scope.storePath = context.databasePath; resolved.path = context.databasePath; options.path = context.databasePath; if (command.type === "session.metadata.mutation") return { ok: true, value: readTranscriptMutationAtSync(scope) }; assertCanonicalSessionKeyWrite(resolved.sessionKey, resolved.agentId); if (command.type === "session.transcript.compactionBoundary") return { ok: true, value: persistCompactionBoundaryWithSessionEntryInWorker(scope, { ...command.input, prepared: { ...command.input.prepared, scope: { ...command.input.prepared.scope, env: scope.env } } }, context) }; if (command.type === "session.transcript.branch") return { ok: true, value: executeSessionMaintenance(command, scope, context) }; if (command.type === "session.transcript.replaceSuffix") return { ok: true, value: executeSessionMaintenance(command, scope, context) }; if (command.type === "session.transcript.rewrite") { const result = runWithMetadataMessageAdmission(context, command.input, (admit) => executeSessionMaintenance(command, scope, { ...context, admit })); return { ok: true, value: { ...result.value, pendingInputReceipt: result.pendingInputReceipt } }; } if (command.type === "session.transcript.appendMessage") { const message = JSON.parse(command.input.messageJson); if (!isReadableSessionMessage(message)) throw new Error("Invalid serialized session transcript message"); const prepared = { messageJson: command.input.messageJson, persistedMessage: message }; const result = runWithMetadataMessageAdmission(context, command.input, (admit) => runOpenClawAgentWriteTransaction((database) => { if (database.db !== context.database) throw new Error("Session message lost its borrowed canonical connection"); admit("transaction"); let projectionNeedsReconcile = false; const snapshot = appendTranscriptMessageSnapshotSync(scope, { message, cwd: command.input.cwd }, prepared, { messageAlreadyRedacted: true, scheduleProjectionReconcile: false, onProjectionReconcileNeeded: () => { projectionNeedsReconcile = true; } }); admit("commit"); return { ok: true, value: { snapshot, projectionNeedsReconcile } }; }, options, { operationLabel: command.type })); if (result.value.ok) { result.value.value.pendingInputReceipt = result.pendingInputReceipt; const { snapshot } = result.value.value; if (snapshot.ok && snapshot.value.result?.message === message) snapshot.value.result.message = void 0; } return result.value; } const event = command.type === "session.metadata.append" ? decodeMetadataAppendEvent(command.input) : void 0; let prepared; if (command.type === "session.metadata.append" && event?.type === "message") { const { message } = command.input; if (!message) throw new Error("Session message append requires prepared storage bytes"); const { message: _message, ...envelope } = event; const eventJson = `${JSON.stringify(envelope).slice(0, -1)},"message":${message.messageJson}}`; prepared = { messageJson: message.messageJson, persistedMessage: event.message, physicalPayload: prepareTranscriptPayloadForReuse(context.database, eventJson, event) }; if (message.validateTurn) { const mutationAt = validatePreparedAssistantAppendSync(scope, event.parentId, command.input.view?.admission?.entryId); if (mutationAt === void 0) throw new SqliteTranscriptMutationConflictError(scope.sessionId); command.input.options.expectedMutationAt = mutationAt; } } const messageControl = command.type === "session.metadata.append" ? command.input.message : void 0; const result = runWithMetadataMessageAdmission(context, messageControl, (admit, beforeFreshMessageCommit) => runOpenClawAgentWriteTransaction((database) => { if (database.db !== context.database) throw new Error("Session metadata lost its borrowed canonical connection"); admit("transaction"); if (command.type === "session.metadata.initialize") { const initialized = ensureSessionEntryInTransaction(database, resolved, scope, command.input.entry, command.input.initialWriterRunId); admit("commit"); return { ok: true, value: initialized }; } let projectionNeedsReconcile = false; const projection = { scheduleProjectionReconcile: false, onProjectionReconcileNeeded: () => { projectionNeedsReconcile = true; } }; if (!event) throw new Error("Session metadata append requires a serialized event"); const { message } = command.input; const snapshot = event.type === "message" && message ? appendTranscriptMessageSnapshotSync(scope, { ...command.input.options, message: event.message, eventId: event.id, parentId: event.parentId, now: Date.parse(event.timestamp), cwd: message.cwd, idempotencyLookup: message.idempotencyLookup, ...message.freshMessageCheck ? { beforeFreshMessageCommit } : {} }, prepared, projection) : appendTranscriptEventSnapshotSync(scope, event, command.input.options, { ...projection, eventJson: typeof command.input.event === "string" ? command.input.event : void 0 }); admit("commit"); return { ok: true, value: { snapshot, projectionNeedsReconcile } }; }, options, { operationLabel: command.type, diagnosticContext: { sessionId: scope.sessionId, eventType: event?.type, messageRole: event?.type === "message" ? event.message.role : void 0 } })); const outcome = result.value; if (outcome.ok && "snapshot" in outcome.value && outcome.value.snapshot.ok) { const receipt = outcome.value.snapshot.value.result; if (receipt && "message" in receipt && receipt.message === prepared?.persistedMessage) receipt.message = void 0; } if (result.pendingInputReceipt && outcome.ok && "snapshot" in outcome.value) outcome.value.pendingInputReceipt = result.pendingInputReceipt; if (command.type === "session.metadata.append" && event && event.type !== "session" && command.input.view && outcome.ok && "snapshot" in outcome.value && outcome.value.snapshot.ok) { const { view } = command.input; const committed = outcome.value.snapshot.value; if (!committed.result) return outcome; const adoptedMessage = "messageId" in committed.result && committed.result.messageId !== event.id; if (!committed.result.appended && !adoptedMessage) return outcome; const version = view.loadedVersion; const effectiveParentId = "effectiveParentId" in committed.result ? committed.result.effectiveParentId : void 0; if (adoptedMessage || version && (committed.before.generation !== version.generation || committed.before.rawSeq !== version.rawSeq) || effectiveParentId !== void 0 && effectiveParentId !== event.parentId) try { outcome.value.reload = { ok: true, value: runWithSessionTranscriptReadFence(view.admission, () => readSessionManagerReload(scope, view.limits, view.admission !== void 0)) }; serialize(outcome); } catch (error) { outcome.value.reload = { ok: false, error: encodeOpenClawStateWorkerError(error, { includeOrdinary: true }) }; } } return outcome; }; return { execute(command) { try { const cliWriter = command.type === "session.transcript.appendMessage" || command.type === "session.metadata.append" ? command.input.cliWriter : void 0; return runWithCliHistoryWriter(cliWriter ? { ...cliWriter, target: { ...command.input.scope, storePath: context.databasePath }, assertCurrent: assertOpen, assertReadable: assertOpen } : void 0, () => captureSessionRowChanges(context.database, (changes) => { entryChanges = changes; try { return execute(command); } finally { entryChanges = []; } }).result); } catch (error) { if (error instanceof SessionTranscriptWriterClaimReboundError) { const refusal = parseTranscriptAppendRefusal(error.cause); if (error.cause !== void 0 && !refusal) throw new Error("Session metadata refusal has an invalid identity", { cause: error }); return { ok: false, refusal }; } throw error; } }, assertSettled() { assertOpen(); if (context.database.isTransaction) throw new Error("Session metadata command left a transaction open"); }, close() { closed = true; } }; } //#endregion export { bindSqliteWorkerBackend };