UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

167 lines (166 loc) 4.84 kB
import { t as KeyedAsyncQueue } from "./keyed-async-queue-Ckmdd15z.js"; //#region src/channels/run-state-machine.ts const DEFAULT_RUN_ACTIVITY_HEARTBEAT_MS = 6e4; /** Creates a channel run-state tracker with heartbeat updates while runs are active. */ function createRunStateMachine(params) { const heartbeatMs = params.heartbeatMs ?? DEFAULT_RUN_ACTIVITY_HEARTBEAT_MS; const now = params.now ?? Date.now; let activeRuns = 0; let runActivityHeartbeat = null; let lifecycleActive = !params.abortSignal?.aborted; const publish = () => { if (!lifecycleActive) return; params.setStatus?.({ activeRuns, busy: activeRuns > 0, lastRunActivityAt: now() }); }; const clearHeartbeat = () => { if (!runActivityHeartbeat) return; clearInterval(runActivityHeartbeat); runActivityHeartbeat = null; }; const ensureHeartbeat = () => { if (runActivityHeartbeat || activeRuns <= 0 || !lifecycleActive) return; runActivityHeartbeat = setInterval(() => { if (!lifecycleActive || activeRuns <= 0) { clearHeartbeat(); return; } publish(); }, heartbeatMs); runActivityHeartbeat.unref?.(); }; const deactivate = () => { lifecycleActive = false; clearHeartbeat(); }; const onAbort = () => { deactivate(); }; if (params.abortSignal?.aborted) onAbort(); else params.abortSignal?.addEventListener("abort", onAbort, { once: true }); if (lifecycleActive) params.setStatus?.({ activeRuns: 0, busy: false }); return { isActive() { return lifecycleActive; }, onRunStart() { activeRuns += 1; publish(); ensureHeartbeat(); }, onRunEnd() { activeRuns = Math.max(0, activeRuns - 1); if (activeRuns <= 0) clearHeartbeat(); publish(); }, deactivate }; } //#endregion //#region src/plugin-sdk/channel-lifecycle.core.ts /** Bind a fixed account id into a status writer so lifecycle code can emit partial snapshots. */ function createAccountStatusSink(params) { return (patch) => { params.setStatus({ accountId: params.accountId, ...patch }); }; } /** * Serialize channel work per key while keeping lifecycle/busy accounting out of * channel-specific message handlers. The queue does not impose run timeouts; * callers should rely on session/tool/runtime lifecycle for long-running work. */ function createChannelRunQueue(params) { const queue = new KeyedAsyncQueue(); const runState = createRunStateMachine({ setStatus: params.setStatus, abortSignal: params.abortSignal }); const reportError = (error) => { try { params.onError?.(error); } catch {} }; return { enqueue(key, task) { queue.enqueue(key, async () => { if (!runState.isActive()) return; runState.onRunStart(); try { if (!runState.isActive()) return; await task({ lifecycleSignal: params.abortSignal }); } finally { runState.onRunEnd(); } }).catch(reportError); }, deactivate: runState.deactivate }; } /** * Return a promise that resolves when the signal is aborted. * * If no signal is provided, the promise stays pending forever. When provided, * `onAbort` runs once before the promise resolves. */ function waitUntilAbort(signal, onAbort) { return new Promise((resolve, reject) => { const complete = () => { Promise.resolve(onAbort?.()).then(() => resolve(), reject); }; if (!signal) return; if (signal.aborted) { complete(); return; } signal.addEventListener("abort", complete, { once: true }); }); } /** * Keep a passive account task alive until abort, then run optional cleanup. */ async function runPassiveAccountLifecycle(params) { const handle = await params.start(); try { await waitUntilAbort(params.abortSignal); } finally { await params.stop?.(handle); await params.onStop?.(); } } /** * Keep a channel/provider task pending until the HTTP server closes. * * When an abort signal is provided, `onAbort` is invoked once and should * trigger server shutdown. The returned promise resolves only after `close`. */ async function keepHttpServerTaskAlive(params) { const { server, abortSignal, onAbort } = params; let abortTask = Promise.resolve(); let abortTriggered = false; const triggerAbort = () => { if (abortTriggered) return; abortTriggered = true; abortTask = Promise.resolve(onAbort?.()).then(() => void 0); }; const onAbortSignal = () => { triggerAbort(); }; if (abortSignal) if (abortSignal.aborted) triggerAbort(); else abortSignal.addEventListener("abort", onAbortSignal, { once: true }); await new Promise((resolve) => { server.once("close", () => resolve()); }); if (abortSignal) abortSignal.removeEventListener("abort", onAbortSignal); await abortTask; } //#endregion export { waitUntilAbort as a, runPassiveAccountLifecycle as i, createChannelRunQueue as n, createRunStateMachine as o, keepHttpServerTaskAlive as r, createAccountStatusSink as t };