openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
266 lines (265 loc) • 14.3 kB
JavaScript
import { d as asPositiveSafeInteger } from "./number-coercion-CLj0HTDM.js";
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js";
import "./io-bdCzpGWJ.js";
import { M as isSessionTranscriptProjectionUnavailableError } from "./session-accessor-YsytfDtG.js";
import { a as listSessionEntriesReadOnly, u as loadSessionEntryReadOnly, v as resolveSessionKeyBySessionId } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { n as parseSqliteSessionFileMarker } from "./legacy-sqlite-marker-COPKCuIN.js";
import { a as readSessionMessageCountAsync, b as projectSessionMessagePayload, i as readSessionMessageByIdAsync, w as projectChatDisplayMessage } from "./session-transcript-readers-CYDRQsH5.js";
import { o as loadGatewaySessionRow } from "./session-utils-list-B0k8KJn5.js";
import { n as resolveSessionSubscriptionKeys, t as resolveSessionSubscriptionKey } from "./session-subscription-keys-BwUDpvzj.js";
import { r as resolveSessionEventAgentScope, t as resolvePrivateSessionEventBroadcastScope } from "./session-request-agent-CCRSEGCB.js";
import { i as loadGatewaySessionEntryReadOnly } from "./session-utils-store-CInT2loy.js";
import "./session-utils-Cai0_C6U.js";
import { n as readTranscriptDisplayPosition } from "./transcript-display-position-3BMmjSOO.js";
import { n as buildGatewaySessionSnapshot } from "./session-event-payload-COi33-eM.js";
import { i as resolveVisibleActiveSessionRunState } from "./session-active-runs-CCNuJW7B.js";
import { t as hasSessionChangeReceivers } from "./session-change-receivers-BH6Ki2zl.js";
import path from "node:path";
//#region src/gateway/server-session-events.ts
function readTranscriptUpdateLifecycleOwner(update) {
const marker = parseSqliteSessionFileMarker(update.sessionFile);
const sessionKey = normalizeOptionalString(update.target?.sessionKey) ?? normalizeOptionalString(update.sessionKey) ?? (marker ? resolveSessionKeyBySessionId(marker) : void 0);
if (!sessionKey) return;
const agentId = normalizeOptionalString(update.target?.agentId) ?? normalizeOptionalString(update.agentId) ?? marker?.agentId;
const sessionId = normalizeOptionalString(update.target?.sessionId) ?? normalizeOptionalString(update.sessionId) ?? marker?.sessionId;
const storePath = normalizeOptionalString(update.target?.storePath) ?? marker?.storePath;
const entry = storePath ? loadSessionEntryReadOnly({
agentId,
sessionKey,
storePath
}) : loadGatewaySessionEntryReadOnly(sessionKey, agentId ? { agentId } : void 0)?.entry;
if (!entry || sessionId && entry.sessionId !== sessionId) return;
const lifecycleRevision = normalizeOptionalString(entry.lifecycleRevision);
return lifecycleRevision ? { lifecycleRevision } : {};
}
/** Creates a serialized transcript-update broadcaster for session websocket clients. */
function createTranscriptUpdateBroadcastHandler(params) {
const broadcastQueues = /* @__PURE__ */ new Map();
return (update) => {
const lifecycleRevision = normalizeOptionalString(update.lifecycleRevision) ?? (update.message !== void 0 ? readTranscriptUpdateLifecycleOwner(update)?.lifecycleRevision : void 0);
const queuedUpdate = lifecycleRevision ? {
...update,
lifecycleRevision
} : update;
const legacyMarker = parseSqliteSessionFileMarker(update.sessionFile);
const sessionKey = normalizeOptionalString(update.target?.sessionKey) ?? normalizeOptionalString(update.sessionKey) ?? (legacyMarker ? resolveSessionKeyBySessionId(legacyMarker) : void 0);
const agentId = normalizeOptionalString(update.target?.agentId) ?? normalizeOptionalString(update.agentId) ?? legacyMarker?.agentId;
const agentScope = sessionKey ? resolveSessionEventAgentScope(getRuntimeConfig(), sessionKey, agentId) : void 0;
if (agentScope === null) return Promise.resolve();
const laneKey = sessionKey && agentScope?.[1] ? resolveSessionSubscriptionKey(sessionKey, agentScope[1]) : sessionKey ?? normalizeOptionalString(update.sessionFile) ?? "";
const task = (broadcastQueues.get(laneKey) ?? Promise.resolve()).then(() => handleTranscriptUpdateBroadcast(params, queuedUpdate, agentScope));
const settled = task.then(() => void 0, () => void 0);
broadcastQueues.set(laneKey, settled);
settled.then(() => {
if (broadcastQueues.get(laneKey) === settled) broadcastQueues.delete(laneKey);
});
return task;
};
}
async function handleTranscriptUpdateBroadcast(params, update, capturedAgentScope) {
const legacyMarker = parseSqliteSessionFileMarker(update.sessionFile);
const targetAgentId = normalizeOptionalString(update.target?.agentId);
const targetSessionId = normalizeOptionalString(update.target?.sessionId);
const targetSessionKey = normalizeOptionalString(update.target?.sessionKey);
const suppliedSessionKey = normalizeOptionalString(update.sessionKey);
const candidateSessionKey = targetSessionKey ?? suppliedSessionKey;
const targetKeyAgentId = parseAgentSessionKey(candidateSessionKey)?.agentId;
const targetStorePath = normalizeOptionalString(update.target?.storePath);
const completeTarget = Boolean(targetAgentId && targetSessionId && targetSessionKey && targetStorePath);
const markerSessionKey = legacyMarker && !completeTarget ? resolveSessionKeyBySessionId(legacyMarker) : void 0;
const markerMatches = legacyMarker && !completeTarget ? listSessionEntriesReadOnly({
agentId: legacyMarker.agentId,
storePath: legacyMarker.storePath
}).filter(({ entry }) => entry.sessionId === legacyMarker.sessionId) : [];
const candidateKeyEntry = candidateSessionKey && legacyMarker && !completeTarget ? loadSessionEntryReadOnly({
agentId: legacyMarker.agentId,
sessionKey: candidateSessionKey,
storePath: legacyMarker.storePath
}) : void 0;
if (targetKeyAgentId && targetAgentId && targetKeyAgentId !== targetAgentId) return;
if (legacyMarker && !completeTarget && (targetAgentId && targetAgentId !== legacyMarker.agentId || targetSessionId && targetSessionId !== legacyMarker.sessionId && candidateKeyEntry?.sessionId !== legacyMarker.sessionId || targetKeyAgentId && targetKeyAgentId !== legacyMarker.agentId || candidateSessionKey && (candidateKeyEntry && candidateKeyEntry.sessionId !== legacyMarker.sessionId || !candidateKeyEntry && markerMatches.length > 0) || targetStorePath && path.resolve(targetStorePath) !== path.resolve(legacyMarker.storePath))) return;
const compatibleLegacyMarker = completeTarget ? void 0 : legacyMarker;
const sessionKey = compatibleLegacyMarker ? candidateKeyEntry?.sessionId === compatibleLegacyMarker.sessionId || !candidateKeyEntry && markerMatches.length === 0 ? candidateSessionKey : markerSessionKey : candidateSessionKey;
if (!sessionKey) return;
const agentScope = capturedAgentScope ?? resolveSessionEventAgentScope(getRuntimeConfig(), sessionKey, compatibleLegacyMarker?.agentId ?? targetAgentId ?? update.agentId);
if (!agentScope) return;
const [eventAgentId, routingAgentId, compatibilityOwnerAgentId] = agentScope;
const privateBroadcastScope = resolvePrivateSessionEventBroadcastScope(sessionKey, agentScope);
const connIds = /* @__PURE__ */ new Set();
for (const connId of params.sessionEventSubscribers.getAll()) connIds.add(connId);
const broadcastKeys = routingAgentId ? resolveSessionSubscriptionKeys(sessionKey, routingAgentId, compatibilityOwnerAgentId) : [sessionKey];
for (const broadcastKey of broadcastKeys) for (const connId of params.sessionMessageSubscribers.get(broadcastKey)) connIds.add(connId);
if (connIds.size === 0) {
if (!hasSessionChangeReceivers(connIds) || update.message !== void 0 && projectChatDisplayMessage(update.message)) return;
}
const lifecycleRevision = normalizeOptionalString(update.lifecycleRevision);
if (!eventAgentId && !compatibilityOwnerAgentId && !parseAgentSessionKey(sessionKey)) {
if (lifecycleRevision) {
const currentLifecycleOwner = readTranscriptUpdateLifecycleOwner(update);
if (!currentLifecycleOwner || currentLifecycleOwner.lifecycleRevision && currentLifecycleOwner.lifecycleRevision !== lifecycleRevision) return;
}
params.broadcastToConnIds("sessions.changed", {
sessionKey,
phase: "message",
ts: Date.now()
}, connIds, {
...privateBroadcastScope,
dropIfSlow: true
});
return;
}
let message = update.message;
let messageSeq = asPositiveSafeInteger(update.messageSeq);
let transcriptPosition;
if (message !== void 0 && update.messageId && completeTarget && targetSessionId) try {
const stored = await readSessionMessageByIdAsync({
agentId: targetAgentId,
sessionId: targetSessionId,
sessionKey,
storePath: targetStorePath
}, update.messageId);
message = stored.message;
messageSeq = stored.seq;
transcriptPosition = readTranscriptDisplayPosition(asOptionalRecord(asOptionalRecord(message)?.["__openclaw"])?.transcriptPosition);
} catch (error) {
if (!isSessionTranscriptProjectionUnavailableError(error)) throw error;
message = void 0;
}
else if (message !== void 0 && messageSeq === void 0) {
const updateStorePath = targetStorePath ?? compatibleLegacyMarker?.storePath;
const fallbackTarget = updateStorePath ? {
entry: loadSessionEntryReadOnly({
agentId: routingAgentId,
sessionKey,
storePath: updateStorePath
}),
storePath: updateStorePath
} : loadGatewaySessionEntryReadOnly(sessionKey, { agentId: routingAgentId });
const entry = fallbackTarget?.entry;
const messageSessionId = compatibleLegacyMarker?.sessionId ?? normalizeOptionalString(update.target?.sessionId) ?? entry?.sessionId;
const storePath = updateStorePath ?? fallbackTarget?.storePath;
messageSeq = messageSessionId ? asPositiveSafeInteger(await readSessionMessageCountAsync({
agentId: update.target?.agentId ?? routingAgentId,
sessionEntry: entry,
sessionId: messageSessionId,
sessionKey,
storePath
})) : void 0;
}
if (lifecycleRevision) {
const currentLifecycleOwner = readTranscriptUpdateLifecycleOwner(update);
if (!currentLifecycleOwner || currentLifecycleOwner.lifecycleRevision && currentLifecycleOwner.lifecycleRevision !== lifecycleRevision) return;
}
const sessionRow = loadGatewaySessionRow(sessionKey, {
agentId: routingAgentId,
transcriptUsageMaxBytes: 65536
});
const activeRunState = sessionRow && (sessionRow.key !== "global" || routingAgentId !== void 0 || compatibilityOwnerAgentId) ? resolveVisibleActiveSessionRunState({
context: params,
requestedKey: sessionKey,
canonicalKey: sessionRow.key,
sessionId: sessionRow.sessionId,
...routingAgentId ? { agentId: routingAgentId } : {},
defaultAgentId: compatibilityOwnerAgentId
}) : null;
const sessionSnapshot = buildGatewaySessionSnapshot({
sessionRow,
agentId: eventAgentId,
includeSession: true,
activeRunState
});
if (message === void 0) {
params.broadcastToConnIds("sessions.changed", {
sessionKey,
...eventAgentId ? { agentId: eventAgentId } : {},
phase: "message",
ts: Date.now(),
...sessionSnapshot
}, connIds);
return;
}
const projected = projectSessionMessagePayload({
sessionKey,
...eventAgentId ? { agentId: eventAgentId } : {},
message,
transcriptPosition,
...typeof update.messageId === "string" ? { messageId: update.messageId } : {},
...messageSeq !== void 0 ? { messageSeq } : {},
...update.runId ? { runId: update.runId } : {},
sessionSnapshot
});
if (projected.payload) {
params.broadcastToConnIds("session.message", projected.payload, connIds);
return;
}
const sessionEventConnIds = params.sessionEventSubscribers.getAll();
if (!hasSessionChangeReceivers(sessionEventConnIds)) return;
params.broadcastToConnIds("sessions.changed", {
sessionKey,
...eventAgentId ? { agentId: eventAgentId } : {},
phase: "message",
ts: Date.now(),
...typeof update.messageId === "string" ? { messageId: update.messageId } : {},
...messageSeq !== void 0 ? { messageSeq } : {},
...sessionSnapshot
}, sessionEventConnIds, { dropIfSlow: true });
}
/** Creates a lifecycle-event broadcaster for session list refreshes. */
function createLifecycleEventBroadcastHandler(params) {
return (event) => {
const connIds = params.sessionEventSubscribers.getAll();
if (!hasSessionChangeReceivers(connIds)) return;
const agentScope = resolveSessionEventAgentScope(getRuntimeConfig(), event.sessionKey, normalizeOptionalString(event.agentId), true);
if (!agentScope) return;
const [eventAgentId, routingAgentId, compatibilityOwnerAgentId] = agentScope;
const broadcastOptions = {
...resolvePrivateSessionEventBroadcastScope(event.sessionKey, agentScope),
dropIfSlow: true
};
if (event.reason === "delete" || !routingAgentId || !eventAgentId && !compatibilityOwnerAgentId) {
params.broadcastToConnIds("sessions.changed", {
sessionKey: event.sessionKey,
...eventAgentId ? { agentId: eventAgentId } : {},
reason: event.reason,
ts: Date.now()
}, connIds, broadcastOptions);
return;
}
const sessionRow = loadGatewaySessionRow(event.sessionKey, { agentId: routingAgentId });
const activeRunState = sessionRow && (sessionRow.key !== "global" || routingAgentId) ? resolveVisibleActiveSessionRunState({
context: params,
requestedKey: event.sessionKey,
canonicalKey: sessionRow.key,
sessionId: sessionRow.sessionId,
...routingAgentId ? { agentId: routingAgentId } : {},
defaultAgentId: compatibilityOwnerAgentId
}) : null;
params.broadcastToConnIds("sessions.changed", {
sessionKey: event.sessionKey,
...eventAgentId ? { agentId: eventAgentId } : {},
reason: event.reason,
parentSessionKey: event.parentSessionKey,
label: event.label,
displayName: event.displayName,
ts: Date.now(),
...buildGatewaySessionSnapshot({
sessionRow,
agentId: eventAgentId,
label: event.label,
displayName: event.displayName,
parentSessionKey: event.parentSessionKey,
activeRunState
}),
...event.swarmGroupId ? {
swarmGroupId: event.swarmGroupId,
kind: event.kind,
text: event.text
} : {}
}, connIds, { dropIfSlow: true });
};
}
//#endregion
export { createLifecycleEventBroadcastHandler, createTranscriptUpdateBroadcastHandler };