UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

260 lines (259 loc) • 11.6 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs"; import { c as resolveSessionStorePathCore } from "./paths-Bonddxfk.mjs"; import { i as isIncognitoOpenClawAgentSqlitePath, l as resolveOpenClawAgentSqlitePath, r as createOpenClawAgentDatabasePathMatcher } from "./openclaw-agent-db.paths-BbmZlEP5.mjs"; import { n as withOpenClawAgentDatabaseReadOnly } from "./openclaw-agent-db-readonly-DAqV3tqr.mjs"; import { c as SqliteWorkerError } from "./sqlite-worker-contract-C51cJHzg.mjs"; import { r as createSqliteWorkerOperationAdmission } from "./sqlite-worker-operation-admission-C5lBzzTk.mjs"; import { n as captureOpenClawStateReadWorkerContext } from "./openclaw-state-worker-context-BvvgY6-t.mjs"; import { i as getOpenClawAgentDatabaseIfOpen } from "./openclaw-agent-db-DWI9Bgur.mjs"; import { t as runOpenClawAgentWorkerWrite } from "./openclaw-agent-write-admission-CXJiE5VB.mjs"; import { i as toDatabaseOptions } from "./session-accessor.sqlite-scope-helpers-CfDTAEsL.mjs"; import { t as captureSessionTranscriptStorageEnvironment } from "./transcript-target-binding-DpcuSIFy.mjs"; import { n as captureOpenClawAgentDatabaseExecution } from "./openclaw-agent-execution-C2B2fucX.mjs"; import { o as resolveSqliteReadScope } from "./session-accessor.sqlite-scope-BkgwxatT.mjs"; import { n as selectConversationRowsFromDatabase } from "./session-accessor.sqlite-conversation-read-WXGxcUdb.mjs"; import { r as withConversationPublication } from "./session-accessor.sqlite-conversation-publication-D5ZVrcUt.mjs"; import { t as captureSessionStoreReadCandidates } from "./session-store-target-inventory-C9ERDnSH.mjs"; import { t as withOpenClawAgentDatabaseWrite } from "./openclaw-agent-db-write-B3Uithdx.mjs"; import { u as withSessionStoreReaderInWorker } from "./session-entry-read-runtime-BdnQjoL6.mjs"; import path from "node:path"; //#region src/config/sessions/conversation-registry.ts function resolveConversationRegistryScope(params) { return pinConversationDatabaseScope({ agentId: params.agentId, storePath: resolveSessionStorePathCore(params.config.session?.store, { agentId: params.agentId }) }).scope; } async function prepareConversationRegistryScope(params) { const input = { agentId: params.agentId, storePath: resolveSessionStorePathCore(params.config.session?.store, { agentId: params.agentId }) }; if (isIncognitoOpenClawAgentSqlitePath(input.storePath, input)) return pinConversationDatabaseScope(input).scope; return withConversationRead(input, async ({ database, logicalAgentId }) => ({ agentId: logicalAgentId, databaseAgentId: database.agentId, storePath: database.path, env: database.env })); } function withConversationRead(input, read) { const env = captureSessionTranscriptStorageEnvironment(input.env ?? process.env); const storePath = path.resolve(input.storePath ?? resolveSessionStorePathCore(void 0, { agentId: input.agentId, env })); const context = captureOpenClawStateReadWorkerContext({ env }); const source = createOpenClawAgentDatabasePathMatcher(); for (const candidate of captureSessionStoreReadCandidates(storePath)) source(candidate.path, candidate.path); return withSessionStoreReaderInWorker({ agentId: input.agentId, storePath, env }, async (owner) => { if (input.databaseAgentId && owner.database.agentId !== input.databaseAgentId) throw new Error("Conversation database owner changed. Retry the request."); const result = await read(owner); owner.assertCurrent(); return result; }, { dataOnly: true, logical: { assertCurrent() { context.maintenanceScope?.assertAdmission(); context.admission.assertCurrent(); if (!source.isCurrent()) throw new Error("Session store changed while reading conversations. Retry the request."); } } }); } function selectConversationRowsInWorker(scope, query) { const capturedQuery = structuredClone(query); if (scope.storePath && isIncognitoOpenClawAgentSqlitePath(scope.storePath, scope)) return Promise.resolve(selectConversationRows(scope, capturedQuery)); return withConversationRead(scope, ({ reader, database }) => reader.readConversations({ query: capturedQuery, env: database.env })); } function pinConversationDatabaseScope(input) { const env = { ...input.env ?? process.env }; env.OPENCLAW_STATE_DIR = resolveStateDir(env); const options = input.databaseAgentId && input.storePath ? { agentId: input.databaseAgentId, path: input.storePath, env } : toDatabaseOptions(resolveSqliteReadScope({ ...input, env })); const storePath = resolveOpenClawAgentSqlitePath(options); return { options: { ...options, path: storePath }, scope: { ...input, databaseAgentId: options.agentId, storePath, env } }; } /** Keep the logical agent and physical store fixed while its synchronous write waits. */ function runConversationDatabaseWrite(input, operation) { const { options, scope } = pinConversationDatabaseScope(input); return withOpenClawAgentDatabaseWrite(options, () => operation(scope)); } function selectConversationRows(scope, options = {}) { const resolved = resolveSqliteReadScope({ agentId: scope.agentId, ...scope.env ? { env: scope.env } : {}, ...scope.storePath ? { storePath: scope.storePath } : {} }); const databaseOptions = toDatabaseOptions(resolved); const readRows = (database) => selectConversationRowsFromDatabase(database, options); const held = getOpenClawAgentDatabaseIfOpen(databaseOptions); if (held?.db.isTransaction) return readRows(held); const read = withOpenClawAgentDatabaseReadOnly(readRows, databaseOptions); return read.found ? read.value : []; } /** Catalogs routable addresses in the existing agent writer without creating sessions. */ async function registerConversationAddresses(scope, identities, discoveredAt = Date.now(), selectEligible = (values) => values.map(() => true), query) { if (identities.length === 0) return; const { options } = pinConversationDatabaseScope(scope); const input = { identities: structuredClone(identities), discoveredAt, query: query && structuredClone(query) }; const selectCurrent = () => { const selected = selectEligible(input.identities); if (selected.length !== input.identities.length) throw new Error("Conversation route owner returned an incomplete eligibility selection"); return selected; }; const assertCurrent = () => { if (!selectCurrent().every(Boolean)) throw new Error("Conversation route ownership changed during discovery"); }; const source = { assertCurrent() {}, createAdmission(binding) { return withConversationPublication(() => ({ nativeLocations: binding.nativeLocations, admission: createSqliteWorkerOperationAdmission((request, grant) => { binding.authorize(request); if (request.stage === "transaction" || request.stage === "commit") assertCurrent(); if (!grant()) throw new Error("Conversation registration authority expired"); }, binding.attachment) }), () => binding.assertCurrent(), () => execution.captureGenerationClaim(), () => [...new Set(input.identities.map((identity) => JSON.stringify(["catalogue", identity.conversationRef])))]); } }; const execution = captureOpenClawAgentDatabaseExecution(options); try { return await runOpenClawAgentWorkerWrite(options, async () => { const eligible = selectCurrent(); input.identities = input.identities.filter((_, index) => eligible[index]); if (input.identities.length === 0) return; if (!execution.capturePreparedGenerationClaim()) await execution.prepare(source); return await execution.runExisting(source, (worker) => worker.execute({ type: "conversation.register", input })); }); } finally { await execution.release(); } } /** Initiate under the writer grant; settle network work after releasing its transaction and FIFO. */ async function withConversationAuthority(scope, query, select) { const { options } = pinConversationDatabaseScope(scope); const input = structuredClone(query); const execution = captureOpenClawAgentDatabaseExecution(options); let consumed; try { try { await runOpenClawAgentWorkerWrite(options, async () => { const settled = await execution.runExisting({ assertCurrent() {}, createAdmission(binding) { return () => ({ nativeLocations: binding.nativeLocations, admission: createSqliteWorkerOperationAdmission((request, grant) => { binding.authorize(request); let consume; if (request.stage === "commit") { const publication = isRecord(request.facts) && request.facts.publication; if (consumed || !isRecord(publication) || publication.kind !== "conversation-authority" || !isRecord(publication.facts)) throw new Error("Conversation authority omitted its transaction facts"); const facts = publication.facts; const initiate = select(facts); consume = () => { consumed = new Promise((resolve) => { resolve(initiate()); }).then((value) => ({ ok: true, value }), (error) => ({ ok: false, error })); }; } if (!grant(consume)) throw new Error("Conversation read authority expired"); }, binding.attachment) }); } }, (worker) => worker.execute({ type: "conversation.authority", input })); if (!consumed && settled !== void 0) throw new Error("Conversation authority omitted its transaction grant"); }); } finally { await execution.release(); } } catch (cause) { if (!consumed) throw cause; await consumed; const error = new SqliteWorkerError("Conversation authority settlement failed after dispatch initiation", "outcome-unknown"); error.cause = cause; throw error; } if (!consumed) return await select({ operation: void 0, conversation: void 0 })(); const outcome = await consumed; if (!outcome.ok) throw outcome.error; return outcome.value; } /** Lists stable external addresses for one agent, newest activity first. */ function listConversations(scope, options = {}) { return selectConversationRowsInWorker(scope, options); } async function readConversation(scope, conversationRef) { return (await selectConversationRowsInWorker(scope, { conversationRef, limit: 1 }))[0]; } /** Reads only an authoritative association on an address's current session window. */ function resolveCurrentConversationSession(scope, conversationRef, currentSession) { const [conversation] = selectConversationRows(scope, { conversationRef, currentBindingOnly: true, currentSession, limit: 1 }); return conversation?.sessionKey && conversation.sessionId ? { sessionKey: conversation.sessionKey, sessionId: conversation.sessionId } : void 0; } /** Reads only the primary address bound to this exact current session window. */ async function resolveCurrentSessionPrimaryConversation(scope) { const [conversation] = await selectConversationRowsInWorker(scope, { primarySession: { sessionId: scope.sessionId, sessionKey: scope.sessionKey } }); return conversation?.sessionId === scope.sessionId && conversation.sessionKey === scope.sessionKey ? conversation : void 0; } //#endregion export { registerConversationAddresses as a, resolveCurrentSessionPrimaryConversation as c, readConversation as i, runConversationDatabaseWrite as l, pinConversationDatabaseScope as n, resolveConversationRegistryScope as o, prepareConversationRegistryScope as r, resolveCurrentConversationSession as s, listConversations as t, withConversationAuthority as u };