UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

779 lines (778 loc) 26.8 kB
import { n as defaultRuntime } from "./runtime-B4lgFmsS.js"; import { v as resolveSessionAgentId } from "./agent-scope-MrLta7Pq.js"; import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js"; import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js"; import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js"; import { r as DEFAULT_PROVIDER } from "./defaults-mDjiWzE5.js"; import "./config-C9RxTsn1.js"; import { l as onAgentEvent } from "./agent-events-C1B8VhOg.js"; import { t as INTERNAL_MESSAGE_CHANNEL } from "./message-channel-constants-CgI32lqD.js"; import { d as updateSessionStore } from "./store-Qsgtu-0y.js"; import "./message-channel-BiOeMu0l.js"; import { t as loadCombinedSessionStoreForGateway } from "./combined-store-gateway-Drfj1Kad.js"; import { _ as updateSessionGoalStatus, f as clearSessionGoal, h as getSessionGoal, m as formatSessionGoalStatus, p as createSessionGoal } from "./sessions-D6zZvoxX.js"; import { r as buildConfiguredModelCatalog } from "./model-selection-shared-b32cUYts.js"; import { t as resolveThinkingDefault } from "./model-thinking-default-dIc9T64G.js"; import { t as buildAllowedModelSet } from "./model-selection-CA_65iXi.js"; import { d as readSessionMessagesAsync, n as capArrayByJsonBytes } from "./session-utils.fs-D_8-X4jl.js"; import { a as ensureContextWindowCacheLoaded } from "./context-CvrdNrKN.js"; import { a as getSessionDefaults, b as resolveSessionModelRef, c as listSessionsFromStoreAsync, d as migrateAndPruneGatewaySessionStoreKey, g as resolveGatewaySessionStoreTarget, o as listAgentsForGateway, t as buildGatewaySessionInfo, u as loadSessionEntry } from "./session-utils-CHNCuY8a.js"; import { E as setEmbeddedMode } from "./openclaw-tools-C0nKaVVY.js"; import { t as ensureRuntimePluginsLoaded } from "./runtime-plugins-CyXD1U_W.js"; import { s as augmentChatHistoryWithCliSessionImports } from "./attempt-execution.helpers-CPoC_qYr.js"; import { a as parseGoalCommand } from "./commands-goal-D_61uAAw.js"; import { i as shouldSuppressAssistantEventForLiveChat, n as projectLiveAssistantBufferedText, r as resolveMergedAssistantText, t as normalizeLiveAssistantEventText } from "./live-chat-projector-CnA2pXR3.js"; import { a as isChatStopCommandText } from "./chat-abort-Cu_ehtms.js"; import { c as performGatewaySessionReset } from "./session-reset-service-DG5dw0Ix.js"; import { c as getMaxChatHistoryMessagesBytes } from "./server-constants-BGwLM6XN.js"; import { n as agentCommandFromIngress } from "./agent-command-DimMXeog.js"; import { t as createDefaultDeps } from "./deps-CWqGdIJS.js"; import { t as loadGatewayModelCatalog } from "./server-model-catalog-CcvpyvTp.js"; import { n as augmentChatHistoryWithCanvasBlocks, o as projectRecentChatDisplayMessages, s as resolveEffectiveChatHistoryMaxChars } from "./chat-display-projection-Bv2HD4Rr.js"; import { a as replaceOversizedChatHistoryMessages, i as enforceChatHistoryFinalBudget, t as CHAT_HISTORY_MAX_SINGLE_MESSAGE_BYTES } from "./chat-DKi9Erun.js"; import { t as resolveLocalRunShutdownGraceMs } from "./local-run-shutdown-CpyUvHAd.js"; import { t as applySessionsPatchToStore } from "./sessions-patch-Kxm51Uno.js"; import { randomUUID } from "node:crypto"; //#region src/tui/embedded-backend.ts const LIFECYCLE_ERROR_RETRY_GRACE_MS = 15e3; const silentRuntime = { log: (..._args) => void 0, error: (..._args) => void 0, exit: (code) => { throw new Error(`embedded tui runtime exit ${String(code)}`); } }; function hasProviderWildcardModelAllowlist(cfg) { return [cfg.agents?.defaults?.models, ...cfg.agents?.list?.map((agent) => agent?.models) ?? []].some((models) => Object.keys(models ?? {}).some((key) => key.trim().endsWith("/*"))); } function resolveConfiguredReplaceModeCatalog(cfg) { if (cfg.models?.mode !== "replace") return; if (hasProviderWildcardModelAllowlist(cfg)) return; return buildConfiguredModelCatalog({ cfg }); } function shouldLoadFullGatewayCatalogForReplaceMode(cfg) { return cfg.models?.mode === "replace" && hasProviderWildcardModelAllowlist(cfg); } function ensureEmbeddedHistoryRuntimePluginsLoaded(params) { try { const workspaceDir = resolveAgentWorkspaceDir(params.cfg, params.sessionAgentId); ensureRuntimePluginsLoaded({ config: params.cfg, workspaceDir }); return { status: "warmed" }; } catch (err) { return { status: "failed", error: String(err) }; } } async function loadEmbeddedTuiModelCatalog(cfg) { const configuredCatalog = resolveConfiguredReplaceModeCatalog(cfg); if (configuredCatalog !== void 0) return configuredCatalog; return await loadGatewayModelCatalog(shouldLoadFullGatewayCatalogForReplaceMode(cfg) ? { readOnly: false } : void 0); } function resolveBtwQuestion(message) { const question = /^\/(?:btw|side)(?::|\s)+(.*)$/i.exec(message.trim())?.[1]?.trim(); return question ? question : void 0; } function payloadText(parts) { if (!Array.isArray(parts)) return ""; return parts.map((part) => { if (!part || typeof part !== "object") return ""; const payload = part; return typeof payload.text === "string" ? payload.text.trim() : ""; }).filter(Boolean).join("\n\n").trim(); } function timeoutSecondsFromMs(timeoutMs) { if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs < 0) return; return String(Math.max(0, Math.ceil(timeoutMs / 1e3))); } 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) }; } function createQueuedRunReadiness() { let resolve; const promise = new Promise((ready) => { resolve = ready; }); if (!resolve) throw new Error("Expected queue readiness resolver to be initialized"); const resolveReady = resolve; let settled = false; return { promise, markReady: () => { if (settled) return; settled = true; resolveReady(); } }; } async function waitForLocalRunShutdown(promises) { if (promises.length === 0) return true; const timeoutMs = resolveLocalRunShutdownGraceMs(); if (timeoutMs <= 0) return false; let timeout; let completed = false; await Promise.race([Promise.allSettled(promises).then(() => { completed = true; }), new Promise((resolve) => { timeout = setTimeout(resolve, timeoutMs); timeout.unref?.(); })]); if (timeout) clearTimeout(timeout); return completed; } async function waitForQueuedLocalRun(previousRun, runId) { await previousRun.run.queuedRunReady; if (!previousRun.run.finishing && !previousRun.run.lifecycleEnded) { await previousRun.promise; return; } const timeoutMs = resolveLocalRunShutdownGraceMs(); if (timeoutMs <= 0) throw new Error(`timed out waiting for previous local run to finish post-turn maintenance for ${runId}`); let timeout; try { await Promise.race([previousRun.promise, new Promise((_, reject) => { timeout = setTimeout(() => { reject(/* @__PURE__ */ new Error(`timed out waiting for previous local run to finish post-turn maintenance for ${runId}`)); }, timeoutMs); timeout.unref?.(); })]); } finally { if (timeout) clearTimeout(timeout); } } 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(); } 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); }); queueMicrotask(() => { this.onConnected?.(); }); } async stop() { 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(); } if (!await waitForLocalRunShutdown(maintenancePromises)) { for (const run of this.runs.values()) if (run.finishing || run.lifecycleEnded) run.controller.abort(); } this.unsubscribe?.(); this.unsubscribe = void 0; this.clearPendingLifecycleErrors(); 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); } async sendChat(opts) { const runId = opts.runId ?? randomUUID(); const question = resolveBtwQuestion(opts.message); const runScope = { sessionKey: opts.sessionKey, agentId: opts.agentId }; const stopCommand = this.hasAbortableSessionRun(runScope) && isChatStopCommandText(opts.message); const queuedAfter = question || stopCommand ? void 0 : this.findQueuedSessionRunPromise(runScope); if (stopCommand) { this.abortSessionRuns(runScope); return { runId }; } const controller = new AbortController(); const queuedRunReadiness = createQueuedRunReadiness(); this.runs.set(runId, { sessionKey: opts.sessionKey, agentId: opts.agentId, controller, buffer: "", isBtw: Boolean(question), question, finishing: false, lifecycleEnded: false, finalSent: false, registered: false, queuedRunReady: queuedRunReadiness.promise, markQueuedRunReady: queuedRunReadiness.markReady }); 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); }); return { runId }; } async abortChat(opts) { const run = this.runs.get(opts.runId); if (!run || run.sessionKey !== opts.sessionKey) return { ok: true, aborted: false }; if (opts.sessionKey === "global") { const defaultAgentId = resolveDefaultAgentId(getRuntimeConfig()); const requestedAgentId = opts.agentId ? normalizeAgentId(opts.agentId) : defaultAgentId; if ((run.agentId ? normalizeAgentId(run.agentId) : defaultAgentId) !== requestedAgentId) return { ok: true, aborted: false }; } if (!this.isAbortableRun(opts.runId, run)) return { ok: true, aborted: false }; run.controller.abort(); return { ok: true, aborted: true }; } async loadHistory(opts) { const loadOptions = opts.agentId ? { agentId: opts.agentId } : void 0; const { cfg, storePath, store, entry, canonicalKey } = loadSessionEntry(opts.sessionKey, loadOptions); const sessionId = entry?.sessionId; const sessionAgentId = resolveSessionAgentId({ sessionKey: opts.sessionKey, config: cfg, agentId: opts.agentId }); const runtimePluginsPrewarm = ensureEmbeddedHistoryRuntimePluginsLoaded({ cfg, sessionAgentId }); const resolvedSessionModel = resolveSessionModelRef(cfg, entry, sessionAgentId); const max = Math.min(1e3, typeof opts.limit === "number" ? opts.limit : 200); const maxHistoryBytes = getMaxChatHistoryMessagesBytes(); const localMessages = sessionId && storePath ? await readSessionMessagesAsync(sessionId, storePath, entry?.sessionFile, { mode: "recent", maxMessages: max, maxBytes: Math.max(maxHistoryBytes * 2, 1024 * 1024) }) : []; const capped = capArrayByJsonBytes(replaceOversizedChatHistoryMessages({ messages: augmentChatHistoryWithCanvasBlocks(projectRecentChatDisplayMessages(augmentChatHistoryWithCliSessionImports({ entry, provider: resolvedSessionModel.provider, localMessages }), { maxChars: resolveEffectiveChatHistoryMaxChars(cfg), maxMessages: max })), maxSingleMessageBytes: Math.min(CHAT_HISTORY_MAX_SINGLE_MESSAGE_BYTES, maxHistoryBytes) }).messages, maxHistoryBytes).items; const messages = enforceChatHistoryFinalBudget({ messages: capped, maxBytes: maxHistoryBytes }).messages; let thinkingLevel = entry?.thinkingLevel; if (!thinkingLevel) { const catalog = await loadEmbeddedTuiModelCatalog(cfg); thinkingLevel = resolveThinkingDefault({ cfg, provider: resolvedSessionModel.provider, model: resolvedSessionModel.model, catalog }); } const defaults = getSessionDefaults(cfg, void 0, { allowPluginNormalization: false }); const sessionInfo = buildGatewaySessionInfo({ cfg, storePath, store, key: canonicalKey, entry, agentId: opts.agentId }); sessionInfo.thinkingLevel = thinkingLevel; sessionInfo.verboseLevel = entry?.verboseLevel ?? cfg.agents?.defaults?.verboseDefault; return { sessionKey: opts.sessionKey, sessionId, messages, defaults, sessionInfo, thinkingLevel, fastMode: entry?.fastMode, verboseLevel: sessionInfo.verboseLevel, runtimePluginsPrewarm }; } async listSessions(opts) { const cfg = getRuntimeConfig(); const { storePath, store } = loadCombinedSessionStoreForGateway(cfg, { agentId: opts?.agentId }); return await listSessionsFromStoreAsync({ cfg, storePath, store, opts: opts ?? {} }); } async listAgents() { return listAgentsForGateway(getRuntimeConfig()); } async patchSession(opts) { const cfg = getRuntimeConfig(); const target = resolveGatewaySessionStoreTarget({ cfg, key: opts.key, agentId: opts.agentId }); const applied = await updateSessionStore(target.storePath, async (store) => { const { primaryKey } = migrateAndPruneGatewaySessionStoreKey({ cfg, key: opts.key, store, agentId: opts.agentId }); return await applySessionsPatchToStore({ cfg, store, storeKey: primaryKey, agentId: opts.agentId, patch: opts, loadGatewayModelCatalog: () => loadEmbeddedTuiModelCatalog(cfg) }); }); if (!applied.ok) throw new Error(applied.error.message); const agentId = resolveSessionAgentId({ sessionKey: target.canonicalKey ?? opts.key, config: cfg, agentId: opts.agentId }); const resolved = resolveSessionModelRef(cfg, applied.entry, agentId); return { ok: true, path: target.storePath, key: target.canonicalKey ?? opts.key, entry: applied.entry, resolved: { modelProvider: resolved.provider, model: resolved.model } }; } async resetSession(key, reason, opts) { const result = await performGatewaySessionReset({ key, ...opts?.agentId ? { agentId: opts.agentId } : {}, reason: reason === "new" ? "new" : "reset", commandSource: "tui:embedded" }); if (!result.ok) throw new Error(result.error.message); return { ok: true, key: result.key, entry: result.entry }; } async getGatewayStatus() { return `local embedded mode${this.runs.size > 0 ? ` (${String(this.runs.size)} active run${this.runs.size === 1 ? "" : "s"})` : ""}`; } async listModels() { const cfg = getRuntimeConfig(); const catalog = await loadEmbeddedTuiModelCatalog(cfg); const { allowedCatalog } = buildAllowedModelSet({ cfg, catalog, defaultProvider: DEFAULT_PROVIDER }); return (allowedCatalog.length > 0 ? allowedCatalog : catalog).map((entry) => ({ id: entry.id, name: entry.name ?? entry.id, provider: entry.provider, contextWindow: entry.contextWindow, reasoning: entry.reasoning })); } async runGoalCommand(opts) { const loadOptions = opts.agentId ? { agentId: opts.agentId } : void 0; const { canonicalKey, storePath, entry } = loadSessionEntry(opts.sessionKey, loadOptions); const sessionKey = canonicalKey ?? opts.sessionKey; const parsed = parseGoalCommand(opts.command.trim()); if (!parsed) throw new Error("invalid goal command"); switch (parsed.action) { case "status": return { text: formatSessionGoalStatus((await getSessionGoal({ sessionKey, storePath })).goal) }; case "start": case "set": case "create": { const objective = parsed.text.trim(); if (!objective) return { text: "Usage: /goal start <objective>" }; return { text: `Goal started: ${(await createSessionGoal({ sessionKey, storePath, objective, fallbackEntry: entry ?? { sessionId: randomUUID(), updatedAt: Date.now() } })).objective}` }; } case "pause": return { text: `Goal paused: ${(await updateSessionGoalStatus({ sessionKey, storePath, status: "paused", ...parsed.text ? { note: parsed.text } : {} })).objective}` }; case "resume": return { text: `Goal resumed: ${(await updateSessionGoalStatus({ sessionKey, storePath, status: "active", ...parsed.text ? { note: parsed.text } : {} })).objective}` }; case "complete": case "done": { const goal = await updateSessionGoalStatus({ sessionKey, storePath, status: "complete", ...parsed.text ? { note: parsed.text } : {} }); return { text: `Goal complete: ${goal.objective}\nTokens used: ${goal.tokensUsed}` }; } case "block": case "blocked": return { text: `Goal blocked: ${(await updateSessionGoalStatus({ sessionKey, storePath, status: "blocked", ...parsed.text ? { note: parsed.text } : {} })).objective}` }; case "clear": return { text: await clearSessionGoal({ sessionKey, storePath }) ? "Goal cleared." : "No goal to clear." }; default: return { text: "Usage: /goal [status] | /goal start <objective> | /goal pause|resume|complete|block|clear" }; } } findQueuedSessionRunPromise(params) { let queuedAfter; for (const [runId, run] of this.runs) if (this.isSameRunScope(run, params) && !run.isBtw) { const promise = this.runPromises.get(runId); if (promise) queuedAfter = { run, promise }; } return queuedAfter; } abortSessionRuns(params) { for (const [runId, run] of this.runs) if (this.isSameRunScope(run, params) && !run.isBtw && this.isAbortableRun(runId, run)) run.controller.abort(); } hasAbortableSessionRun(params) { for (const [runId, run] of this.runs) if (this.isSameRunScope(run, params) && !run.isBtw && this.isAbortableRun(runId, run)) return true; return false; } isSameRunScope(run, params) { if (run.sessionKey !== params.sessionKey) return false; if (params.sessionKey !== "global") return true; return run.agentId === params.agentId; } isAbortableRun(runId, run) { return !run.lifecycleEnded || this.runPromises.has(runId); } nextSeq() { this.seq += 1; return this.seq; } emit(event, payload) { this.onEvent?.({ event, payload, seq: this.nextSeq() }); } clearPendingLifecycleError(runId) { const pending = this.pendingLifecycleErrors.get(runId); if (!pending) return; clearTimeout(pending); this.pendingLifecycleErrors.delete(runId); } clearPendingLifecycleErrors() { for (const pending of this.pendingLifecycleErrors.values()) clearTimeout(pending); this.pendingLifecycleErrors.clear(); } scheduleChatError(runId, run, errorMessage) { this.clearPendingLifecycleError(runId); const timer = setTimeout(() => { this.pendingLifecycleErrors.delete(runId); this.emitChatError(runId, run, errorMessage); }, LIFECYCLE_ERROR_RETRY_GRACE_MS); timer.unref?.(); this.pendingLifecycleErrors.set(runId, timer); } emitChatDelta(runId, run) { const projected = projectLiveAssistantBufferedText(run.buffer.trim(), { suppressLeadFragments: true }); const text = projected.text.trim(); if (!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, state: "delta", ...deltaPayload, message: { role: "assistant", content: [{ type: "text", text }], timestamp: Date.now() } }); } emitChatFinal(runId, run, stopReason) { this.clearPendingLifecycleError(runId); run.markQueuedRunReady(); const alreadyFinal = run.finalSent; run.finishing = false; run.lifecycleEnded = true; run.finalSent = true; if (alreadyFinal) return; run.registered = true; run.lastBroadcastText = void 0; const projected = projectLiveAssistantBufferedText(run.buffer.trim(), { suppressLeadFragments: false }); const text = projected.text.trim(); const shouldIncludeMessage = Boolean(text) && !projected.suppress; this.emit("chat", { runId, sessionKey: run.sessionKey, state: "final", ...stopReason ? { stopReason } : {}, ...shouldIncludeMessage ? { message: { role: "assistant", content: [{ type: "text", text }], timestamp: Date.now() } } : {} }); } emitChatAborted(runId, run) { this.clearPendingLifecycleError(runId); run.markQueuedRunReady(); const alreadyFinal = run.finalSent; run.finishing = false; run.lifecycleEnded = true; run.finalSent = true; if (alreadyFinal) return; run.registered = true; run.lastBroadcastText = void 0; this.emit("chat", { runId, sessionKey: run.sessionKey, state: "aborted" }); } emitChatError(runId, run, errorMessage) { this.clearPendingLifecycleError(runId); run.markQueuedRunReady(); const alreadyFinal = run.finalSent; run.finishing = false; run.lifecycleEnded = true; run.finalSent = true; if (alreadyFinal) return; run.registered = true; run.lastBroadcastText = void 0; this.emit("chat", { runId, sessionKey: run.sessionKey, state: "error", ...errorMessage ? { errorMessage } : {} }); } ensureRunRegistered(runId, run) { if (run.registered || run.isBtw) return; run.registered = true; run.lastBroadcastText = ""; this.emit("chat", { runId, sessionKey: run.sessionKey, state: "delta", deltaText: "", message: { role: "assistant", content: [{ type: "text", text: "" }], timestamp: Date.now() } }); } async 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, stream: evt.stream, data: evt.data }); if (evt.stream === "assistant" && !run.isBtw && typeof evt.data?.text === "string" && !shouldSuppressAssistantEventForLiveChat(evt.data)) { const cleaned = normalizeLiveAssistantEventText({ text: evt.data.text, delta: evt.data.delta }); run.buffer = resolveMergedAssistantText({ previousText: run.buffer, nextText: cleaned.text, nextDelta: cleaned.delta }); this.emitChatDelta(evt.runId, run); return; } if (evt.stream !== "lifecycle") return; const phase = lifecyclePhase; const aborted = evt.data?.aborted === true || run.controller.signal.aborted; if (phase === "finishing") { run.finishing = true; run.markQueuedRunReady(); run.lifecycleStopReason = typeof evt.data?.stopReason === "string" ? evt.data.stopReason : void 0; return; } if (phase === "end") { run.finishing = false; if (aborted) { this.emitChatAborted(evt.runId, run); return; } run.lifecycleEnded = true; run.markQueuedRunReady(); run.lifecycleStopReason = typeof evt.data?.stopReason === "string" ? evt.data.stopReason : void 0; return; } if (phase === "error") { run.finishing = false; if (aborted) { this.emitChatAborted(evt.runId, run); return; } const errorMessage = typeof evt.data?.error === "string" ? evt.data.error : void 0; run.buffer = ""; this.scheduleChatError(evt.runId, run, errorMessage); } } async runTurn(params) { try { if (params.queuedAfter) { try { await waitForQueuedLocalRun(params.queuedAfter, params.runId); } catch (error) { const run = this.runs.get(params.runId); if (run) { const errorMessage = error instanceof Error ? error.message : String(error); this.emitChatError(params.runId, run, `previous run did not finish cleanly: ${errorMessage}`); } return; } if (params.controller.signal.aborted) { const run = this.runs.get(params.runId); if (run) this.emitChatAborted(params.runId, run); return; } } const loadOptions = params.agentId ? { agentId: params.agentId } : void 0; const { canonicalKey, entry } = loadSessionEntry(params.sessionKey, loadOptions); const result = await agentCommandFromIngress({ message: params.message, sessionKey: canonicalKey, ...params.agentId ? { agentId: params.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 (params.controller.signal.aborted || result?.meta?.aborted === true) { this.emitChatAborted(params.runId, run); return; } if (run.isBtw) { const text = payloadText(result?.payloads); if (run.question && text) this.emit("chat.side_result", { kind: "btw", runId: params.runId, sessionKey: run.sessionKey, question: run.question, text }); this.emitChatFinal(params.runId, run); return; } if (!run.finalSent) { const normalizedText = payloadText(result?.payloads); if (normalizedText && !run.buffer) run.buffer = normalizedText; const stopReason = run.lifecycleStopReason ?? (typeof result?.meta?.stopReason === "string" ? result.meta.stopReason : void 0); this.emitChatFinal(params.runId, run, stopReason); } } catch (error) { const run = this.runs.get(params.runId); if (!run) return; if (params.controller.signal.aborted) { this.emitChatAborted(params.runId, run); return; } const errorMessage = error instanceof Error ? error.message : String(error); this.emitChatError(params.runId, run, errorMessage); } finally { this.runs.get(params.runId)?.markQueuedRunReady(); this.runs.delete(params.runId); } } }; //#endregion export { EmbeddedTuiBackend };