openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,149 lines (1,148 loc) • 45.4 kB
JavaScript
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { i as asOptionalObjectRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as defaultRuntime } from "./runtime-DlO3PTAd.mjs";
import { n as waitForAbortSignal } from "./abort-signal-QqAmLWTl.mjs";
import { t as GatewayScheduler } from "./gateway-scheduler-BYbkpAYn.mjs";
import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs";
import { a as resolveAgentDir, d as resolveAgentWorkspaceDir, g as resolveDefaultAgentId } from "./agent-scope-config-CDYpzA0r.mjs";
import { r as isIncognitoSessionKey } from "./session-key-CmZ4o9-l.mjs";
import { M as parseAgentSessionKey, r as agentSessionKeysMatchByRequestKey } from "./session-key-C09ow4h0.mjs";
import { g as resolveSessionAgentId } from "./agent-scope-CG17uXFn.mjs";
import { f as onAgentEvent } from "./agent-events-GvNTkbe3.mjs";
import { t as settlesWithin } from "./settle-within-CTpJLoBG.mjs";
import { r as getRuntimeConfig, x as registerConfigWriteListener } from "./io.runtime-BsUXSsD4.mjs";
import "./config-DLycWFvB.mjs";
import { t as INTERNAL_MESSAGE_CHANNEL } from "./message-channel-constants-B3vfipkP.mjs";
import "./message-channel-j-k-Nup3.mjs";
import { i as logWarn, r as logInfo } from "./logger-DskmrSVa.mjs";
import { X as clearEmbeddedPluginApprovalBroker, Y as EmbeddedPluginApprovalBroker, Z as setEmbeddedPluginApprovalBroker } from "./agent-tools.before-tool-call-DSzRS11c.mjs";
import { t as CHAT_HISTORY_MAX_ENTRIES } from "./chat-history-constants-C-H8nkgi.mjs";
import { o as getPreparedModelRuntimeAuthMaterializations } from "./prepared-model-runtime-auth-Dn7HF9Vu.mjs";
import { r as resolvePublishedModelCatalogOwner } from "./prepared-model-catalog-owner-lSB2_Cf6.mjs";
import "./model-selection-CC48fyGE.mjs";
import { r as resolveThinkingDefaultCore } from "./model-thinking-default-twlQ6xyp.mjs";
import { n as loadAgentRuntimePluginRegistryHandle } from "./runtime-plugins-C5iCn6Pz.mjs";
import { b as refreshPreparedModelRuntimeSnapshots } from "./prepared-model-runtime-BbAS5OtQ.mjs";
import { a as loadPreparedModelCatalogSnapshot, d as readPreparedModelCatalog, p as withPreparedModelCatalogOwner } from "./prepared-model-catalog-CntkE0sm.mjs";
import { i as classifyAgentRunTerminalOutcome, s as isDefinitiveRunLifecycle } from "./agent-run-terminal-outcome-Ccvy5spR.mjs";
import "./session-accessor-BDmTPdtx.mjs";
import { l as getMaxChatHistoryMessagesBytes } from "./server-constants-BH_EolwD.mjs";
import { p as applySessionPatchProjection } from "./session-accessor.reset-CwTHm9f4.mjs";
import { n as setEmbeddedMode } from "./embedded-mode--Mk71zY-.mjs";
import { n as QuestionAnswerUnconfirmedError } from "./gateway-question-dispatch-Cwgenaif.mjs";
import { r as claimPendingEmbeddedAgentQuestionAnswer, x as queueEmbeddedAgentMessageWithOutcomeAsync } from "./runs-D3elciTh.mjs";
import { i as resolveActiveEmbeddedRunSessionId } from "./active-run-projections-xWOafyEQ.mjs";
import { r as resolveTextCommand } from "./commands-registry-normalize-BXXav9x1.mjs";
import "./commands-registry-DbGlcGcR.mjs";
import { n as findAgentRunTerminalOutcome } from "./agent-run-terminal-error-ByvrtDXD.mjs";
import { i as parseGoalCommand, t as executeSessionGoalCommand } from "./commands-goal-CecrSi8w.mjs";
import { i as buildAgentRunTerminalOutcomeFromLifecycleEvent, t as AGENT_RUN_TERMINAL_RETRY_GRACE_MS } from "./agent-run-terminal-outcome-BTCs8JFs.mjs";
import { n as resolveSessionModelRef } from "./session-model-ref-3-J97GZs.mjs";
import { i as readToolValidationErrorSummary } from "./tool-error-summary-64MNk8zq.mjs";
import { m as resolveEffectiveChatHistoryMaxChars } from "./chat-display-projection.helpers-pbUdgTgb.mjs";
import { a as shouldSuppressAssistantEventForLiveChat, i as projectLiveAssistantBufferedText, r as normalizeLiveAssistantBufferedText, t as capLiveAssistantText } from "./live-chat-projector-DYrwpcEg.mjs";
import { c as mergeAssistantText, u as resolveAssistantTextInput } from "./server-chat-state-BP63MB41.mjs";
import { _ as capArrayByJsonBytes } from "./session-transcript-readers-B2XS9yB_.mjs";
import { t as ensureContextWindowCacheLoaded } from "./context-Df0Zy7Qm.mjs";
import { l as bindEmbeddedSessionRowProjection } from "./openclaw-tools-9Ow1U4nB.mjs";
import { c as getSubagentSessionListReadSnapshotIdentity, f as prepareOptionalSubagentSessionListReadCache } from "./subagent-registry-state-CVSclnHX.mjs";
import { s as resolveGatewaySessionStoreTargetWithStore, t as createGatewaySessionEntryReader } from "./session-utils-store-lookup-DugC83hw.mjs";
import { P as buildGatewaySessionRow, i as withReadySessionRows } from "./session-row-prepared-read-XTl_DJgr.mjs";
import { n as projectSessionPatchResult, t as getSessionDefaults } from "./session-utils-model-BzF2akFQ.mjs";
import { t as resolveQueueSettingsCore } from "./settings-B6NU_Y85.mjs";
import { C as previewQueueSummaryPrompt, T as waitForQueueDebounce, g as applyQueueDropPolicy, v as buildCollectPrompt } from "./state-22A-PMUF.mjs";
import { i as loadGatewaySessionEntryReadOnly, n as listAgentsForGateway, o as resolveCanonicalGatewaySessionStoreKey, r as loadGatewaySessionEntry } from "./session-utils-store-BAlWJHht.mjs";
import { n as listProjectedSessions } from "./session-utils-list-BT-CGa6u.mjs";
import "./session-utils-seGz8XhX.mjs";
import "./chat-display-projection-CkWUSNcY.mjs";
import { n as isAbortRequestText } from "./abort-primitives-CV8ivy8r.mjs";
import { t as createDefaultDeps } from "./deps-B554EEzz.mjs";
import { r as agentCommandFromIngress } from "./agent-command-DDUZ1Cjb.mjs";
import { a as createChatHistoryByteCounter, i as createChatHistoryActivityProjection, s as replaceOversizedChatHistoryMessages, t as CHAT_HISTORY_MAX_SINGLE_MESSAGE_BYTES } from "./chat-history-budget-D3ZekwOg.mjs";
import { n as enrichChatHistoryCompactionMarkers } from "./chat-history-page-kernel-DgKDawOP.mjs";
import { t as readChatHistoryPage } from "./chat-history-pages-Dwy5WUPH.mjs";
import { i as performGatewaySessionReset } from "./session-reset-service-CkY_bhRn.mjs";
import { l as formatTuiErrorMessage } from "./tui-formatters-MrekNjkx.mjs";
import { t as isAgentLifecycleYieldedWaiting } from "./agent-lifecycle-parent-state-DbD_93hn.mjs";
import { t as buildModelsListResult } from "./models-list-result-CEgmG6FH.mjs";
import { t as createGatewaySession } from "./session-create-service-WjfsziAq.mjs";
import { n as projectSessionsPatchEntry } from "./sessions-patch-DEK4SDOE.mjs";
import { t as createSessionRowProjection } from "./session-row-projection-BRJoIeFg.mjs";
import { i as setEmbeddedQuestionBroker, n as clearEmbeddedQuestionBroker, t as EmbeddedQuestionBroker } from "./embedded-question-broker-DXEhrnSW.mjs";
import { t as resolveLocalRunShutdownGraceMs } from "./local-run-shutdown-DyARw9Ur.mjs";
import { randomUUID } from "node:crypto";
//#region src/tui/embedded-chat-projection.ts
const TUI_STATE_BY_TERMINAL_CLASSIFICATION = {
success: void 0,
timeout: "error",
cancellation: "aborted",
failure: "error"
};
function resolveTerminalChatState(outcome) {
return TUI_STATE_BY_TERMINAL_CLASSIFICATION[classifyAgentRunTerminalOutcome(outcome)];
}
function assistantChatMessage(text) {
return {
role: "assistant",
content: [{
type: "text",
text
}],
timestamp: Date.now()
};
}
function projectLocalRunText(run, final = false) {
return projectLiveAssistantBufferedText(normalizeLiveAssistantBufferedText(run.buffer, {
final,
managedMediaUrls: [...run.managedMediaUrls]
}).trim(), { suppressLeadFragments: !final });
}
function payloadText(parts) {
if (!Array.isArray(parts)) return "";
return parts.map((part) => {
const payload = asOptionalObjectRecord(part);
return typeof payload?.text === "string" ? payload.text.trim() : "";
}).filter(Boolean).join("\n\n").trim();
}
function resolveDeltaPayload(text, previousText) {
if (previousText === void 0) return { deltaText: text };
if (!text.startsWith(previousText)) return {
deltaText: text,
replace: true
};
return { deltaText: text.slice(previousText.length) };
}
//#endregion
//#region src/tui/embedded-history-runtime.ts
function ensureEmbeddedHistoryRuntimePluginsLoaded(params) {
try {
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, params.sessionAgentId);
loadAgentRuntimePluginRegistryHandle({
config: params.cfg,
workspaceDir
});
return { status: "warmed" };
} catch (err) {
return {
status: "failed",
error: formatTuiErrorMessage(err)
};
}
}
//#endregion
//#region src/tui/embedded-local-run.ts
function timeoutSecondsFromMs(timeoutMs) {
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs < 0) return;
return String(Math.max(0, Math.ceil(timeoutMs / 1e3)));
}
function buildLocalQueuedPrompt(queue) {
return [previewQueueSummaryPrompt({
state: queue,
noun: "message"
}), queue.mode === "collect" && queue.messages.length > 1 ? buildCollectPrompt({
title: "[Queued messages while agent was busy]",
items: queue.messages,
renderItem: (message, index) => `---\nQueued #${index + 1}\n${message}`
}) : queue.messages[0] ?? ""].filter(Boolean).join("\n\n");
}
async function waitForLocalRunShutdown(promises) {
if (promises.length === 0) return true;
const timeoutMs = resolveLocalRunShutdownGraceMs();
if (timeoutMs <= 0) return false;
return await settlesWithin(Promise.allSettled(promises), timeoutMs);
}
async function waitForQueuedLocalRun(previousRun, runId) {
await previousRun.run.queuedRunReady;
if (previousRun.run.controller.signal.aborted && previousRun.run.queuedAfter) return await waitForQueuedLocalRun(previousRun.run.queuedAfter, runId);
if (!previousRun.run.finishing && !previousRun.run.lifecycleEnded) {
await previousRun.promise;
return;
}
const timeoutMs = resolveLocalRunShutdownGraceMs();
if (timeoutMs <= 0 || !await settlesWithin(previousRun.promise, timeoutMs)) throw new Error(`timed out waiting for previous local run to finish post-turn maintenance for ${runId}`);
}
//#endregion
//#region src/tui/embedded-prepared-runtime.ts
var EmbeddedPreparedModelRuntimeHost = class {
constructor() {
this.ready = Promise.resolve();
}
publish(config) {
this.ready = refreshPreparedModelRuntimeSnapshots(config);
this.ready.catch(() => void 0);
}
async waitUntilReady() {
for (;;) {
const ready = this.ready;
await ready;
if (ready === this.ready) return;
}
}
};
//#endregion
//#region src/tui/embedded-session-reader.ts
function readEmbeddedHistorySessionInfo(projection, target, identity) {
return withReadySessionRows(projection, () => [target], (read) => {
const current = read.describe(target);
return current && current.entry.sessionId === identity.sessionId && current.entry.lifecycleRevision === identity.lifecycleRevision ? read.present(current) : void 0;
});
}
function createEmbeddedSessionReader(lifecycle) {
const read = async (opts, key) => {
await lifecycle.ready();
const publication = lifecycle.projection();
const projection = await publication;
if (!projection || publication !== lifecycle.projection()) throw new Error("Embedded session projection is unavailable");
const result = await listProjectedSessions({
projection,
opts: opts ?? {},
...key !== void 0 ? { key } : {}
});
if (publication !== lifecycle.projection()) throw new Error("Embedded session projection is unavailable");
return result;
};
return {
listSessions: (opts) => read(opts),
async describeSession(opts) {
const selected = parseAgentSessionKey(opts.sessionKey);
const result = await read({
agentId: opts.agentId ?? selected?.agentId,
includeGlobal: opts.sessionKey === "global" || selected?.rest === "global",
includeUnknown: opts.sessionKey === "unknown" || selected?.rest === "unknown",
limit: 1
}, opts.sessionKey);
return {
session: result.sessions[0] ?? null,
defaults: result.defaults
};
}
};
}
//#endregion
//#region src/tui/embedded-backend.ts
const silentRuntime = {
log: (..._args) => void 0,
error: (..._args) => void 0,
exit: (code) => {
throw new Error(`embedded tui runtime exit ${String(code)}`);
}
};
const embeddedSessionStartupMigrationLog = {
info: (message) => logInfo(message, silentRuntime),
warn: (message) => logWarn(message, silentRuntime)
};
var EmbeddedTuiBackend = class {
constructor() {
this.connection = { url: "local embedded" };
this.deps = createDefaultDeps();
this.runs = /* @__PURE__ */ new Map();
this.runPromises = /* @__PURE__ */ new Map();
this.seq = 0;
this.pendingLifecycleErrors = /* @__PURE__ */ new Map();
this.pluginApprovalBroker = new EmbeddedPluginApprovalBroker();
this.scheduler = new GatewayScheduler();
this.questionBroker = new EmbeddedQuestionBroker(this.scheduler);
this.preparedModelRuntime = new EmbeddedPreparedModelRuntimeHost();
this.ready = Promise.resolve();
this.sessionReader = createEmbeddedSessionReader({
ready: () => this.ready,
projection: () => this.sessionProjection
});
this.listSessions = this.sessionReader.listSessions;
this.describeSession = this.sessionReader.describeSession;
}
start() {
if (this.unsubscribe) return;
setEmbeddedMode(true);
ensureContextWindowCacheLoaded();
this.previousRuntimeLog = defaultRuntime.log;
this.previousRuntimeError = defaultRuntime.error;
defaultRuntime.log = silentRuntime.log;
defaultRuntime.error = silentRuntime.error;
this.unsubscribe = onAgentEvent((evt) => this.handleAgentEvent(evt));
setEmbeddedPluginApprovalBroker(this.pluginApprovalBroker);
this.unsubscribePluginApprovals = this.pluginApprovalBroker.subscribe((event) => {
this.emit(event.event, event.payload);
});
setEmbeddedQuestionBroker(this.questionBroker);
this.unsubscribeQuestions = this.questionBroker.subscribe((event) => {
this.emit(event.event, event.payload);
});
const config = getRuntimeConfig();
this.sessionProjection = (async () => {
const { runSessionStartupMigration } = await import("./startup-migration-DgTpX2AW.mjs");
await runSessionStartupMigration({
cfg: config,
env: process.env,
log: embeddedSessionStartupMigrationLog
});
this.unsubscribeConfigWrites = registerConfigWriteListener((event) => {
this.preparedModelRuntime.publish(event.runtimeConfig);
});
this.preparedModelRuntime.publish(getRuntimeConfig());
return createSessionRowProjection({
cfg: getRuntimeConfig(),
getConfig: getRuntimeConfig
});
})();
this.ready = this.sessionProjection.then(() => {});
this.unbindSessionProjection = bindEmbeddedSessionRowProjection(this.sessionProjection);
queueMicrotask(() => {
this.onConnected?.();
});
}
async stop() {
this.scheduler.beginClose();
clearEmbeddedPluginApprovalBroker(this.pluginApprovalBroker);
this.unsubscribePluginApprovals?.();
this.unsubscribePluginApprovals = void 0;
clearEmbeddedQuestionBroker(this.questionBroker);
this.unsubscribeQuestions?.();
this.unsubscribeQuestions = void 0;
const maintenancePromises = [];
for (const [runId, run] of this.runs) {
if (run.finishing || run.lifecycleEnded) {
const promise = this.runPromises.get(runId);
if (promise) maintenancePromises.push(promise);
continue;
}
run.controller.abort();
}
this.pluginApprovalBroker.stop();
this.questionBroker.stop();
await this.scheduler.stop();
if (!await waitForLocalRunShutdown(maintenancePromises)) {
for (const run of this.runs.values()) if (run.finishing || run.lifecycleEnded) run.controller.abort();
}
this.unbindSessionProjection?.();
this.unbindSessionProjection = void 0;
const projection = this.sessionProjection;
this.sessionProjection = void 0;
await projection?.catch(() => void 0).then((value) => value?.dispose());
this.unsubscribeConfigWrites?.();
this.unsubscribeConfigWrites = void 0;
this.unsubscribe?.();
this.unsubscribe = void 0;
this.pendingLifecycleErrors.forEach(clearTimeout);
this.pendingLifecycleErrors.clear();
for (const run of this.runs.values()) run.controller.abort();
this.runs.clear();
this.runPromises.clear();
defaultRuntime.log = this.previousRuntimeLog ?? defaultRuntime.log;
defaultRuntime.error = this.previousRuntimeError ?? defaultRuntime.error;
this.previousRuntimeLog = void 0;
this.previousRuntimeError = void 0;
setEmbeddedMode(false);
await this.preparedModelRuntime.waitUntilReady();
}
async sendChat(opts) {
await this.ready;
await this.preparedModelRuntime.waitUntilReady();
const runId = opts.runId ?? randomUUID();
const question = /^\/(?:btw|side)(?::|\s)+(.*)$/i.exec(opts.message.trim())?.[1]?.trim() || void 0;
const isQueueCommand = resolveTextCommand(opts.message)?.command.key === "queue";
const agentId = resolveSessionAgentId({
sessionKey: opts.sessionKey,
config: getRuntimeConfig(),
agentId: opts.agentId
});
const runScope = {
sessionKey: opts.sessionKey,
agentId
};
const stopCommand = this.hasAbortableSessionRun(runScope) && isAbortRequestText(opts.message);
const queuedAfter = question || stopCommand || isQueueCommand ? void 0 : this.findQueuedSessionRunPromise(runScope);
if (stopCommand) {
this.abortSessionRuns(runScope);
return { runId };
}
let pendingQueue;
if (queuedAfter) {
const loadOptions = opts.agentId ? { agentId: opts.agentId } : void 0;
const { cfg, canonicalKey, entry } = loadGatewaySessionEntry(opts.sessionKey, loadOptions);
const activeSessionId = resolveActiveEmbeddedRunSessionId(canonicalKey);
if (activeSessionId) {
const claimed = await claimPendingEmbeddedAgentQuestionAnswer(activeSessionId, opts.message);
if (claimed) return claimed;
}
let queueSettings = resolveQueueSettingsCore({
cfg,
channel: INTERNAL_MESSAGE_CHANNEL,
sessionEntry: entry
});
if (queueSettings.mode === "steer") {
if (activeSessionId) {
if ((await queueEmbeddedAgentMessageWithOutcomeAsync(activeSessionId, opts.message, {
steeringMode: "all",
debounceMs: queueSettings.debounceMs ?? 500,
isInboundUserMessage: true
}).catch((error) => {
if (error instanceof QuestionAnswerUnconfirmedError) throw error;
}))?.queued) return { runId: queuedAfter.runId };
}
queueSettings = {
...queueSettings,
mode: "followup"
};
}
if (queueSettings.mode === "interrupt") this.abortSessionRuns(runScope);
else {
const queued = this.enqueuePendingLocalMessage({
runScope,
message: opts.message,
settings: queueSettings,
fallbackRunId: queuedAfter.runId
});
if (queued.kind === "handled") return { runId: queued.runId };
pendingQueue = queued.queue;
}
}
const controller = new AbortController();
const queuedRunReadiness = createDeferredCore();
this.runs.set(runId, {
sessionKey: opts.sessionKey,
agentId,
controller,
buffer: "",
managedMediaUrls: /* @__PURE__ */ new Set(),
question,
finishing: false,
lifecycleEnded: false,
registered: false,
...pendingQueue ? { pendingQueue } : {},
...queuedAfter ? { queuedAfter } : {},
queuedRunReady: queuedRunReadiness.promise,
markQueuedRunReady: queuedRunReadiness.resolve
});
const runPromise = this.runTurn({
runId,
sessionKey: opts.sessionKey,
agentId: opts.agentId,
message: opts.message,
thinking: opts.thinking,
deliver: opts.deliver,
timeoutMs: opts.timeoutMs,
controller,
queuedAfter
});
this.runPromises.set(runId, runPromise);
runPromise.finally(() => {
this.runPromises.delete(runId);
});
if (isQueueCommand) await runPromise;
return { runId };
}
async abortChat(opts) {
const runIds = [];
const candidates = opts.runId ? [[opts.runId, this.runs.get(opts.runId)]] : this.runs;
for (const [runId, run] of candidates) {
if (!run || !opts.runId && run.question || run.sessionKey !== opts.sessionKey) continue;
if (opts.sessionKey === "global") {
const defaultAgentId = opts.agentId && run.agentId ? void 0 : resolveDefaultAgentId(getRuntimeConfig());
const requestedAgentId = opts.agentId ? normalizeAgentId(opts.agentId) : defaultAgentId;
if ((run.agentId ? normalizeAgentId(run.agentId) : defaultAgentId) !== requestedAgentId) continue;
}
if (!this.isAbortableRun(runId, run)) continue;
run.controller.abort();
runIds.push(runId);
}
return {
ok: true,
aborted: runIds.length > 0,
runIds
};
}
async loadImage(opts) {
const { loadEmbeddedImage } = await import("./embedded-image-loader-CI1lHj8U.mjs");
return await loadEmbeddedImage(opts);
}
async loadHistory(opts) {
await this.ready;
await this.preparedModelRuntime.waitUntilReady();
if (!getSubagentSessionListReadSnapshotIdentity()) await prepareOptionalSubagentSessionListReadCache();
const loadOptions = opts.agentId ? { agentId: opts.agentId } : void 0;
const selected = loadGatewaySessionEntryReadOnly(opts.sessionKey, {
...loadOptions,
includeStoreChildEntries: true
});
const { cfg, agentId: sessionAgentId, storePath, store, readSource, entry, canonicalKey } = selected;
const sessionId = entry?.sessionId;
const runtimePluginsPrewarm = ensureEmbeddedHistoryRuntimePluginsLoaded({
cfg,
sessionAgentId
});
const resolvedSessionModel = resolveSessionModelRef(cfg, entry, sessionAgentId);
const max = Math.min(CHAT_HISTORY_MAX_ENTRIES, typeof opts.limit === "number" ? opts.limit : 200);
const maxHistoryBytes = getMaxChatHistoryMessagesBytes();
const effectiveMaxChars = resolveEffectiveChatHistoryMaxChars();
const historyPage = await readChatHistoryPage({
entry,
provider: resolvedSessionModel.provider,
sessionId,
storePath,
sessionAgentId,
canonicalKey,
max,
maxHistoryBytes,
effectiveMaxChars,
offset: void 0,
messageId: void 0
});
const normalized = enrichChatHistoryCompactionMarkers(historyPage.messages, entry);
const activity = createChatHistoryActivityProjection(normalized, historyPage.activity);
const byteCounter = createChatHistoryByteCounter(activity);
const replaced = replaceOversizedChatHistoryMessages({
messages: normalized,
byteCounter,
maxSingleMessageBytes: Math.min(CHAT_HISTORY_MAX_SINGLE_MESSAGE_BYTES, maxHistoryBytes)
});
const messages = capArrayByJsonBytes(replaced.messages, maxHistoryBytes - byteCounter.framingBytes(replaced.messages), byteCounter.messageBytes).items;
const newestInFlightRun = [...this.runs.entries()].findLast(([, run]) => !run.question && run.terminalState !== "final" && agentSessionKeysMatchByRequestKey(run.sessionKey, opts.sessionKey) && normalizeAgentId(run.agentId) === normalizeAgentId(sessionAgentId));
const inFlightRun = newestInFlightRun ? {
runId: newestInFlightRun[0],
text: projectLocalRunText(newestInFlightRun[1]).text.trim()
} : void 0;
let thinkingLevel = entry?.thinkingLevel;
if (!thinkingLevel) {
const catalog = await readPreparedModelCatalog({
config: cfg,
agentId: sessionAgentId,
readOnly: true
});
thinkingLevel = resolveThinkingDefaultCore({
cfg,
agentId: sessionAgentId,
provider: resolvedSessionModel.provider,
model: resolvedSessionModel.model,
catalog
});
}
const defaults = getSessionDefaults(cfg, void 0, { allowPluginNormalization: false });
const projection = await this.sessionProjection;
const target = {
key: canonicalKey,
agentId: sessionAgentId,
storePath: readSource?.path ?? storePath
};
const sessionInfo = entry && (entry.incognito || isIncognitoSessionKey(canonicalKey)) ? buildGatewaySessionRow({
cfg,
storePath,
store,
key: canonicalKey,
entry,
agentId: sessionAgentId,
modelSource: {
entry,
readSourceEntry: createGatewaySessionEntryReader(selected)
},
lightweightListRow: true,
skipTranscriptUsageFallback: true
}) : entry && projection ? await readEmbeddedHistorySessionInfo(projection, target, {
sessionId,
lifecycleRevision: entry.lifecycleRevision
}) : void 0;
const verboseLevel = entry?.verboseLevel ?? cfg.agents?.defaults?.verboseDefault;
if (sessionInfo) {
sessionInfo.thinkingLevel = thinkingLevel;
sessionInfo.verboseLevel = verboseLevel;
}
return {
sessionKey: opts.sessionKey,
sessionId,
messages,
defaults,
activity: messages.flatMap((message) => activity.get(message) ?? []),
...sessionInfo ? { sessionInfo } : {},
thinkingLevel,
fastMode: entry?.fastMode,
verboseLevel,
runtimePluginsPrewarm,
...inFlightRun ? { inFlightRun } : {}
};
}
async listAgents() {
return await listAgentsForGateway(getRuntimeConfig());
}
async patchSession(opts) {
await this.ready;
await this.preparedModelRuntime.waitUntilReady();
const cfg = getRuntimeConfig();
const target = resolveGatewaySessionStoreTargetWithStore({
cfg,
key: opts.key,
agentId: opts.agentId,
exactRead: true
});
const applied = await applySessionPatchProjection({
...opts.label === void 0 ? { sessionKeys: target.storeKeys } : {},
storePath: target.storePath,
resolveTarget: ({ store }) => {
const { target: migratedTarget, primaryKey } = resolveCanonicalGatewaySessionStoreKey({
cfg,
key: opts.key,
store,
agentId: opts.agentId
});
return {
primaryKey,
candidateKeys: migratedTarget.storeKeys
};
},
project: async ({ primaryKey, existingEntry, isLabelInUse }) => await projectSessionsPatchEntry({
cfg,
existingEntry,
isLabelInUse,
storeKey: primaryKey,
agentId: target.agentId,
patch: opts,
loadGatewayModelCatalogSnapshot: () => loadPreparedModelCatalogSnapshot({
config: cfg,
agentId: target.agentId,
readOnly: true
})
})
});
if (!applied.ok) throw new Error(applied.error.message);
const projected = projectSessionPatchResult({
canonicalKey: target.canonicalKey ?? opts.key,
cfg,
entry: applied.entry,
storePath: target.storePath,
targetAgentId: target.agentId
});
return {
...projected,
entry: { ...projected.entry }
};
}
async resetSession(key, reason, opts) {
await this.ready;
if (loadGatewaySessionEntryReadOnly(key, opts).entry?.incognito === true) throw new Error("Incognito sessions cannot reset in place.");
const result = await performGatewaySessionReset({
key,
operatorRoleActor: { kind: "system" },
...opts?.agentId ? { agentId: opts.agentId } : {},
reason: reason === "new" ? "new" : "reset",
commandSource: "tui:embedded",
armSessionDiffBaselineCapture: true
});
if (!result.ok) throw new Error(result.error.message);
if ("incognitoDeleted" in result) return {
ok: true,
key: result.key,
deleted: true
};
return {
ok: true,
key: result.key,
entry: result.entry,
resolved: result.resolved
};
}
async createSession(opts) {
await this.ready;
await this.preparedModelRuntime.waitUntilReady();
const cfg = getRuntimeConfig();
const result = await createGatewaySession({
cfg,
operatorRoleActor: { kind: "system" },
...opts,
creation: {
via: "operator",
actor: {
type: "human",
source: "unknown"
}
},
armSessionDiffBaselineCapture: true,
emitCommandHooks: Boolean(opts.parentSessionKey),
commandSource: "tui:embedded",
loadGatewayModelCatalogSnapshot: () => loadPreparedModelCatalogSnapshot({
config: cfg,
agentId: resolveSessionAgentId({
sessionKey: opts.key,
config: cfg,
agentId: opts.agentId
}),
readOnly: true
})
});
if (!result.ok) throw new Error(result.error.message);
return {
ok: true,
key: result.key,
entry: result.entry,
resolved: result.resolved
};
}
async runBtwTurn(params) {
const loadOptions = params.agentId ? { agentId: params.agentId } : void 0;
const { cfg, agentId: sessionAgentId, canonicalKey, storePath, store, entry } = loadGatewaySessionEntry(params.sessionKey, loadOptions);
if (!entry?.sessionId) throw new Error("/btw requires an active session with existing context.");
const resolvedModel = resolveSessionModelRef(cfg, entry, sessionAgentId);
const timeoutSeconds = timeoutSecondsFromMs(params.timeoutMs);
const { runBtwSideQuestion } = await import("./btw-D2tlCy6l.mjs");
const reply = await runBtwSideQuestion({
cfg,
agentId: sessionAgentId,
agentDir: resolveAgentDir(cfg, sessionAgentId),
provider: resolvedModel.provider,
model: resolvedModel.model,
question: params.question,
sessionEntry: entry,
sessionStore: store,
sessionKey: canonicalKey,
storePath,
resolvedThinkLevel: "off",
resolvedReasoningLevel: "off",
opts: {
runId: params.runId,
abortSignal: params.controller.signal,
...timeoutSeconds !== void 0 ? { timeoutOverrideSeconds: Number(timeoutSeconds) } : {}
},
isNewSession: false,
messageChannel: INTERNAL_MESSAGE_CHANNEL,
messageProvider: INTERNAL_MESSAGE_CHANNEL,
currentChannelId: INTERNAL_MESSAGE_CHANNEL
});
const text = reply?.text?.trim() ?? "";
if (!text) throw new Error("/btw produced no answer.");
return {
sessionKey: canonicalKey,
text,
isError: reply?.isError === true
};
}
async getGatewayStatus() {
return `local embedded mode${this.runs.size > 0 ? ` (${String(this.runs.size)} active run${this.runs.size === 1 ? "" : "s"})` : ""}`;
}
async listPluginApprovals() {
return this.pluginApprovalBroker.listPending();
}
async listQuestions() {
return this.questionBroker.list();
}
async getQuestion(id) {
return this.questionBroker.get({ id });
}
async resolveQuestion(params) {
return this.questionBroker.resolve(params);
}
async resolvePluginApproval(id, decision) {
return { ok: this.pluginApprovalBroker.resolve(id, decision) };
}
async listModels(opts) {
await this.ready;
await this.preparedModelRuntime.waitUntilReady();
const cfg = getRuntimeConfig();
const agentId = opts?.agentId ?? resolveDefaultAgentId(cfg);
return await withPreparedModelCatalogOwner({
config: cfg,
agentId,
readOnly: true
}, async (snapshot) => (await buildModelsListResult({
source: {
kind: "published",
owner: {
...resolvePublishedModelCatalogOwner(snapshot),
authMaterializations: getPreparedModelRuntimeAuthMaterializations(snapshot)
}
},
agentId,
params: { includeDetails: true }
})).models);
}
async runGoalCommand(opts) {
await this.ready;
const loadOptions = opts.agentId ? { agentId: opts.agentId } : void 0;
const { agentId, canonicalKey, storePath, entry } = loadGatewaySessionEntry(opts.sessionKey, loadOptions);
const parsed = parseGoalCommand(opts.command.trim());
if (!parsed) throw new Error("invalid goal command");
const result = await executeSessionGoalCommand({
parsed,
sessionKey: canonicalKey,
storePath,
fallbackEntry: entry ?? {
sessionId: randomUUID(),
updatedAt: Date.now()
},
agentId
});
return result.continuationPrompt ? {
text: result.text,
continuationPrompt: result.continuationPrompt
} : { text: result.text };
}
async runUsageCostCommand(opts) {
await this.ready;
const { cfg, agentId, canonicalKey, storePath, entry } = loadGatewaySessionEntry(opts.sessionKey, opts.agentId ? { agentId: opts.agentId } : void 0);
const { formatSessionUsageCostSummary } = await import("./commands-session-cost.runtime.js");
return { text: await formatSessionUsageCostSummary({
cfg,
sessionKey: canonicalKey,
agentId,
sessionEntry: entry,
storePath
}) };
}
enqueuePendingLocalMessage(params) {
const pendingMessages = this.listPendingLocalMessages(params.runScope);
const overflowQueue = {
items: [...pendingMessages],
cap: params.settings.cap ?? 20,
dropPolicy: params.settings.dropPolicy ?? "summarize",
droppedCount: 0,
summaryLines: []
};
if (!applyQueueDropPolicy({
queue: overflowQueue,
summarize: (item) => item.message
})) return {
kind: "handled",
runId: params.fallbackRunId
};
const retained = new Set(overflowQueue.items);
const droppedByRun = /* @__PURE__ */ new Map();
for (const dropped of pendingMessages) {
if (retained.has(dropped)) continue;
const indices = droppedByRun.get(dropped.run) ?? [];
indices.push(dropped.messageIndex);
droppedByRun.set(dropped.run, indices);
}
const inheritedSummaryLines = [];
for (const [run, indices] of droppedByRun) {
for (const index of indices.toSorted((a, b) => b - a)) run.pendingQueue?.messages.splice(index, 1);
if (run.pendingQueue?.messages.length === 0) {
inheritedSummaryLines.push(...run.pendingQueue.summaryLines);
overflowQueue.droppedCount += run.pendingQueue.droppedCount;
run.controller.abort();
}
}
overflowQueue.summaryLines.unshift(...inheritedSummaryLines);
if (overflowQueue.summaryLines.length > overflowQueue.cap) overflowQueue.summaryLines.splice(0, overflowQueue.summaryLines.length - overflowQueue.cap);
const enqueuedAt = Date.now();
for (const run of this.runs.values()) {
if (!this.isSameRunScope(run, params.runScope) || !run.pendingQueue) continue;
run.pendingQueue.lastEnqueuedAt = enqueuedAt;
run.pendingQueue.debounceMs = params.settings.debounceMs ?? 500;
}
if (params.settings.mode === "collect") {
const target = [...this.runs.entries()].findLast(([, run]) => this.isSameRunScope(run, params.runScope) && run.pendingQueue);
const targetQueue = target?.[1].pendingQueue;
if (target && targetQueue?.mode === "collect" && !target[1].controller.signal.aborted) {
const [targetRunId] = target;
targetQueue.messages.push(params.message);
targetQueue.dropPolicy = params.settings.dropPolicy ?? "summarize";
targetQueue.droppedCount += overflowQueue.droppedCount;
targetQueue.summaryLines.push(...overflowQueue.summaryLines);
return {
kind: "handled",
runId: targetRunId
};
}
}
return {
kind: "enqueue",
queue: {
mode: params.settings.mode === "collect" ? "collect" : "followup",
messages: [params.message],
debounceMs: params.settings.debounceMs ?? 500,
lastEnqueuedAt: enqueuedAt,
dropPolicy: params.settings.dropPolicy ?? "summarize",
droppedCount: overflowQueue.droppedCount,
summaryLines: overflowQueue.summaryLines
}
};
}
listPendingLocalMessages(params) {
const pending = [];
for (const run of this.runs.values()) {
if (!this.isSameRunScope(run, params) || !run.pendingQueue) continue;
run.pendingQueue.messages.forEach((message, messageIndex) => {
pending.push({
run,
messageIndex,
message
});
});
}
return pending;
}
findQueuedSessionRunPromise(params) {
let queuedAfter;
for (const [runId, run] of this.runs) if (this.isSameRunScope(run, params) && !run.question) {
const promise = this.runPromises.get(runId);
if (promise) queuedAfter = {
runId,
run,
promise
};
}
return queuedAfter;
}
abortSessionRuns(params) {
for (const [runId, run] of this.runs) if (this.isSameRunScope(run, params) && !run.question && this.isAbortableRun(runId, run)) run.controller.abort();
}
hasAbortableSessionRun(params) {
for (const [runId, run] of this.runs) if (this.isSameRunScope(run, params) && !run.question && this.isAbortableRun(runId, run)) return true;
return false;
}
isSameRunScope(run, params) {
return run.sessionKey === params.sessionKey && (params.sessionKey !== "global" || run.agentId === params.agentId);
}
isAbortableRun(runId, run) {
return !run.lifecycleEnded || this.runPromises.has(runId);
}
emit(event, payload) {
this.onEvent?.({
event,
payload,
seq: ++this.seq
});
}
clearPendingLifecycleError(runId) {
clearTimeout(this.pendingLifecycleErrors.get(runId));
this.pendingLifecycleErrors.delete(runId);
}
scheduleChatError(runId, run, errorMessage) {
this.clearPendingLifecycleError(runId);
const timer = setTimeout(() => {
this.pendingLifecycleErrors.delete(runId);
this.emitChatTerminal(runId, run, "error", errorMessage, "provisional");
}, AGENT_RUN_TERMINAL_RETRY_GRACE_MS);
timer.unref?.();
this.pendingLifecycleErrors.set(runId, timer);
}
emitChatDelta(runId, run) {
const projected = projectLocalRunText(run);
const text = projected.text.trim();
if (run.buffer && (!text || projected.suppress)) return;
const deltaPayload = resolveDeltaPayload(text, run.lastBroadcastText);
if (!deltaPayload.deltaText && !deltaPayload.replace) return;
run.registered = true;
run.lastBroadcastText = text;
this.emit("chat", {
runId,
sessionKey: run.sessionKey,
agentId: run.agentId,
state: "delta",
...deltaPayload,
message: assistantChatMessage(text)
});
}
emitChatTerminal(runId, run, state, detail, terminalState = "final") {
this.clearPendingLifecycleError(runId);
if (run.terminalState === "final" || run.terminalState === terminalState) return;
run.terminalState = terminalState;
if (terminalState === "final") {
run.markQueuedRunReady();
run.finishing = false;
run.lifecycleEnded = true;
}
run.registered = true;
run.lastBroadcastText = void 0;
const projected = projectLocalRunText(run, true);
const text = state === "final" && !projected.suppress ? projected.text.trim() : "";
this.emit("chat", {
runId,
sessionKey: run.sessionKey,
agentId: run.agentId,
state,
...state === "final" && detail ? { stopReason: detail } : {},
...state === "final" && run.lifecycleYielded ? { yielded: true } : {},
...text ? { message: assistantChatMessage(text) } : {},
...state !== "final" && (detail || state === "aborted" && run.toolErrorSummary) ? { errorMessage: formatTuiErrorMessage(detail ?? run.toolErrorSummary) } : {}
});
}
projectTerminalOutcome(runId, run, metadata, options = {}) {
const terminalError = metadata.error && typeof metadata.error === "object" && "message" in metadata.error ? metadata.error.message : metadata.error;
const outcome = options.terminalOutcome ?? buildAgentRunTerminalOutcomeFromLifecycleEvent({
phase: metadata.phase === "error" || terminalError ? "error" : "end",
data: {
...metadata,
error: terminalError ? formatTuiErrorMessage(terminalError) : void 0
},
abortSignal: run.controller.signal
});
const state = resolveTerminalChatState(outcome);
if (!state) return false;
const diagnostic = state === "aborted" ? readToolValidationErrorSummary(metadata.toolErrorSummary) : outcome.reason === "failed" && options.visibleText || outcome.error || (outcome.status === "timeout" ? "The provider timed out. Please try again." : "Agent run failed.");
if (metadata.phase === "error" && !isDefinitiveRunLifecycle({
phase: "error",
data: metadata
})) this.scheduleChatError(runId, run, diagnostic);
else this.emitChatTerminal(runId, run, state, diagnostic);
return true;
}
ensureRunRegistered(runId, run) {
if (run.registered || run.question) return;
run.registered = true;
run.lastBroadcastText = "";
this.emit("chat", {
runId,
sessionKey: run.sessionKey,
agentId: run.agentId,
state: "delta",
deltaText: "",
message: assistantChatMessage("")
});
}
handleAgentEvent(evt) {
const run = this.runs.get(evt.runId);
if (!run) return;
const lifecyclePhase = evt.stream === "lifecycle" && typeof evt.data?.phase === "string" ? evt.data.phase : "";
if (evt.stream !== "lifecycle" || lifecyclePhase !== "error") this.clearPendingLifecycleError(evt.runId);
if (evt.stream !== "assistant") this.ensureRunRegistered(evt.runId, run);
this.emit("agent", {
runId: evt.runId,
sessionKey: run.sessionKey,
agentId: run.agentId,
stream: evt.stream,
data: evt.data
});
if (evt.stream === "assistant" || evt.stream === "tool" && evt.data?.phase === "start") run.toolErrorSummary = void 0;
else if (evt.stream === "tool" && evt.data?.phase === "result") run.toolErrorSummary = readToolValidationErrorSummary(evt.data.toolErrorSummary);
const assistantLiveChatInput = evt.stream === "assistant" ? resolveAssistantTextInput(evt.data) : void 0;
if (assistantLiveChatInput && !run.question && !shouldSuppressAssistantEventForLiveChat(evt.data)) {
for (const url of assistantLiveChatInput.managedMediaUrls ?? []) run.managedMediaUrls.add(url);
const snapshot = mergeAssistantText({
text: run.buffer,
scope: run.assistantScope
}, assistantLiveChatInput, "live");
run.assistantScope = snapshot.scope;
run.buffer = capLiveAssistantText(snapshot);
this.emitChatDelta(evt.runId, run);
return;
}
if (evt.stream !== "lifecycle") return;
const phase = lifecyclePhase;
if (phase === "finishing") {
run.finishing = true;
run.markQueuedRunReady();
run.lifecycleStopReason = typeof evt.data?.stopReason === "string" ? evt.data.stopReason : void 0;
return;
}
if (phase !== "end" && phase !== "error") return;
run.finishing = false;
if (phase === "error") {
run.buffer = "";
delete run.assistantScope;
}
if (this.projectTerminalOutcome(evt.runId, run, evt.data)) return;
run.lifecycleEnded = true;
run.markQueuedRunReady();
run.lifecycleStopReason = typeof evt.data?.stopReason === "string" ? evt.data.stopReason : void 0;
run.lifecycleYielded = isAgentLifecycleYieldedWaiting(evt.data);
}
async runTurn(params) {
try {
const recheckPreparedRuntimeAtAdmission = params.queuedAfter !== void 0;
if (params.queuedAfter) {
try {
await Promise.race([waitForQueuedLocalRun(params.queuedAfter, params.runId), waitForAbortSignal(params.controller.signal)]);
} catch (error) {
const run = this.runs.get(params.runId);
if (run) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.emitChatTerminal(params.runId, run, "error", `previous run did not finish cleanly: ${errorMessage}`);
}
return;
}
if (params.controller.signal.aborted) {
const run = this.runs.get(params.runId);
if (run) this.emitChatTerminal(params.runId, run, "aborted");
return;
}
}
const activeRun = this.runs.get(params.runId);
delete activeRun?.queuedAfter;
let message = params.message;
if (activeRun?.pendingQueue) {
await waitForQueueDebounce(activeRun.pendingQueue, params.controller.signal);
if (params.controller.signal.aborted) {
this.emitChatTerminal(params.runId, activeRun, "aborted");
return;
}
message = buildLocalQueuedPrompt(activeRun.pendingQueue);
delete activeRun.pendingQueue;
}
if (recheckPreparedRuntimeAtAdmission) {
await this.preparedModelRuntime.waitUntilReady();
if (params.controller.signal.aborted) {
if (activeRun) this.emitChatTerminal(params.runId, activeRun, "aborted");
return;
}
}
if (activeRun?.question) {
const result = await this.runBtwTurn({
runId: params.runId,
sessionKey: params.sessionKey,
...params.agentId ? { agentId: params.agentId } : {},
question: activeRun.question,
timeoutMs: params.timeoutMs,
controller: params.controller
});
const run = this.runs.get(params.runId);
if (!run) return;
if (params.controller.signal.aborted) {
this.emitChatTerminal(params.runId, run, "aborted");
return;
}
this.emit("chat.side_result", {
kind: "btw",
runId: params.runId,
sessionKey: result.sessionKey,
agentId: run.agentId,
question: run.question,
text: result.text,
...result.isError ? { isError: true } : {}
});
this.emitChatTerminal(params.runId, run, "final");
return;
}
const loadOptions = params.agentId ? { agentId: params.agentId } : void 0;
const { agentId, canonicalKey, entry } = loadGatewaySessionEntry(params.sessionKey, loadOptions);
const result = await agentCommandFromIngress({
message,
sessionKey: canonicalKey,
agentId,
...entry?.sessionId ? { sessionId: entry.sessionId } : {},
thinking: params.thinking,
deliver: params.deliver,
channel: INTERNAL_MESSAGE_CHANNEL,
runContext: { messageChannel: INTERNAL_MESSAGE_CHANNEL },
timeout: timeoutSecondsFromMs(params.timeoutMs),
runId: params.runId,
abortSignal: params.controller.signal,
allowModelOverride: false
}, silentRuntime, this.deps);
const run = this.runs.get(params.runId);
if (!run) return;
if (this.projectTerminalOutcome(params.runId, run, result?.meta ?? {}, { visibleText: payloadText(result?.payloads) })) return;
run.lifecycleYielded ||= isAgentLifecycleYieldedWaiting({
phase: "end",
...result?.meta
});
if (run.terminalState !== "final") {
const finalText = payloadText(result?.payloads);
if (finalText) run.buffer = finalText;
const stopReason = run.lifecycleStopReason ?? (typeof result?.meta?.stopReason === "string" ? result.meta.stopReason : void 0);
this.emitChatTerminal(params.runId, run, "final", stopReason);
}
} catch (error) {
const run = this.runs.get(params.runId);
if (!run) return;
const errorMessage = error instanceof Error ? error.message : String(error);
const outcome = findAgentRunTerminalOutcome(error);
this.projectTerminalOutcome(params.runId, run, outcome ?? {
status: "error",
error: errorMessage
}, outcome ? { terminalOutcome: outcome } : {});
} finally {
this.runs.get(params.runId)?.markQueuedRunReady();
this.runs.delete(params.runId);
}
}
};
//#endregion
export { EmbeddedTuiBackend };