openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
403 lines (402 loc) • 17.8 kB
JavaScript
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 };