openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
493 lines (492 loc) • 21.2 kB
JavaScript
import { a as extractErrorCode, l as readErrorName, r as collectErrorGraphCandidates } from "./error-coercion-W9BtigRm.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { i as resolveRuntimeWorkerUrl } from "./runtime-worker-url-CFqA9Dvi.mjs";
import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs";
import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs";
import { d as readDatabasePathIdentitySync, r as assertExistingDatabaseIdentity } from "./sqlite-worker-identity-BRNRpRyc.mjs";
import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs";
import { n as runtimeProcessEntrypoints } from "./runtime-process-entrypoints-De68tE_W.mjs";
import { c as resolveSessionStorePathCore } from "./paths-Bonddxfk.mjs";
import { a as readOpenClawAgentDatabaseIdentity } from "./openclaw-agent-db-identity-BlmER9-X.mjs";
import { i as isIncognitoOpenClawAgentSqlitePath, l as resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths-BbmZlEP5.mjs";
import { n as withOpenClawAgentDatabaseReadOnly } from "./openclaw-agent-db-readonly-DAqV3tqr.mjs";
import { u as isSqliteWorkerError } from "./sqlite-worker-contract-C51cJHzg.mjs";
import { i as sessionChanges } from "./session-row-changes-Dj2yVWo-.mjs";
import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-BzUE_dOh.mjs";
import { c as runOpenClawAgentWriteTransaction, d as withOpenClawAgentDatabaseRuntime, i as getOpenClawAgentDatabaseIfOpen, u as withOpenClawAgentDatabaseAsync } from "./openclaw-agent-db-DWI9Bgur.mjs";
import { n as runOpenClawAgentWriteAdmission, t as runOpenClawAgentWorkerWrite } from "./openclaw-agent-write-admission-CXJiE5VB.mjs";
import { t as captureSessionTranscriptStorageEnvironment } from "./transcript-target-binding-DpcuSIFy.mjs";
import { o as resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target-Dd3VSz82.mjs";
import { i as captureExternalSessionCommitGuard, l as releaseSessionSourceAuthorities, s as prepareSessionSourceAuthority, t as acceptSessionSourceValidation } from "./session-source-authority-h1UO3u0I.mjs";
import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs";
import "./io-BGddrCVX.mjs";
import { n as openOpenClawAgentSqliteWorkerStore } from "./openclaw-agent-worker-store-BE9hhRoh.mjs";
import { r as BoardValidationError } from "./board-layout-6DyK3jbx.mjs";
import { r as captureIncognitoSessionOperation } from "./session-incognito-binding-Dp6RTei2.mjs";
import { h as targetDiscoveryLane } from "./session-transcript-worker-resources-cnBmyJ1M.mjs";
import { c as withSessionHistoryWorkerDatabase } from "./session-transcript-worker-runtime-BdmdicNg.mjs";
import { a as putBoardWidgetInDatabase, c as readBoardWidgetDocument, i as hasBoardSession, l as normalizeBoardWidgetPutParams, n as ensureBoardSchema, r as grantBoardWidgetInDatabase, s as readBoardSnapshotWithHtmlViewMetadata, t as applyBoardOpsToDatabase } from "./sqlite-board-store.kernel-DgDGNaxo.mjs";
import { t as captureSessionEntryNativeMutationWitness } from "./session-entry-read-ordered-D14zdHVq.mjs";
import { i as resolveSessionStoreIdentity } from "./session-store-key-Dvf3CKvN.mjs";
import { AsyncLocalStorage } from "node:async_hooks";
import { randomBytes } from "node:crypto";
import { isPromise } from "node:util/types";
//#region src/boards/sqlite-board-authority.ts
const log = createSubsystemLogger("boards/store");
function reportBoardCleanupFailure(error) {
try {
log.warn(`Board publication completed before cleanup failed: ${formatErrorMessage(error)}`);
} catch {}
}
async function prepareBoardSourceAuthority(assertion, identity) {
const source = await prepareSessionSourceAuthority(captureExternalSessionCommitGuard(assertion));
return {
...source,
nativeSource: source.nativeSource || source.checks.some(({ predicate }) => typeof predicate.source.databaseIdentity !== "string" || `file:${predicate.source.databaseIdentity}` !== identity.key),
assertAdmission(request) {
const validation = isRecord(request.facts) && request.facts.boardSourceValidation;
if (validation) acceptSessionSourceValidation(source, validation);
return request;
}
};
}
//#endregion
//#region src/boards/sqlite-board-store.ts
function restoreBoardError(error) {
if (error instanceof Error && error.name === "BoardValidationError" && "code" in error && (error.code === "conflict" || error.code === "invalid_operation" || error.code === "not_found")) return new BoardValidationError(error.code, error.message);
return error;
}
/** Invalidation never grants retries; transported post-execution failures are plain Errors. */
function hasUnknownBoardWriteOutcome(error) {
return collectErrorGraphCandidates(error, (current) => current instanceof AggregateError ? [current.cause] : []).some((current) => isSqliteWorkerError(current, "outcome-unknown") || current instanceof Error && readErrorName(current) === "SqliteWorkerError" && extractErrorCode(current) === "outcome-unknown");
}
var SqliteBoardStore = class {
constructor(options) {
this.options = options;
}
assertTargetCurrent(target, resolved) {
if (resolved.assertCurrent) {
resolved.assertCurrent();
return;
}
const current = this.options.resolveSession(target);
if (current.agentId !== resolved.agentId || current.path !== resolved.path || current.sessionKey !== resolved.sessionKey) throw new BoardValidationError("invalid_operation", "board session changed; retry");
}
requireExistingSession(resolved, env) {
const result = withOpenClawAgentDatabaseReadOnly((database) => hasBoardSession(database, resolved.sessionKey), {
agentId: resolved.agentId,
...resolved.path ? { path: resolved.path } : {},
env
});
if (!result.found || !result.value) throw new BoardValidationError("not_found", `board session not found: ${resolved.sessionKey}`);
}
write(target, options, operationLabel, native, worker, actorWrite, prepare) {
const resolved = this.options.resolveSession(target);
const env = cloneEnvWithPlatformSemantics(this.options.env ?? process.env);
env.OPENCLAW_STATE_DIR = resolveStateDir(env);
const databaseOptions = {
agentId: resolved.agentId,
env,
path: resolveOpenClawAgentSqlitePath({
...resolved,
env
})
};
const assertCurrent = () => {
options?.assertCurrent?.();
this.assertTargetCurrent(target, resolved);
};
if (resolved.incognito) {
const { actor, authority } = resolved.incognito;
if (actor.agentId !== resolved.agentId || actor.path !== databaseOptions.path) throw new Error("Board target differs from its captured incognito actor");
const currentAuthority = {
assertCurrent() {
assertCurrent();
authority.assertCurrent();
actor.assertCurrent();
},
authorize: (stage, facts) => authority.authorize?.(stage, facts)
};
currentAuthority.assertCurrent();
const execute = async (writeAuthority) => {
try {
const committed = await actorWrite(actor, writeAuthority, resolved.sessionKey);
sessionChanges.emitBatch(committed.changes);
return committed.value;
} catch (error) {
sessionChanges.emit({
sessionKey: resolved.sessionKey,
storePath: actor.path
});
throw restoreBoardError(error);
}
};
return actor.sessions.withSharedState(async () => {
if (!prepare) return execute(currentAuthority);
const source = await actor.sessions.read(currentAuthority, { sessionKey: resolved.sessionKey });
if (!source.entry) throw new BoardValidationError("not_found", `board session not found: ${resolved.sessionKey}`);
await prepare();
source.claim.assertCurrent();
const expected = source.entry;
const writeAuthority = {
...currentAuthority,
authorize(stage, facts) {
if (facts.sharing?.entry?.sessionId !== expected.sessionId || facts.sharing.entry.lifecycleRevision !== expected.lifecycleRevision) throw new BoardValidationError("invalid_operation", "board session changed; retry");
return currentAuthority.authorize?.(stage, facts);
}
};
return execute(writeAuthority);
}).then((result) => {
currentAuthority.assertCurrent();
actor.assertReadable();
return result;
});
}
const incognito = isIncognitoOpenClawAgentSqlitePath(databaseOptions.path, databaseOptions);
const assertOpenCurrent = () => {
assertCurrent();
if (incognito) this.requireExistingSession({
...resolved,
path: databaseOptions.path
}, env);
};
assertOpenCurrent();
return runOpenClawAgentWriteAdmission(databaseOptions, async (identity, assertDatabaseCurrent) => {
let expectedSession;
if (!incognito) {
const source = await withSessionHistoryWorkerDatabase(databaseOptions, (reader) => reader.readExactEntries({
env,
sessionKeys: [resolved.sessionKey],
projection: "exact",
snapshotFields: [],
expectedIdentity: identity
}), targetDiscoveryLane);
assertDatabaseCurrent();
assertOpenCurrent();
const entry = source.entries[0]?.entry;
if (!entry) throw new BoardValidationError("not_found", `board session not found: ${resolved.sessionKey}`);
expectedSession = {
sessionId: entry.sessionId,
lifecycleRevision: entry.lifecycleRevision
};
}
const authority = await prepareBoardSourceAuthority(options?.assertCurrent, identity);
const nativeSource = incognito || authority.nativeSource;
const assertPreparedCurrent = () => {
assertDatabaseCurrent();
this.assertTargetCurrent(target, resolved);
if (nativeSource) assertOpenCurrent();
else authority.assertCurrent();
};
return (nativeSource ? withOpenClawAgentDatabaseAsync : withOpenClawAgentDatabaseRuntime)(databaseOptions, async (database) => {
if (prepare) await prepare();
assertPreparedCurrent();
if (prepare && getOpenClawAgentDatabaseIfOpen(databaseOptions) !== database) throw new BoardValidationError("invalid_operation", "board database closed or changed; retry");
if (nativeSource || typeof readOpenClawAgentDatabaseIdentity(database).identity === "symbol") {
ensureBoardSchema(database);
return runOpenClawAgentWriteTransaction((current) => {
assertPreparedCurrent();
if (expectedSession && !hasBoardSession(current, resolved.sessionKey, expectedSession)) throw new BoardValidationError("invalid_operation", "board session changed; retry");
const value = native(current, resolved.sessionKey);
assertPreparedCurrent();
return value;
}, databaseOptions, { operationLabel });
}
const publication = await openOpenClawAgentSqliteWorkerStore(databaseOptions, database.db, {
moduleUrl: resolveRuntimeWorkerUrl(runtimeProcessEntrypoints.boardStore),
input: expectedSession ? {
agentId: databaseOptions.agentId,
sessionKey: resolved.sessionKey,
expectedSession,
sources: authority.checks.map(({ predicate }) => predicate)
} : void 0,
assertAdmission: authority.assertAdmission
});
let outcome;
try {
outcome = {
ok: true,
value: await publication.run(async (scope) => {
let committed;
try {
committed = await worker(scope, resolved.sessionKey);
} catch (error) {
if (hasUnknownBoardWriteOutcome(error)) sessionChanges.emit({
sessionKey: resolved.sessionKey,
storePath: database.path
});
throw error;
}
sessionChanges.emitBatch(committed.changes);
return committed.value;
}, assertPreparedCurrent)
};
} catch (error) {
outcome = {
ok: false,
error: restoreBoardError(error)
};
}
let cleanup;
try {
await publication.close();
cleanup = {
ok: true,
value: void 0
};
} catch (error) {
cleanup = {
ok: false,
error
};
}
if (!outcome.ok) {
if (!cleanup.ok) throw new AggregateError([outcome.error, cleanup.error], "Board publication and cleanup failed", { cause: outcome.error });
throw outcome.error;
}
if (!cleanup.ok) reportBoardCleanupFailure(cleanup.error);
return outcome.value;
}, assertPreparedCurrent).then(async (value) => {
try {
await releaseSessionSourceAuthorities([authority]);
} catch (error) {
reportBoardCleanupFailure(error);
}
return value;
}, async (error) => {
await releaseSessionSourceAuthorities([authority], [error]);
throw error;
});
}, true);
}
async getSnapshot(target) {
return this.useSnapshot(target, (snapshot) => snapshot);
}
async getSnapshotWithHtmlViewMetadata(target) {
return this.consumeSnapshotWithHtmlViewMetadata(target, (snapshot) => snapshot);
}
async consumeRead(target, native, worker, actorRead, consume) {
const capturedTarget = { ...target };
const resolved = this.options.resolveSession(capturedTarget);
const env = captureSessionTranscriptStorageEnvironment(this.options.env ?? process.env);
const captured = {
agentId: resolved.agentId,
sessionKey: resolved.sessionKey,
env,
path: resolveOpenClawAgentSqlitePath({
...resolved,
env
})
};
const runInCallerContext = AsyncLocalStorage.snapshot();
const accept = (value) => {
this.assertTargetCurrent(capturedTarget, resolved);
const result = runInCallerContext(consume, value, captured.sessionKey);
if (isPromise(result)) result.catch(() => {});
return { value: result };
};
if (resolved.incognito) {
const { actor, authority } = resolved.incognito;
if (actor.agentId !== captured.agentId || actor.path !== captured.path) throw new Error("Board target differs from its captured incognito actor");
const currentAuthority = {
assertCurrent: () => {
this.assertTargetCurrent(capturedTarget, resolved);
authority.assertCurrent();
actor.assertCurrent();
},
authorize: (stage, facts) => authority.authorize?.(stage, facts)
};
const result = await actor.sessions.withSharedState(async () => {
const runInRetainedContext = AsyncLocalStorage.snapshot();
try {
const value = await actorRead(actor, currentAuthority, captured.sessionKey);
currentAuthority.assertCurrent();
actor.assertReadable();
return await runInRetainedContext(consume, value, captured.sessionKey);
} catch (error) {
throw restoreBoardError(error);
}
});
currentAuthority.assertCurrent();
actor.assertReadable();
return result;
}
if (isIncognitoOpenClawAgentSqlitePath(captured.path, captured)) return await (await runOpenClawAgentWorkerWrite(captured, async () => {
this.assertTargetCurrent(capturedTarget, resolved);
const read = withOpenClawAgentDatabaseReadOnly((database) => native(database, captured.sessionKey), captured);
return accept(read.found ? read.value : void 0);
})).value;
const identity = readDatabasePathIdentitySync(captured.path);
if (!identity.key.startsWith("file:")) return await (await runOpenClawAgentWorkerWrite(captured, async () => accept(void 0))).value;
return await (await withSessionHistoryWorkerDatabase({
...captured,
path: identity.canonicalPath,
requestedPaths: [captured.path]
}, (reader) => runOpenClawAgentWriteAdmission(captured, async () => {
this.assertTargetCurrent(capturedTarget, resolved);
const assertNativeCurrent = captureSessionEntryNativeMutationWitness([captured]);
const value = await worker(reader, captured.sessionKey, env, identity);
assertExistingDatabaseIdentity(captured.path, identity.key, identity.birthtime);
reader.assertCurrent();
assertNativeCurrent();
return accept(value);
}, true), targetDiscoveryLane).catch((error) => {
throw restoreBoardError(error);
})).value;
}
async useSnapshot(target, consume) {
return this.consumeSnapshotWithHtmlViewMetadata(target, ({ snapshot }) => consume(snapshot));
}
async useWidgetDocument(target, name, consume) {
return this.consumeWidgetDocument(target, name, consume);
}
consumeSnapshotWithHtmlViewMetadata(target, consume) {
return this.consumeRead(target, readBoardSnapshotWithHtmlViewMetadata, (reader, sessionKey, env, expectedIdentity) => reader.readBoardSnapshot({
sessionKey,
env,
expectedIdentity
}), (actor, authority, sessionKey) => actor.sessions.sideData(authority, {
type: "session.boards.readSnapshot",
input: { sessionKey }
}), (stored, sessionKey) => consume(stored ?? {
snapshot: {
sessionKey,
revision: 0,
tabs: [],
widgets: []
},
htmlViewMetadata: /* @__PURE__ */ new Map()
}));
}
async applyOps(target, ops, options) {
if (ops.length === 0) return this.getSnapshot(target);
const capturedOps = structuredClone(ops);
return this.write(target, options, "board.apply-ops", (database, sessionKey) => applyBoardOpsToDatabase(database, sessionKey, capturedOps), (scope, sessionKey) => scope.execute({
type: "boards.applyOps",
input: {
sessionKey,
ops: capturedOps
}
}), (actor, authority, sessionKey) => actor.sessions.sideData(authority, {
type: "session.boards.applyOps",
input: {
sessionKey,
ops: capturedOps
}
}));
}
async putWidget(params, options) {
const capturedParams = structuredClone(params);
const viewGeneration = randomBytes(16).toString("hex");
let preparedParams = capturedParams;
const content = capturedParams.content;
const resolveInteraction = options?.resolveMcpAppInteraction;
const prepare = content.kind === "mcp-app" && content.interactive && resolveInteraction ? async () => {
if (!await resolveInteraction()) preparedParams = {
...capturedParams,
content: {
...content,
interactive: false
},
declared: void 0
};
} : void 0;
return this.write(params, options, "board.put-widget", (database, sessionKey) => putBoardWidgetInDatabase(database, sessionKey, normalizeBoardWidgetPutParams(preparedParams, sessionKey), viewGeneration), (scope, sessionKey) => scope.execute({
type: "boards.putWidget",
input: {
sessionKey,
params: preparedParams,
viewGeneration
}
}), (actor, authority, sessionKey) => actor.sessions.sideData(authority, {
type: "session.boards.putWidget",
input: {
sessionKey,
params: preparedParams,
viewGeneration
}
}), prepare);
}
async grant(target, name, decision, revision, instanceId, options) {
return this.write(target, options, "board.grant-widget", (database, sessionKey) => grantBoardWidgetInDatabase(database, sessionKey, name, decision, revision, instanceId), (scope, sessionKey) => scope.execute({
type: "boards.grant",
input: {
sessionKey,
name,
decision,
revision,
instanceId
}
}), (actor, authority, sessionKey) => actor.sessions.sideData(authority, {
type: "session.boards.grant",
input: {
sessionKey,
name,
decision,
revision,
instanceId
}
}));
}
consumeWidgetDocument(target, name, consume, contentKind) {
return this.consumeRead(target, (database, sessionKey) => readBoardWidgetDocument(database, sessionKey, name, contentKind), (reader, sessionKey, env, expectedIdentity) => reader.readBoardWidgetDocument({
sessionKey,
name,
contentKind,
env,
expectedIdentity
}), (actor, authority, sessionKey) => actor.sessions.sideData(authority, {
type: "session.boards.readWidgetDocument",
input: {
sessionKey,
name,
contentKind
}
}), consume);
}
async readWidgetMcpApp(target, name) {
return this.consumeWidgetDocument(target, name, (document) => document && "descriptor" in document ? document : void 0, "mcp-app");
}
};
//#endregion
//#region src/gateway/board-store.ts
function captureGatewaySessionStoreScope(sessionKey, explicitAgentId) {
const cfg = getRuntimeConfig();
const { agentId, canonicalKey } = resolveSessionStoreIdentity({
cfg,
sessionKey,
agentId: explicitAgentId
});
return {
agentId,
storePath: resolveSessionStorePathCore(cfg.session?.store, { agentId }),
sessionKey: canonicalKey
};
}
function resolveGatewaySessionDatabase(sessionKey, explicitAgentId) {
const { agentId, storePath, sessionKey: canonicalKey } = captureGatewaySessionStoreScope(sessionKey, explicitAgentId);
const databaseTarget = resolveSqliteTargetFromSessionStorePath(storePath, { agentId });
return {
agentId: databaseTarget.agentId ?? agentId,
path: databaseTarget.path,
sessionKey: canonicalKey
};
}
const boardStore = new SqliteBoardStore({ resolveSession: ({ sessionKey, agentId }) => {
const scope = captureGatewaySessionStoreScope(sessionKey, agentId);
const incognito = captureIncognitoSessionOperation(scope);
return {
...incognito ? {
agentId: incognito.actor.agentId,
path: incognito.actor.path,
sessionKey: scope.sessionKey
} : resolveGatewaySessionDatabase(sessionKey, agentId),
incognito,
assertCurrent() {
const current = captureGatewaySessionStoreScope(sessionKey, agentId);
if (current.agentId !== scope.agentId || current.storePath !== scope.storePath || current.sessionKey !== scope.sessionKey) throw new BoardValidationError("invalid_operation", "board session changed; retry");
}
};
} });
//#endregion
export { captureGatewaySessionStoreScope as n, boardStore as t };