openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,548 lines • 65 kB
JavaScript
import { s as asFiniteNumber } from "./number-coercion-CLj0HTDM.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { n as createLazyPromise, r as createLazyPromiseLoader } from "./lazy-promise-DGqyc4Y4.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { h as redactToolPayloadText } from "./redact-BtvPPfTi.js";
import { D as tryResolveLegacyCompatibilityAgentId } from "./agent-scope-config-DcbEhP0R.js";
import "./legacy.default-agent-owner-BGwEdQRe.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { _ as onTrustedToolExecutionEvent } from "./diagnostic-events-Cwe92uV3.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import { n as resolvePersistedSessionStoreOwnerForKey } from "./session-store-owner-CR2Ag3xK.js";
import "./io-bdCzpGWJ.js";
import { p as onAgentRuntimeEvent, u as onAgentAuditEvent, y as createAgentRunStaleLifecycleError } from "./agent-events-CoxiItUi.js";
import { a as clearAgentRunContext, c as getAgentRunContext, l as getAgentRunContextOwnerStatus } from "./agent-run-registry-CKYKdfNd.js";
import { m as onGatewaySuspendAdmissionChange } from "./gateway-work-admission-R1IpuDim.js";
import { t as configureRuntimeActionDecisionSink } from "./runtime-action-decision-C4JNkXkP.js";
import { a as configureChannelAdmissionEvidenceCollection, i as configureChannelAdmissionDecisionSink } from "./admission-evidence-BW_2Woj4.js";
import { d as readSessionTranscriptBoundedMessageTailPage } from "./session-accessor-YsytfDtG.js";
import { m as onSessionLifecycleEvent } from "./session-history-eviction-C4srftLJ.js";
import { r as onInternalSessionTranscriptUpdate } from "./transcript-events-wgPr4ILk.js";
import { a as resolveStoredSessionKeyForAgentStore } from "./session-store-key-8xEjWSNi.js";
import { i as onUserProfilesChanged } from "./user-profiles-owner-DdMEsuGt.js";
import { t as configureExecutionIdentityAdmissionSink } from "./execution-identity-admission-Ch95Lxwv.js";
import { T as isTerminalTaskStatus } from "./task-registry.store.sqlite-BK18rNh0.js";
import { c as isDefinitiveRunLifecycle, t as AGENT_RUN_TERMINAL_RETRY_GRACE_MS } from "./agent-run-terminal-outcome-CigeY75d.js";
import { n as isExecutionIdentityCollectionEnabled, r as resolveAuditMessageMode, t as isAuditLedgerEnabled } from "./audit-config-BKFiXlHH.js";
import { r as onTrustedMessageAuditEvent } from "./message-audit-events-DGtoPYvb.js";
import { t as configureExecutionDecisionWorkSink } from "./execution-decision-work-BR8wVcpq.js";
import { n as resolveSessionSubscriptionKeys, t as resolveSessionSubscriptionKey } from "./session-subscription-keys-BwUDpvzj.js";
import { i as tryResolveSessionCompatibilityOwnerAgentId } from "./session-request-agent-CCRSEGCB.js";
import { i as loadGatewaySessionEntryReadOnly } from "./session-utils-store-CInT2loy.js";
import "./session-utils-Cai0_C6U.js";
import { t as extractStoredAssistantText } from "./chat-history-text-C2UOMgRe.js";
import { n as resolveUtilityModelRefForAgent } from "./utility-model-C0ozcQ73.js";
import { t as configureMessageActionDecisionSink } from "./message-action-decision-o6mn6Seg.js";
import { t as stripMarkdown } from "./strip-markdown-B6s98ZUi.js";
import { c as removeChatAbortControllerEntry, p as markChatAbortTerminalPersistenceError } from "./chat-abort-qLn3eFOg.js";
import { i as terminalHealthFor, n as flushSessionActivityAssistantNote, r as noteSessionActivityEvent, t as createSessionActivityNoteState } from "./session-activity-notes-CEJKpygi.js";
import { a as sanitizeProgressStatusText } from "./progress-draft-status-text-DAFR4vkg.js";
import { r as onHeartbeatEvent } from "./heartbeat-events-bg9alNGv.js";
import { a as persistGatewaySessionLifecycleEvent } from "./session-lifecycle-state-DCd4j8Bd.js";
import { i as resolveVisibleActiveSessionRunState } from "./session-active-runs-CCNuJW7B.js";
import { n as onGatewaySessionReset } from "./session-reset-notifications-DgKdsPPS.js";
import { t as createAuditEventRecorder } from "./audit-recorder-BCaTSxYZ.js";
import { a as defaultPersistDigest, c as markSessionObserverRunSuperseded, d as rememberSessionObserverDisabledRun, f as rememberSessionObserverDormantRun, h as synthesizeSessionObserverTerminalDigest, i as defaultCompleteModel, l as normalizeSessionObserverModelOutput, m as sessionObserverScopeKey, n as buildSessionObserverPrompt, o as defaultPrepareModel, p as rememberSessionObserverRevisionFloor, r as createDormantSessionObserverRun, s as defaultReadSession, t as SESSION_OBSERVER_SYSTEM_PROMPT } from "./session-observer-model-D5MYTAfa.js";
import { i as resolveTaskRequesterSessionTarget, t as mapTaskSummary } from "./task-summary-CgTKm-QC.js";
import { n as createSessionCompanionAskRuntime } from "./session-companion-ask-NbxUgDmR.js";
//#region src/gateway/session-companion-context.ts
const CONTEXT_MAX_MESSAGES = 40;
const CONTEXT_MAX_BYTES = 24576;
const CONTEXT_MESSAGE_MAX_CHARS = 4e3;
const CONTEXT_READ_MAX_SCANNED_MESSAGES = 4096;
const CONTEXT_READ_MAX_BYTES = 1048576;
const CONTEXT_READ_PAGE_MESSAGES = 128;
function normalizeContextText(value) {
return truncateUtf16Safe(redactToolPayloadText(value).replace(/\s+/gu, " ").trim(), CONTEXT_MESSAGE_MAX_CHARS);
}
function extractUserText(message) {
if (!message || typeof message !== "object") return;
const content = message.content;
if (typeof content === "string") return normalizeContextText(content) || void 0;
if (!Array.isArray(content)) return;
return normalizeContextText(content.flatMap((block) => {
if (!block || typeof block !== "object" || block.type !== "text") return [];
const blockText = block.text;
return typeof blockText === "string" ? [blockText] : [];
}).join("\n")) || void 0;
}
function readMessageTimestamp(message) {
if (!message || typeof message !== "object") return 0;
const value = message.timestamp;
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
}
function appendContextMessages(events, messages) {
for (let index = events.length - 1; index >= 0 && messages.length < CONTEXT_MAX_MESSAGES; index--) {
const event = events[index]?.event;
if (!event || typeof event !== "object") continue;
const message = event.message;
if (!message || typeof message !== "object") continue;
const role = message.role;
const text = role === "assistant" ? normalizeContextText(extractStoredAssistantText(message) ?? "") : role === "user" ? extractUserText(message) : void 0;
if (text && (role === "assistant" || role === "user")) messages.push({
role,
text,
ts: readMessageTimestamp(message)
});
}
}
function selectContextMessages(messages) {
const selected = [];
let bytes = 2;
for (const message of messages) {
const messageBytes = Buffer.byteLength(JSON.stringify(message), "utf8") + 1;
if (bytes + messageBytes > CONTEXT_MAX_BYTES) break;
selected.push(message);
bytes += messageBytes;
}
return selected.toReversed();
}
async function readSessionCompanionContext(params) {
const loaded = loadGatewaySessionEntryReadOnly(params.sessionKey, { agentId: params.agentId });
const sessionId = loaded.entry?.sessionId?.trim();
if (!sessionId) return { kind: "missing" };
try {
const scope = {
agentId: params.agentId,
sessionId,
sessionKey: params.sessionKey,
storePath: loaded.storePath
};
if (params.signal?.aborted) return { kind: "unavailable" };
let offset = 0;
let rawBytes = 0;
let scannedMessages = 0;
let totalMessages = 0;
let stoppedAtOlderByteBoundary = false;
let snapshot;
const contextMessages = [];
while (contextMessages.length < CONTEXT_MAX_MESSAGES && scannedMessages < CONTEXT_READ_MAX_SCANNED_MESSAGES) {
const page = readSessionTranscriptBoundedMessageTailPage(scope, {
maxBytes: CONTEXT_READ_MAX_BYTES - rawBytes,
maxMessages: Math.min(CONTEXT_READ_PAGE_MESSAGES, CONTEXT_READ_MAX_SCANNED_MESSAGES - scannedMessages),
offset
});
if (params.signal?.aborted) return { kind: "unavailable" };
const pageSnapshot = {
activeLeafEntryId: page.activeLeafEntryId,
generation: page.snapshot.generation,
indexedSeq: page.snapshot.indexedSeq,
totalMessages: page.totalMessages
};
snapshot ??= pageSnapshot;
if (pageSnapshot.activeLeafEntryId !== snapshot.activeLeafEntryId || pageSnapshot.generation !== snapshot.generation || pageSnapshot.indexedSeq !== snapshot.indexedSeq || pageSnapshot.totalMessages !== snapshot.totalMessages) return { kind: "unavailable" };
totalMessages = page.totalMessages;
const pageIsPartial = page.newestContiguousEventCount !== page.scannedMessages;
const pageEvents = page.newestContiguousEventCount === page.events.length ? page.events : page.events.slice(page.events.length - page.newestContiguousEventCount);
rawBytes += page.serializedBytes;
scannedMessages += page.scannedMessages;
offset += page.scannedMessages;
appendContextMessages(pageEvents, contextMessages);
if (pageIsPartial) {
if (contextMessages.length === 0) return { kind: "unavailable" };
stoppedAtOlderByteBoundary = true;
break;
}
if (page.scannedMessages === 0 || offset >= totalMessages) break;
}
if (contextMessages.length < CONTEXT_MAX_MESSAGES && offset < totalMessages && !stoppedAtOlderByteBoundary) return { kind: "unavailable" };
const fence = readSessionTranscriptBoundedMessageTailPage(scope, {
maxBytes: 0,
maxMessages: 0,
offset: 0
});
if (params.signal?.aborted || !snapshot || fence.activeLeafEntryId !== snapshot.activeLeafEntryId || fence.snapshot.generation !== snapshot.generation || fence.snapshot.indexedSeq !== snapshot.indexedSeq || fence.totalMessages !== snapshot.totalMessages) return { kind: "unavailable" };
return {
kind: "ready",
context: {
empty: totalMessages === 0,
messages: selectContextMessages(contextMessages),
sessionId
}
};
} catch {
return { kind: "unavailable" };
}
}
const defaultSessionCompanionContextReader = {
currentSessionId: ({ agentId, sessionKey }) => loadGatewaySessionEntryReadOnly(sessionKey, { agentId }).entry?.sessionId?.trim() || void 0,
read: readSessionCompanionContext
};
//#endregion
//#region src/gateway/session-companion.ts
const SESSION_COMPANION_IDLE_TTL_MS = 72e5;
const SESSION_COMPANION_SWEEP_INTERVAL_MS = 6e5;
function createSessionCompanion(deps) {
const now = deps.now ?? Date.now;
const setIntervalFn = deps.setIntervalFn ?? setInterval;
const clearIntervalFn = deps.clearIntervalFn ?? clearInterval;
const threads = /* @__PURE__ */ new Map();
let disposed = false;
const askRuntime = createSessionCompanionAskRuntime({
...deps,
now,
threads,
isDisposed: () => disposed
});
const reset = (target, cancellation) => {
const sessionKey = target.sessionKey.trim();
const agentId = target.agentId.trim();
if (!sessionKey || !agentId) return;
const key = sessionObserverScopeKey(sessionKey, agentId);
askRuntime.cancel(sessionKey, agentId, cancellation);
threads.delete(key);
};
const sweep = () => {
const cutoff = now() - SESSION_COMPANION_IDLE_TTL_MS;
for (const [key, thread] of threads) if (!thread.busy && thread.lastUsedAt <= cutoff) threads.delete(key);
};
const sweepTimer = setIntervalFn(sweep, SESSION_COMPANION_SWEEP_INTERVAL_MS);
sweepTimer.unref?.();
const unsubscribeReset = onGatewaySessionReset((sessionKey, suppliedAgentId) => {
let agentId = suppliedAgentId;
try {
agentId ??= resolveSessionAgentId({
sessionKey,
config: deps.getConfig()
});
} catch {
return;
}
reset({
sessionKey,
agentId
}, "backing-session-revoked");
});
return {
ask: askRuntime.ask,
state(target) {
const key = sessionObserverScopeKey(target.sessionKey.trim(), target.agentId.trim());
const thread = threads.get(key);
if (!thread) return { exchanges: [] };
thread.lastUsedAt = now();
return { exchanges: thread.exchanges.map(({ question, answer, ts }) => ({
question,
answer,
ts
})) };
},
reset(target) {
reset(target, "explicit-reset");
},
dispose() {
if (disposed) return;
disposed = true;
clearIntervalFn(sweepTimer);
unsubscribeReset();
askRuntime.dispose();
threads.clear();
}
};
}
//#endregion
//#region src/gateway/session-lifecycle-persistence-owner.ts
function assertTerminalAuthority(authority) {
if (getAgentRunContextOwnerStatus(authority.runId, authority.claimId, authority.lifecycleGeneration) !== "active") throw createAgentRunStaleLifecycleError();
}
function terminalEventAuthority(event) {
return event.contextClaimId && event.lifecycleGeneration && event.runId ? {
claimId: event.contextClaimId,
lifecycleGeneration: event.lifecycleGeneration,
runId: event.runId
} : void 0;
}
function terminalEventKey(event) {
if (!event.runId || event.seq === void 0) return;
return `${event.contextClaimId ?? ""}\0${event.lifecycleGeneration ?? ""}\0${event.runId}\0${event.seq}`;
}
/** Owns each definitive lifecycle write before optional chat presentation code runs. */
function createSessionLifecyclePersistenceOwner() {
const prepared = /* @__PURE__ */ new Map();
const inFlight = /* @__PURE__ */ new Set();
const observe = (params) => {
const key = terminalEventKey(params.event);
const existing = key ? prepared.get(key)?.promise : void 0;
if (existing) return existing;
const authority = params.authority;
const promise = persistGatewaySessionLifecycleEvent({
sessionKey: params.sessionKey,
...params.agentId ? { agentId: params.agentId } : {},
event: {
...params.event,
...params.event.lifecycleGeneration ? { lifecycleGeneration: params.event.lifecycleGeneration } : {},
...params.event.mainSessionRestartRecovery === true ? { mainSessionRestartRecovery: true } : {},
...params.clientRunId ? { clientRunId: params.clientRunId } : {}
},
...authority ? { assertCommitAllowed: () => assertTerminalAuthority(authority) } : {}
});
inFlight.add(promise);
let entry;
const settle = () => {
inFlight.delete(promise);
if (!entry) return;
entry.settled = true;
if (entry.expired && key && prepared.get(key) === entry) prepared.delete(key);
};
promise.then(settle, settle);
if (key) {
const preparedEntry = {
expired: false,
promise,
settled: false,
timer: setTimeout(() => {
if (prepared.get(key) !== preparedEntry) return;
preparedEntry.expired = true;
if (preparedEntry.settled) prepared.delete(key);
}, AGENT_RUN_TERMINAL_RETRY_GRACE_MS)
};
entry = preparedEntry;
preparedEntry.timer.unref?.();
prepared.set(key, preparedEntry);
}
return promise;
};
const take = (event) => {
const key = terminalEventKey(event);
if (!key) return;
const entry = prepared.get(key);
if (!entry) return;
clearTimeout(entry.timer);
prepared.delete(key);
return entry.promise;
};
return {
observe,
persist: (params) => {
const preparedPersistence = take(params.event);
if (preparedPersistence) return preparedPersistence;
if (isDefinitiveRunLifecycle({
phase: params.event.data?.phase,
data: params.event.data
}) && terminalEventKey(params.event)) return Promise.reject(createAgentRunStaleLifecycleError());
const authority = terminalEventAuthority(params.event);
return persistGatewaySessionLifecycleEvent({
...params,
...authority ? { assertCommitAllowed: () => assertTerminalAuthority(authority) } : {}
});
},
async drain() {
await Promise.allSettled(inFlight);
for (const entry of prepared.values()) clearTimeout(entry.timer);
prepared.clear();
}
};
}
//#endregion
//#region src/gateway/session-observer-audience.ts
function createSessionObserverAudience(params) {
const messageSubscriberKeys = (sessionKey, agentId) => {
const canonicalKeys = resolveSessionSubscriptionKeys(sessionKey, agentId);
if (canonicalKeys[0] === sessionKey) return canonicalKeys;
const config = params.getConfig();
const persistedOwner = resolvePersistedSessionStoreOwnerForKey(config, sessionKey);
const compatibilityAgentId = persistedOwner.kind === "configured" ? persistedOwner.agentId : tryResolveLegacyCompatibilityAgentId(config);
return resolveSessionSubscriptionKeys(sessionKey, agentId, compatibilityAgentId);
};
const messageRecipients = (sessionKey, agentId) => {
const recipients = /* @__PURE__ */ new Set();
for (const key of messageSubscriberKeys(sessionKey, agentId)) for (const connId of params.subscribers.get(key)) recipients.add(connId);
return recipients;
};
const classify = (sessionKey, agentId) => {
for (const key of messageSubscriberKeys(sessionKey, agentId)) for (const connId of params.subscribers.get(key)) if (params.isVisible(connId)) return "direct";
for (const connId of params.sessionEventSubscribers?.getAll() ?? []) if (params.isVisible(connId)) return "broad";
return "none";
};
return {
classify,
deliveryOptions(sessionKey, agentId) {
return {
agentId,
dropIfSlow: true,
sessionKeys: messageSubscriberKeys(sessionKey, agentId),
sessionSubscriptionVerified: true
};
},
recipients(sessionKey, agentId) {
const recipients = messageRecipients(sessionKey, agentId);
for (const connId of params.sessionEventSubscribers?.getAll() ?? []) if (params.isVisible(connId)) recipients.add(connId);
return recipients;
},
criticalRecipients(sessionKey, agentId) {
const recipients = messageRecipients(sessionKey, agentId);
for (const connId of params.sessionEventSubscribers?.getAll() ?? []) recipients.add(connId);
return recipients;
}
};
}
function createSessionObserverAudienceLifecycle(params) {
const reconcileState = (state) => {
const audience = params.audience.classify(state.sessionKey, state.agentId);
if (audience === "none") params.suspend(state);
else if (state.utilityModelRef && audience !== "direct") params.demote(state);
};
const reconcileAll = () => {
for (const state of params.states.values()) reconcileState(state);
};
const unsubscribe = params.subscribers.onChange((sessionKey) => {
const state = params.states.get(sessionKey);
if (state) reconcileState(state);
else if (sessionKey.toLowerCase() === "global") reconcileAll();
});
const stateIsCurrent = (state, observedAudience) => params.isCurrent(state) && (observedAudience ?? params.audience.classify(state.sessionKey, state.agentId)) !== "none";
const modelStateIsCurrent = (state, observedAudience) => Boolean(state.utilityModelRef) && (observedAudience ?? params.audience.classify(state.sessionKey, state.agentId)) === "direct" && params.isCurrent(state) && params.resolveUtilityModelRef(state.agentId) === state.utilityModelRef;
return {
stateIsCurrent,
modelStateIsCurrent,
reconcileAll,
unsubscribe
};
}
//#endregion
//#region src/gateway/session-observer-companion.ts
function createSessionObserverCompanionSnapshotReader(params) {
return (sessionKey, selectedAgentId) => {
const cfg = params.getConfig();
const agentId = resolveSessionAgentId({
sessionKey,
config: cfg,
...selectedAgentId ? { agentId: selectedAgentId } : {}
});
const canonicalSessionKey = resolveStoredSessionKeyForAgentStore({
cfg,
agentId,
sessionKey
});
const state = params.states.get(resolveSessionSubscriptionKey(canonicalSessionKey, agentId));
if (state) {
flushSessionActivityAssistantNote(state);
return {
agentId: state.agentId,
runId: state.runId,
...state.previousDigest ? { digest: state.previousDigest } : {},
notes: state.notes.map((note) => ({
sequence: note.sequence,
text: note.text
}))
};
}
const digest = params.readSession(canonicalSessionKey, agentId)?.observerDigest;
return {
agentId,
...digest?.runId ? { runId: digest.runId } : {},
...digest ? { digest } : {},
notes: []
};
};
}
//#endregion
//#region src/gateway/session-observer-completion.ts
const MODEL_TIMEOUT_MS = 1e4;
function createSessionObserverCompletion(params) {
const ensurePrepared = async (state) => {
const modelRef = state.utilityModelRef;
if (!modelRef) throw new Error("session observer utility model is unavailable");
const preparedPromise = state.preparedPromise ??= params.prepareModel({
cfg: params.getConfig(),
agentId: state.agentId,
modelRef,
useUtilityModel: true
});
let failed = true;
try {
const prepared = await preparedPromise;
failed = false;
return prepared;
} finally {
if (failed && state.preparedPromise === preparedPromise) state.preparedPromise = void 0;
}
};
return async (state, notes) => {
const controller = new AbortController();
state.activeController = controller;
const timeout = params.setTimeoutFn(() => controller.abort(), MODEL_TIMEOUT_MS);
const aborted = new Promise((_resolve, reject) => {
controller.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("session observer model call timed out or was cancelled")), { once: true });
});
try {
const execute = async () => {
const prepared = await ensurePrepared(state);
if (!params.isCurrent(state) || controller.signal.aborted) throw new Error("session observer state is no longer active");
for (let attempt = 0; attempt < 2; attempt += 1) {
if (!params.isCurrent(state) || controller.signal.aborted) throw new Error("session observer state is no longer active");
const result = await params.completeModel({
...prepared,
config: params.getConfig(),
systemPrompt: SESSION_OBSERVER_SYSTEM_PROMPT,
prompt: buildSessionObserverPrompt(state, notes),
timeoutMs: MODEL_TIMEOUT_MS,
abortSignal: controller.signal,
streamParams: {
maxTokens: 300,
temperature: .2
}
});
const parsed = normalizeSessionObserverModelOutput(result.text);
if (parsed) return parsed;
}
throw new Error("session observer returned invalid JSON twice");
};
return await Promise.race([execute(), aborted]);
} finally {
params.clearTimeoutFn(timeout);
if (state.activeController === controller) state.activeController = void 0;
}
};
}
//#endregion
//#region src/gateway/session-observer-model-slots.ts
function createSessionObserverModelSlots(params) {
const demoted = /* @__PURE__ */ new WeakSet();
const requestGenerations = /* @__PURE__ */ new WeakMap();
return {
beginRequest(state) {
const generation = (requestGenerations.get(state) ?? 0) + 1;
requestGenerations.set(state, generation);
return generation;
},
invalidateRequest(state) {
requestGenerations.set(state, (requestGenerations.get(state) ?? 0) + 1);
state.activeController?.abort();
},
requestIsCurrent(state, generation) {
return requestGenerations.get(state) === generation;
},
claim(agentId, current) {
const resolved = params.resolve(agentId);
if (!resolved || current?.utilityModelRef === resolved) return resolved;
let occupied = 0;
let evicted;
for (const state of params.states.values()) {
if (state === current || !state.utilityModelRef) continue;
occupied += 1;
if (!state.terminalHealth && !state.finalPending && (!evicted || (state.lastActivityAt - evicted.lastActivityAt || state.sessionKey.localeCompare(evicted.sessionKey)) < 0)) evicted = state;
}
if (occupied >= params.maxSessions) {
if (current && demoted.has(current) || !evicted) return;
demoted.add(evicted);
params.demote(evicted);
} else if (current) demoted.delete(current);
return resolved;
}
};
}
//#endregion
//#region src/gateway/session-observer-persistence.ts
const PERSIST_INTERVAL_MS = 6e4;
function createSessionObserverDigestPersister(params) {
const preamblePersistedAt = /* @__PURE__ */ new WeakMap();
return async (state, digest, final, kind = "model") => {
const lastPersistedAt = kind === "preamble" ? preamblePersistedAt.get(state) : state.lastPersistedAt;
const due = lastPersistedAt === void 0 || params.now() - lastPersistedAt >= PERSIST_INTERVAL_MS;
if (!final && !due) return;
const attempts = final ? 2 : 1;
for (let attempt = 0; attempt < attempts; attempt += 1) try {
const accepted = await params.persistDigest({
sessionKey: state.sessionKey,
sessionId: state.sessionId,
agentId: state.agentId,
digest,
stillCurrent: params.stillCurrent(state.runId, state.sessionKey, state.agentId)
});
if (accepted === null) {
params.onMissingEntry(state);
return;
}
if (accepted) {
if (kind === "preamble") preamblePersistedAt.set(state, params.now());
else state.lastPersistedAt = params.now();
}
return;
} catch (error) {
if (attempt + 1 === attempts) params.onError(state, error);
}
};
}
//#endregion
//#region src/agents/session-preamble.ts
function normalizeSessionPreambleText(value, maxChars) {
if (typeof value !== "string") return "";
const sanitized = sanitizeProgressStatusText(value);
if (!sanitized) return "";
const normalized = stripMarkdown(sanitized, { linkStyle: "label" }).replace(/\s+/gu, " ").trim();
return truncateUtf16Safe(normalized, maxChars);
}
//#endregion
//#region src/gateway/session-observer-preamble.ts
const PREAMBLE_HEADLINE_MAX_CHARS = 120;
const PREAMBLE_PUBLISH_INTERVAL_MS = 2e3;
function createSessionObserverPreamblePublisher(params) {
const entries = /* @__PURE__ */ new Map();
const generations = /* @__PURE__ */ new WeakMap();
const clear = (state) => {
const entry = entries.get(state);
if (entry?.timer) params.clearTimeoutFn(entry.timer);
entries.delete(state);
};
const publish = (state, entry) => {
entry.timer = void 0;
if (!params.isCurrent(state)) {
clear(state);
return;
}
const previous = state.previousDigest;
if (previous?.runId === state.runId && previous.headline === entry.headline) {
clear(state);
return;
}
state.revision += 1;
const digest = {
sessionKey: state.sessionKey,
agentId: state.agentId,
runId: state.runId,
revision: state.revision,
updatedAt: Math.max(entry.updatedAt, (previous?.updatedAt ?? -1) + 1),
headline: entry.headline,
health: previous?.runId === state.runId && previous.health !== "done" && previous.health !== "failed" ? previous.health : "on-track",
...state.planProgress ? { planProgress: state.planProgress } : {}
};
state.previousDigest = digest;
state.lastPublishedPreambleHeadline = entry.headline;
entry.lastPublishedAt = params.now();
entry.published = true;
params.publish(state, digest);
};
return {
handle(state, event) {
if (event.stream !== "item" || event.data.kind !== "preamble") return false;
const headline = normalizeSessionPreambleText(event.data.progressText, PREAMBLE_HEADLINE_MAX_CHARS);
if (!headline) return true;
const existing = entries.get(state);
const previousHeadline = state.lastPreambleHeadline ?? (state.previousDigest?.runId === state.runId ? state.previousDigest.headline : "");
if (!existing && previousHeadline === headline) {
state.lastPreambleHeadline = headline;
state.lastPublishedPreambleHeadline = headline;
return true;
}
const entry = existing ?? {
headline: "",
lastPublishedAt: 0,
published: false,
updatedAt: event.ts
};
if (previousHeadline !== headline) generations.set(state, (generations.get(state) ?? 0) + 1);
state.lastPreambleHeadline = headline;
entry.headline = headline;
entry.updatedAt = event.ts;
entries.set(state, entry);
const elapsed = params.now() - entry.lastPublishedAt;
if (!entry.published || elapsed >= PREAMBLE_PUBLISH_INTERVAL_MS) {
if (entry.timer) params.clearTimeoutFn(entry.timer);
publish(state, entry);
} else if (!entry.timer) {
entry.timer = params.setTimeoutFn(() => publish(state, entry), PREAMBLE_PUBLISH_INTERVAL_MS - elapsed);
entry.timer.unref?.();
}
return true;
},
generation(state) {
return generations.get(state) ?? 0;
},
flush(state) {
const entry = entries.get(state);
if (entry) {
if (entry.timer) params.clearTimeoutFn(entry.timer);
publish(state, entry);
}
},
clear,
dispose() {
for (const state of entries.keys()) clear(state);
}
};
}
//#endregion
//#region src/gateway/session-observer.ts
const observerLog = createSubsystemLogger("gateway/session-observer");
const MIN_NOTES_PER_DIGEST = 4;
const MIN_DIGEST_INTERVAL_MS = 12e3;
const MAX_DIGESTS_PER_RUN = 40;
const MAX_LIVE_DIGESTS_PER_RUN = 39;
const MAX_CONSECUTIVE_FAILURES = 2;
const FINAL_DIGEST_MIN_RUN_MS = 3e4;
const MAX_CONCURRENT_MODEL_SESSIONS = 6;
function createSessionObserver(deps) {
const now = deps.now ?? Date.now;
const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;
const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;
const resolveUtilityModelRef = deps.resolveUtilityModelRef ?? resolveUtilityModelRefForAgent;
const prepareModel = deps.prepareModel ?? defaultPrepareModel;
const completeModel = deps.completeModel ?? defaultCompleteModel;
const readSession = deps.readSession ?? defaultReadSession;
const persistDigest = deps.persistDigest ?? defaultPersistDigest;
const states = /* @__PURE__ */ new Map();
const dormantRuns = /* @__PURE__ */ new Map();
const revisionFloors = /* @__PURE__ */ new Map();
const supersededRuns = /* @__PURE__ */ new Map();
const contextlessTerminalRuns = /* @__PURE__ */ new Map();
const terminalRuns = /* @__PURE__ */ new Map();
const pendingTerminalErrors = /* @__PURE__ */ new Map();
const disabledRuns = /* @__PURE__ */ new Set();
const visibleConnections = /* @__PURE__ */ new Set();
let disposed = false;
const clearPendingTerminalError = (runId) => {
clearTimeoutFn(pendingTerminalErrors.get(runId));
pendingTerminalErrors.delete(runId);
};
const getCompanionSnapshot = createSessionObserverCompanionSnapshotReader({
getConfig: deps.getConfig,
readSession,
states
});
const audience = createSessionObserverAudience({
subscribers: deps.subscribers,
sessionEventSubscribers: deps.sessionEventSubscribers,
isVisible: (connId) => visibleConnections.has(connId),
getConfig: deps.getConfig
});
const broadcastDigest = (digest, connIds, agentId) => deps.broadcastToConnIds("session.observer", digest, connIds, audience.deliveryOptions(digest.sessionKey, agentId));
const runStillCurrent = (runId, sessionKey, agentId) => () => !disposed && !supersededRuns.has(runId) && (states.get(resolveSessionSubscriptionKey(sessionKey, agentId))?.runId ?? runId) === runId;
const persistAcceptedDigest = createSessionObserverDigestPersister({
now,
persistDigest,
stillCurrent: runStillCurrent,
onMissingEntry: (state) => {
disableModelForRun(state);
},
onError: (state, error) => observerLog.warn("session observer digest persistence failed", {
sessionKey: state.sessionKey,
runId: state.runId,
error: formatErrorMessage(error)
})
});
const preamblePublisher = createSessionObserverPreamblePublisher({
now,
setTimeoutFn,
clearTimeoutFn,
isCurrent: (state) => audienceLifecycle.stateIsCurrent(state),
publish: (state, digest) => {
broadcastDigest(digest, audience.recipients(state.sessionKey, state.agentId), state.agentId);
persistAcceptedDigest(state, digest, false, "preamble");
}
});
async function synthesizeTerminalDigest(source) {
const runId = source.event?.runId ?? source.state?.runId;
if (!runId) return;
const dormant = dormantRuns.get(runId);
const sessionKey = source.event?.sessionKey ?? source.state?.sessionKey ?? dormant?.sessionKey;
const agentId = source.event?.agentId ?? source.state?.agentId ?? dormant?.agentId;
if (!sessionKey || !agentId) return;
const stillCurrent = runStillCurrent(runId, sessionKey, agentId);
if (!stillCurrent()) return;
try {
const digest = await synthesizeSessionObserverTerminalDigest({
source,
dormant,
readSession,
persistDigest,
now,
stillCurrent
});
if (digest && stillCurrent()) broadcastDigest(digest, audience.recipients(digest.sessionKey, agentId), agentId);
} catch (error) {
observerLog.warn("session observer terminal digest synthesis failed", {
runId,
error: formatErrorMessage(error)
});
}
}
const stateIsTracked = (state) => states.get(resolveSessionSubscriptionKey(state.sessionKey, state.agentId)) === state;
const dropState = (state) => {
preamblePublisher.clear(state);
if (state.timer) clearTimeoutFn(state.timer);
modelSlots.invalidateRequest(state);
if (stateIsTracked(state)) {
const scopeKey = resolveSessionSubscriptionKey(state.sessionKey, state.agentId);
if (state.terminalHealth === "failed" && !terminalRuns.has(state.runId) && state.previousDigest) rememberSessionObserverRevisionFloor(revisionFloors, scopeKey, {
revision: state.revision,
previousDigest: state.previousDigest
});
states.delete(scopeKey);
}
};
const retireTerminalState = (state) => {
synthesizeTerminalDigest({ state });
dormantRuns.delete(state.runId);
dropState(state);
};
const suspendState = (state) => {
if (state.terminalHealth) {
retireTerminalState(state);
return;
}
rememberSessionObserverDormantRun(dormantRuns, revisionFloors, createDormantSessionObserverRun(state));
dropState(state);
};
const retireInactiveState = (state) => (disposed ? dropState : suspendState)(state);
const demoteUtilityModel = (state) => {
if (state.timer) {
clearTimeoutFn(state.timer);
state.timer = void 0;
}
modelSlots.invalidateRequest(state);
state.preparedPromise = void 0;
state.utilityModelRef = void 0;
state.consecutiveFailures = 0;
};
const modelSlots = createSessionObserverModelSlots({
states,
maxSessions: MAX_CONCURRENT_MODEL_SESSIONS,
resolve: (agentId) => resolveUtilityModelRef({
cfg: deps.getConfig(),
agentId
}),
demote: demoteUtilityModel
});
const disableModelForRun = (state) => {
rememberSessionObserverDisabledRun(disabledRuns, state.runId);
demoteUtilityModel(state);
};
const audienceLifecycle = createSessionObserverAudienceLifecycle({
audience,
states,
subscribers: deps.subscribers,
isCurrent: (state) => !disposed && stateIsTracked(state) && deps.getConfig().gateway?.controlUi?.sessionObserver !== false,
resolveUtilityModelRef: (agentId) => resolveUtilityModelRef({
cfg: deps.getConfig(),
agentId
}),
suspend: suspendState,
demote: demoteUtilityModel
});
const { modelStateIsCurrent } = audienceLifecycle;
const requestModelDigest = createSessionObserverCompletion({
getConfig: deps.getConfig,
prepareModel,
completeModel,
setTimeoutFn,
clearTimeoutFn,
isCurrent: modelStateIsCurrent
});
const schedule = (state, run, observedAudience) => {
const currentAudience = observedAudience ?? audience.classify(state.sessionKey, state.agentId);
if (!audienceLifecycle.stateIsCurrent(state, currentAudience)) {
retireInactiveState(state);
return;
}
if (!modelStateIsCurrent(state, currentAudience) || state.inFlight || state.timer || state.terminalHealth || state.digestCount >= MAX_LIVE_DIGESTS_PER_RUN || (state.notes.at(-4)?.sequence ?? 0) <= state.lastDigestNoteSequence) return;
const delay = Math.max(0, MIN_DIGEST_INTERVAL_MS - (now() - state.lastRunAt));
if (delay === 0) {
run(state, false);
return;
}
state.timer = setTimeoutFn(() => {
state.timer = void 0;
run(state, false);
}, delay);
};
const runDigest = (state, final) => {
const currentAudience = audience.classify(state.sessionKey, state.agentId);
if (!audienceLifecycle.stateIsCurrent(state, currentAudience)) {
retireInactiveState(state);
return;
}
if (!modelStateIsCurrent(state, currentAudience)) {
if (final) retireTerminalState(state);
return;
}
if (state.inFlight) {
state.finalPending ||= final;
return;
}
const digestLimit = final ? MAX_DIGESTS_PER_RUN : MAX_LIVE_DIGESTS_PER_RUN;
if (state.digestCount >= digestLimit) return;
flushSessionActivityAssistantNote(state);
const selectedNotes = state.notes.filter((note) => note.sequence > state.lastDigestNoteSequence);
if (!final && selectedNotes.length < MIN_NOTES_PER_DIGEST) return;
if (!final && now() - state.lastRunAt < MIN_DIGEST_INTERVAL_MS) {
schedule(state, runDigest);
return;
}
if (state.timer) {
clearTimeoutFn(state.timer);
state.timer = void 0;
}
state.inFlight = true;
state.lastRunAt = now();
const lastSelectedSequence = selectedNotes.at(-1)?.sequence ?? state.lastDigestNoteSequence;
const retireSelectedNotes = () => {
state.lastDigestNoteSequence = Math.max(state.lastDigestNoteSequence, lastSelectedSequence);
};
const requestGeneration = modelSlots.beginRequest(state);
const digestIsStale = () => !modelStateIsCurrent(state) || !modelSlots.requestIsCurrent(state, requestGeneration) || !final && state.terminalHealth !== void 0;
state.digestCount += 1;
(async () => {
try {
const modelDigest = await requestModelDigest(state, selectedNotes.map((note) => note.text));
if (digestIsStale()) {
retireSelectedNotes();
if (final && stateIsTracked(state)) retireTerminalState(state);
return;
}
if (state.sessionId && readSession(state.sessionKey, state.agentId)?.sessionId !== state.sessionId) return disableModelForRun(state);
preamblePublisher.clear(state);
state.consecutiveFailures = 0;
state.revision += 1;
retireSelectedNotes();
const digest = {
sessionKey: state.sessionKey,
agentId: state.agentId,
runId: state.runId,
revision: state.revision,
updatedAt: now(),
headline: modelDigest.headline,
...modelDigest.assessment ? { assessment: modelDigest.assessment } : {},
health: final ? state.terminalHealth ?? modelDigest.health : modelDigest.health,
...state.planProgress ?? modelDigest.planProgress ? { planProgress: state.planProgress ?? modelDigest.planProgress } : {}
};
const previous = state.previousDigest?.health;
const next = digest.health;
const criticalTransition = (next === "stuck" || next === "waiting-on-user") && previous !== next;
state.previousDigest = digest;
const recipients = criticalTransition ? audience.criticalRecipients(state.sessionKey, state.agentId) : audience.recipients(state.sessionKey, state.agentId);
broadcastDigest(digest, recipients, state.agentId);
await persistAcceptedDigest(state, digest, final);
if (final) dormantRuns.delete(state.runId);
} catch (error) {
if (digestIsStale()) {
retireSelectedNotes();
if (final && stateIsTracked(state)) retireTerminalState(state);
return;
}
state.consecutiveFailures += 1;
if (state.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
observerLog.warn("session observer disabled after consecutive failures", {
sessionKey: state.sessionKey,
runId: state.runId,
error: formatErrorMessage(error)
});
if (final || state.finalPending || state.terminalHealth) retireTerminalState(state);
else disableModelForRun(state);
} else if (final) state.finalPending = true;
} finally {
if (stateIsTracked(state)) {
state.inFlight = false;
const runFinal = state.finalPending;
state.finalPending = false;
if (runFinal) runDigest(state, true);
else if (final) dropState(state);
else schedule(state, runDigest);
}
}
})();
};
const admitState = (event, allowPreambleOnly, sessionKey, agentId, observedAudience) => {
if (observedAudience === "none") return;
const scopeKey = resolveSessionSubscriptionKey(sessionKey, agentId);
if (deps.getConfig().gateway?.controlUi?.sessionObserver === false) return;
const utilityModelRef = disabledRuns.has(event.runId) || observedAudience !== "direct" ? void 0 : modelSlots.claim(agentId);
if (!utilityModelRef && !allowPreambleOnly) return;
const dormant = dormantRuns.get(event.runId);
if (dormant) {
dormantRuns.delete(event.runId);
const { utilityModelRef: _dormantModelRef, ...dormantState } = dormant;
const state = {
...createSessionActivityNoteState(),
...dormantState,
...dormantState.lastPreambleHeadline ? { lastPublishedPreambleHeadline: dormantState.lastPreambleHeadline } : {},
...utilityModelRef ? { utilityModelRef } : {},
lastActivityAt: event.ts,
lastRunAt: now(),
lastDigestNoteSequence: 0,
inFlight: false,
finalPending: false
};
states.set(scopeKey, state);
return state;
}
const session = readSession(sessionKey, agentId);
const startedAt = asFiniteNumber(event.data.startedAt) ?? session?.startedAt ?? event.ts ?? now();
const state = {
...createSessionActivityNoteState(),
sessionKey,
sessionId: event.sessionId ?? session?.sessionId,
runId: event.runId,
agentId,
...utilityModelRef ? { utilityModelRef } : {},
startedAt,
lastActivityAt: event.ts,
lastRunAt: startedAt,
lastPersistedAt: session?.observerDigest?.updatedAt,
revision: session?.observerDigest?.revision ?? 0,
digestCount: 0,
consecutiveFailures: 0,
lastDigestNoteSequence: 0,
previousDigest: session?.observerDigest,
inFlight: false,
finalPending: false
};
states.set(scopeKey, state);
return state;
};
const handleEvent = (event, settledError = false) => {
if (disposed || getAgentRunContext(event.runId)?.isHeartbeat) return;
const lifecyclePhase = event.stream === "lifecycle" ? event.data.phase : void 0;
const terminal = settledError || isDefinitiveRunLifecycle({
phase: lifecyclePhase,
data: event.data
});
if (lifecyclePhase === "error" && !terminal) {
clearPendingTerminalError(event.runId);
const timer = setTimeoutFn(() => handleEvent(event, true), AGENT_RUN_TERMINAL_RETRY_GRACE_MS);
pendingTerminalErrors.set(event.runId, timer);
return;
}
if (terminal || lifecyclePhase === "start") clearPendingTerminalError(event.runId);
if (terminalRuns.has(event.runId)) return;
if (supersededRuns.has(event.runId)) {
if (terminal) {
markSessionObserverRunSuperseded(terminalRuns, event.runId, event.ts);
contextlessTerminalRuns.delete(event.runId);
supersededRuns.delete(event.runId);
dormantRuns.delete(event.runId);
disabledRuns.delete(event.runId);
}
return;
}
if (contextlessTerminalRuns.has(event.runId) && !terminal) return;
const eventSessionKey = event.sessionKey?.trim();
const eventAgentId = event.agentId?.trim();
let knownRun;
if (terminal && (!eventSessionKey || !eventAgentId)) {
for (const candidate of states.values()) if (candidate.runId === event.runId) {
knownRun = candidate;
break;
}
knownRun ??= dormantRuns.get(event.runId);
}
const sessionKey = eventSessionKey || knownRun?.sessionKey;
if (!sessionKey) {
if (terminal) markSessionObserverRunSuperseded(contextlessTerminalRuns, event.runId, event.ts);
return;
}
const agentId = eventAgentId || knownRun?.agentId;
if (terminal) {
contextlessTerminalRuns.delete(event.runId);
if (!settledError) markSessionObserverRunSuperseded(terminalRuns, event.runId, event.ts);
}
const isPreamble = event.stream === "item" && event.data.kind === "preamble";
if (!agentId) {
if (terminal) {
synthesizeTerminalDigest({ event });
dormantRuns.delete(event.runId);
disabledRuns.delete(event.runId);
}
return;
}
const currentAudience = audience.classify(sessionKey, agentId);
const scopeKey = resolveSessionSubscriptionKey(sessionKey, agentId);
if (terminal && audience.recipients(sessionKey, agentId).size === 0) {
synthesizeTerminalDigest({
event,
state: states.get(scopeKey)
});
dormantRuns.delete(event.runId);
disabledRuns.delete(event.runId);
return;
}
const isRunStart = event.stream === "lifecycle" && event.data.phase === "start";
let revisionFloor = revisionFloors.get(scopeKey);
let state = states.get(scopeKey);
if (state && state.runId !== event.runId) {
const candidate = {
revision: state.revision,
previousDigest: state.previousDigest
};
if (!revisionFloor || candidate.revision > revisionFloor.revision) revisionFloor = candidate;
const supersededRunId = state.runId;
clearPendingTerminalError(supersededRunId);
if (isRunStart) markSessionObserverRunSuperseded(supersededRuns, supersededRunId, event.ts);
suspendState(state);
if (isRunStart) dormantRuns.delete(supersededRunId);
state = void 0;
}
if (!state) {
const superseded = [...dormantRuns.values()].filter((run) => resolveSessionSubscriptionKey(run.sessionKey, run.agentId) === scopeKey && run.runId !== event.runId).toSorted((left, right) => right.revision - left.revision || left.runId.localeCompare(right.runId));
const latest = superseded[0];
if (latest && (!revisionFloor || latest.revision > revisionFloor.revision)) revisionFloor = {
revision: latest.revision,
previousDigest: latest.previousDigest
};
if (isRunStart) {
if (revisionFloor) {
rememberSessionObserverRevisionFloor(revisionFloors, scopeKey, revisionFloor);
const previousRunId = revisionFloor.previousDigest?.runId;
if (previousRunId && previousRunId !== event.runId) markSessionObserverRunSuperseded(supersededRuns, previousRunId, event.ts);
}
for (const run of superseded) {
markSessionObserverRunSuperseded(supersededRuns, run.runId, event.ts);
clearPendingTerminalError(run.runId);
dormantRuns.delete(run.runId);
}
}
}
if (state && (currentAudience === "none" || deps.getConfig().gateway?.controlUi?.sessionObserver === false)) {
suspendState(state);
state = void 0;
}
if (!state) state = admitState(event, isPreamble, sessionKey, agentId, currentAudience);
if (!state) {
if (terminal) {
synthesizeTerminalDigest({ event });
dormantRuns.delete(event.runId);
disabledRuns.delete(event.runId);
}
return;
}
if (state.terminalHealth) return;
if (revisionFloor && revisionFloor.revision > state.revision) {
state.revision = revisionFloor.revision;
state.previousDigest = revisionFloor.previousDigest;
}
revisionFloors.delete(scopeKey);
const utilityModelRef = disabledRuns.has(state.runId) || currentAudience !== "direct" ? void 0 : modelSlots.claim(state.agentId, state);
if (state.utilityModelRef !== utilityModelRef) {
modelSlots.invalidateRequest(state);
state.preparedPromise = void 0;
state.utilityModelRef = utilityModelRef;
state.consecutiveFailures = 0;
}
state.lastActivityAt = event.ts;
const eventStartedAt = asFiniteNumber(event.data.startedAt);
if (eventStartedAt !== void 0) state.startedAt = Math.min(state.startedAt, eventStartedAt);
noteSessionActivityEvent(state, event);
preamblePublisher.handle(state, event);
if (terminal) {
if (!state.terminalHealth) modelSlots.invalidateRequest(state);
preamblePublisher.flush(state);
preamblePublisher.clear(state);
state.terminalHealth = terminalHealthFor(event);
disabledRuns.delete(event.runId);
const endedAt = asFiniteNumber(event.data.endedAt) ?? now();
if (!(state.previousDigest?.runId === state.runId) && endedAt - state.startedAt < FINAL_DIGEST_MIN_RUN_MS) {
dormantRuns.delete(state.runId);
dropState(state);
return;
}
runDigest(state, true);
return;
}
schedule(state, runDigest, currentAudience);
};
return {
handleEvent,
setConnectionVisibility(connId, visible) {
if (visible) {
visibleConnections.add(connId);
return;
}
visibleConnections.delete(connId);
audienceLifecycle.reconcileAll();
},
removeConnection(connId) {
if (visibleConnections.delete(connId)) audienceLifecycle.reconcileAll();
},
getCompanionSnapshot,
dispose() {
disposed = true;
pendingTerminalErrors.forEach((_timer, runId) => clearPendingTerminalError(runId));
preamblePublisher.dispose();
audienceLifecycle.unsubscribe();
for (const state of states.values()) dropState(state);
dormantRuns.clear();
revisionFloors.clear();
supersededRuns.clear();
terminalRuns.clear();
contextlessTerminalRuns.clear();
disabledRuns.clear();
visibleConnections.clear();
}
};
}
//#endregion
//#region src/gateway/server-runtime-subscriptions.ts
function dispatchEventHandler(params) {
return params.loadHandler().then((handler) => handler(params.event)).then(() => void 0).catch((error) => {
params.log.warn(params.failureMessage, {
...params.context,
error
});
params.onFailure?.();
});
}
function terminalTaskId(event) {
if (event.kind !== "upserted" || !isTerminalTaskStatus(event.task.status)) return;
if (event.previous && isTerminalTaskStatus(event.previous.status)) return;
return event.task.taskId;
}
/** Register gateway runtime event subscriptions and return unsubscribe handles. */
function startGatewayEventSubscriptions(params) {
const runtimeConfig = getRuntimeConfig();
const auditEnabled = isAuditLedgerEnabled(runtimeConfig);
const auditMessageMode = resolveAuditMessageMode(runtimeConfig);
const auditRecorder = createAuditEventRecorder({ messageMode: auditEnabled ? auditMessageMode : "off" });
const clearExecutionIdentityAdmissionSink = configureExecutionIdentityAdmissionSink(auditRecorder.recordExecutionIdentity);
const clearExecutionDecisionWorkSink = configureExecutionDecisionWorkSink(auditRecorder.recordExecutionDecisionWork);
const clearChannelAdmissionEvidenceCollection = configureChannelAdmissionEvidenceCollection(isExecutionIdentityCollectionEnabled(runtimeConfig));
const clearChannelAdmissionDecisionSink = configureChannelAdmissionDecisionSink(auditRecorder.recordExecutionDecision);
const clearMessageActionDecisionSink = configureMessageActionDecisionSink(auditRecorder.recordExecutionDecision);
const clearRuntimeActionDecisionSink = configureRuntimeActionDecisionSink(auditRecorder.recordExecutionDecision);
const sessionObserver = createSessionObserver({
getConfig: getRuntimeConfig,
subscribers: params.sessionMessageSubscribers,
sessionEventSubscribers: params.sessionEventSubscribers,
broadcastToConnIds: params.broadcastToConnIds
});
const sessionCompanion = createSessionCompanion({
contextReader: defaultSessionCompanionContextReader,
getConfig: getRuntimeConfig,
sessionObserver
});
const unsubscribePrivateAuditEvents = auditEnabled ? onAgentAuditEvent(auditRecorder.record) : void 0;
const unsubscribeToolAuditEvents = auditEnabled ? onTrustedToolExecutionEvent(auditRecorder.recordTool) : void 0;
const unsubscribeMessageAuditEvents = auditEnabled && auditMessageMode !== "off" ? onTrustedMessageAuditEvent(auditRecorder.recordMessage) : void 0;
const sessionLifecyclePersistence = createSessionLifecyclePersistenceOwner();
const agentEventDispatches = /* @__PURE__ */ new Set();
const trackedRunIds = (runId, clientRunId) => runId === clientRunId ? [runId] : [runId, clientRunId];
const clearTrackedActiveRun = (run) => {
for (const candidateRunId of trackedRunIds(run.runId, run.clientRunId)) {
const entry = params.chatAbortControllers.get(candidateRunId);
if (!entry) continue;
entry.projectSessionActive = false;
entry.projectSessionTerminalPersisted = false;
markChatAbortTerminalPersistenceError(entry, void 0);
queueMicrotask(() => {
if (params.chatAbortControllers.get(candidateRunId) === entry && entry.registrationCleanupRequested === true && !entry.projectSessionTerminalPersistence) removeChatAbortControllerEntry(params.chatAbortControllers, candidateRunId, entry);
});
}
};
const settleTrackedTerminal = (run) => {
const persisted = run.persisted ?? true;
for (const candidateRunId of trackedRunIds(run.runId, run.clientRunId)) {
const entry = params.chatAbortControllers.get(candidateRunId);
if (!entry) continue;
if (persisted) {
params.restartRecoveryCandidates.delete(candidateRunId);
markChatAbortTerminalPersistenceError(entry, void 0);
}
entry.projectSessionTerminalPending = false;
entry.projectSessionTerminalPersistence = void 0;
entry.projectSessionTerminalPersisted = persisted;
if (entry.registrationCleanupRequested === true) removeChatAbortControllerEntry(params.chatAbortControllers, candidateRunId, entry);
}
};
const trackTrackedRunTerminalPersistence = (run) => {
let tracked = false;
for (const candidateRunId of trackedRunIds(run.runId, run.clientRunId)) {
const entry = params.chatAbortControllers.get(candidateRunId);
if (!entry) continue;
tracked = true;
entry.projectSessionTerminalPersistence = run.persistence;
run.persistence.catch((error) => {
markChatAbortTerminalPersistenceError(entry, error);
});
const lifecycleGeneration = entry.lifecycleGeneration?.trim();
const sessionKey = entry.sessionKey.trim();
const sessionId = run.sessionId?.trim() || entry.sessionId.trim();
const observedAt = entry.projectSessionTerminalObservedAt;
if (entry.controlUiVisible !== false && lifecycleGeneration && sessionKey && sessionId) run.persistence.catch(() => {
params.restartRecoveryCandidates.set(candidateRunId, {
runId: candidateRunId,
lifecycleGeneration,
sessionKey,
sessionId,
observedAt
});
});
}
return tracked;
};
const getSessionKeyModule = createLazyPromise(() => import("./server-session-key-5JJwsoGu.js"), { cacheRejections: true });
const agentEventHandlerLoader = createLazyPromiseLoader(() => {
return Promise.all([import("./server-chat-DhlqkrkS.js"), getSessionKeyModule()]).then(([{ createAgentEventHandler }, { resolveSessionKeyForRun }]) => createAgentEventHandler({
broadcast: params.broadcast,
broadcastToConnIds: params.broadcastToConnIds,
nodeSendToSession: params.nodeSendToSession,
agentRunSeq: params.agentRunSeq,
chatRunState: params.chatRunState,
resolveSessionKeyForRun,
clearAgentRunContext,
toolEventRecipients: params.toolEventRecipients,
sessionEventSubscribers: params.sessionEventSubscribers,
sessionMessageSubscribers: params.sessionMessageSubscribers,
persistGatewaySessionLifecycleEventForEvent: sessionLifecyclePersistence.persist,
updateRunToolErrorSummary: ({ runId, clientRunId, summary }) => {
for (const candidateRunId of /* @__PURE__ */ new Set([runId, clientRunId])) {
const entry = params.chatAbortControllers.get(candidateRunId);
if (entry) entry.toolErrorSummary = summary;
}
},
clearTrackedActiveRun,
settleTrackedTerminal,
trackTrackedRunTerminalPersistence,
isChatSendRunActive: (runId) => {
const entry = params.chatAbortControllers.get(runId);
return entry !== void 0 && entry.kind !== "agent";
},
resolveActiveLifecycleGenerationForRun: (runId) => params.chatAbortControllers.get(runId)?.lifecycleGeneration,
resolveSessionActiveRunState: (session) => resolveVisibleActiveSessionRunState({
context: params,
...session,
defaultAgentId: tryResolveSessionCompatibilityOwnerAgentId(getRuntimeConfig(), session.requestedKey)
})
}));
}, { cacheRejections: true });
const getAgentEventHandler = agentEventHandlerLoader.load;
const getSessionEventsModule = createLazyPromise(() => import("./server-session-events-nntFoRej.js"), { cacheRejections: true });
let transcriptUpdateHandlerPromise = null;
const getTranscriptUpdateHandler = () => {
transcriptUpdateHandlerPromise ??= getSessionEventsModule().then(({ createTranscriptUpdateBroadcastHandler }) => createTranscriptUpdateBroadcastHandler({
broadcastToConnIds: params.broadcastToConnIds,
sessionEventSubscribers: params.sessionEventSubscribers,
sessionMessageSubscribers: params.sessionMessageSubscribers,
chatAbortControllers: params.chatAbortControllers
}));
return transcriptUpdateHandlerPromise;
};
let lifecycleEventHandlerPromise = null;
const getLifecycleEventHandler = () => {
lifecycleEventHandlerPromise ??= getSessionEventsModule().then(({ createLifecycleEventBroadcastHandler }) => createLifecycleEventBroadcastHandler({
broadcastToConnIds: params.broadcastToConnIds,
sessionEventSubscribers: params.sessionEventSubscribers,
chatAbortControllers: params.chatAbortControllers
}));
return lifecycleEventHandlerPromise;
};
const unsubscribeAgentEvents = onAgentRuntimeEvent((evt) => {
let failedDispatchCleanup;
let terminalPreparation;
sessionObserver.handleEvent(evt);
if (auditEnabled) auditRecorder.record(evt);
const lifecyclePhase = evt.stream === "lifecycle" && typeof evt.data?.phase === "string" ? evt.data.phase : void 0;
if (lifecyclePhase === "end" || lifecyclePhase === "error") {
const clientRunId = (evt.contextClaimId ? void 0 : params.chatRunState.registry.peek(evt.runId))?.clientRunId ?? evt.runId;
const candidateRunIds = evt.runId === clientRunId ? [evt.runId] : [evt.runId, clientRunId];
const observedAt = typeof evt.data.endedAt === "number" && Number.isFinite(evt.data.endedAt) ? evt.data.endedAt : evt.ts;
for (const candidateRunId of candidateRunIds) {
const entry = params.chatAbortControllers.get(candidateRunId);
const eventLifecycleGeneration = evt.lifecycleGeneration?.trim();
if (entry && (!eventLifecycleGeneration || !entry.lifecycleGeneration || entry.lifecycleGeneration === eventLifecycleGeneration)) {
entry.projectSessionTerminalPending = true;
entry.projectSessionTerminalObservedAt = observedAt;
}
}
const trackedEntry = candidateRunIds.map((candidateRunId) => params.chatAbortControllers.get(candidateRunId)).find((entry) => entry !== void 0);
const runContext = getAgentRunContext(evt.runId);
const sessionAgentId = trackedEntry?.agentId ?? evt.agentId ?? runContext?.agentId;
const knownSessionKey = evt.deliverySessionKey ?? evt.sessionKey ?? trackedEntry?.sessionKey ?? runContext?.sessionKey;
const eventLifecycleGeneration = evt.lifecycleGeneration?.trim();
const terminalAuthority = evt.contextClaimId && eventLifecycleGeneration ? {
claimId: evt.contextClaimId,
lifecycleGeneration: eventLifecycleGeneration,
runId: evt.runId
} : void 0;
const trackedOwnerIsCurrent = !trackedEntry || !eventLifecycleGeneration || !trackedEntry.lifecycleGeneration || trackedEntry.lifecycleGeneration === eventLifecycleGeneration;
const claimIsComplete = !evt.contextClaimId || terminalAuthority !== void 0;
const canPersistTerminal = isDefinitiveRunLifecycle({
phase: lifecyclePhase,
data: evt.data
}) && evt.projectSessionLifecycle !== false && trackedOwnerIsCurrent && claimIsComplete;
const prepareTerminalPersistence = (sessionKey) => {
const persistence = sessionLifecyclePersistence.observe({
sessionKey,
...sessionAgentId ? { agentId: sessionAgentId } : {},
event: evt,
...terminalAuthority ? { authority: terminalAuthority } : {},
...clientRunId !== evt.runId ? { clientRunId } : {}
});
if (terminalAuthority) {
const clearTerminalAuthority = () => clearAgentRunContext(terminalAuthority.runId, terminalAuthority.lifecycleGeneration, terminalAuthority.claimId);
failedDispatchCleanup = () => {
persistence.then(clearTerminalAuthority, clearTerminalAuthority);
};
}
clearTrackedActiveRun({
runId: evt.runId,
clientRunId
});
if (!trackTrackedRunTerminalPersistence({
runId: evt.runId,
clientRunId,
sessionId: evt.sessionId,
persistence
})) persistence.catch((error) => {
params.log.warn("Terminal session persistence failed", {
runId: evt.runId,
error
});
});
persistence.then(() => settleTrackedTerminal({
runId: evt.runId,
clientRunId
}), () => settleTrackedTerminal({
runId: evt.runId,
clientRunId,
persisted: false
}));
};
if (canPersistTerminal) {
if (knownSessionKey) prepareTerminalPersistence(knownSessionKey);
else terminalPreparation = getSessionKeyModule().then(({ resolveSessionKeyForRun }) => {
const sessionKey = resolveSessionKeyForRun(evt.runId, sessionAgentId ? { agentId: sessionAgentId } : void 0);
if (sessionKey) prepareTerminalPersistence(sessionKey);
});
}
} else if (lifecyclePhase === "start") {
const clientRunId = (evt.contextClaimId ? void 0 : params.chatRunState.registry.peek(evt.runId))?.clientRunId ?? evt.runId;
const candidateRunIds = evt.runId === clientRunId ? [evt.runId] : [evt.runId, clientRunId];
const eventLifecycleGeneration = evt.lifecycleGeneration?.trim();
for (const candidateRunId of candidateRunIds) {
const entry = params.chatAbortControllers.get(candidateRunId);
if (entry && (!eventLifecycleGeneration || !entry.lifecycleGeneration || entry.lifecycleGeneration === eventLifecycleGeneration)) {
entry.projectSessionTerminalPending = false;
entry.projectSessionTerminalObservedAt = void 0;
}
}
}
const dispatchPreparation = terminalPreparation;
const dispatch = dispatchEventHandler({
loadHandler: dispatchPreparation ? () => dispatchPreparation.then(() => getAgentEventHandler()) : getAgentEventHandler,
event: evt,
log: params.log,
failureMessage: "Agent event dispatch failed",
context: {
runId: evt.runId,
stream: evt.stream
},
onFailure: () => failedDispatchCleanup?.()
});
agentEventDispatches.add(dispatch);
dispatch.then(() => agentEventDispatches.delete(dispatch));
});
const agentUnsub = async () => {
unsubscribeAgentEvents();
sessionCompanion.dispose();
sessionObserver.dispose();
unsubscribePrivateAuditEvents?.();
unsubscribeToolAuditEvents?.();
unsubscribeMessageAuditEvents?.();
clearExecutionDecisionWorkSink();
clearExecutionIdentityAdmissionSink();
clearChannelAdmissionEvidenceCollection();
clearChannelAdmissionDecisionSink();
clearMessageActionDecisionSink();
clearRuntimeActionDecisionSink();
await Promise.allSettled(agentEventDispatches);
await agentEventHandlerLoader.peek()?.then((handler) => handler.dispose()).catch(() => void 0);
await sessionLifecyclePersistence.drain();
await auditRecorder.stop();
};
const heartbeatUnsub = onHeartbeatEvent((evt) => {
params.broadcast("heartbeat", evt, { dropIfSlow: true });
});
const transcriptUnsub = onInternalSessionTranscriptUpdate((evt) => {
dispatchEventHandler({
loadHandler: getTranscriptUpdateHandler,
event: evt,
log: params.log,
failureMessage: "Transcript update dispatch failed",
context: { sessionKey: evt.sessionKey }
});
});
const unsubscribeProfileChanges = onUserProfilesChanged(() => {
params.broadcastToConnIds("sessions.changed", { reason: "profile-identity" }, params.sessionEventSubscribers.getAll());
});
const unsubscribeLifecycle = onSessionLifecycleEvent((evt) => {
dispatchEventHandler({
loadHandler: getLifecycleEventHandler,
event: evt,
log: params.log,
failureMessage: "Lifecycle event dispatch failed",
context: { sessionKey: evt.sessionKey }
});
});
const unsubscribeSuspension = onGatewaySuspendAdmissionChange((phase) => {
params.broadcast("gateway.suspension", { phase });
});
const lifecycleUnsub = () => {
unsubscribeSuspension();
unsubscribeProfileChanges();
unsubscribeLifecycle();
};
let taskObserverDisposed = false;
const lastTaskSummaryById = /* @__PURE__ */ new Map();
const taskObservers = { onEvent: (event) => {
let payload;
let sessionTarget;
switch (event.kind) {
case "upserted": {
const task = mapTaskSummary(event.task);
const summary = JSON.stringify(task);
if (lastTaskSummaryById.get(task.id) === summary) return;
lastTaskSummaryById.set(task.id, summary);
payload = {
action: "upserted",
task
};
sessionTarget = resolveTaskRequesterSessionTarget(event.task);
break;
}
case "deleted":
lastTaskSummaryById.delete(event.taskId);
payload = {
action: "deleted",
taskId: event.taskId
};
sessionTarget = resolveTaskRequesterSessionTarget(event.previous);
break;
case "restored":
lastTaskSummaryById.clear();
payload = { action: "restored" };
}
params.broadcast("task", payload, {
dropIfSlow: true,
...sessionTarget ? {
sessionKeys: [sessionTarget.sessionKey],
agentId: sessionTarget.agentId
} : {}
});
const taskId = terminalTaskId(event);
if (taskId) params.terminalSessions.closeTaskSessions(taskId);
} };
const taskObserverRuntimePromise = import("./task-registry.store-C7GsX6ua.js").then((module) => {
if (!taskObserverDisposed) module.configureTaskRegistryRuntime({ observers: taskObservers });
return module;
});
taskObserverRuntimePromise.catch((error) => {
params.log.warn("Task registry observer registration failed", { error });
});
const taskUnsub = () => {
taskObserverDisposed = true;
return taskObserverRuntimePromise.then((module) => {
if (module.getTaskRegistryObservers() === taskObservers) module.configureTaskRegistryRuntime({ observers: null });
}).catch(() => void 0);
};
return {
sessionCompanion,
sessionObserver,
agentUnsub,
heartbeatUnsub,
transcriptUnsub,
lifecycleUnsub,
taskUnsub
};
}
//#endregion
export { startGatewayEventSubscriptions };