UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

265 lines (264 loc) • 10.4 kB
import { a as runInDetachedAsyncContext } from "./async-work-scope-BYeX3gh6.mjs"; import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { o as racePromiseWithAbortSignal } from "./src-D1BHGNTj.mjs"; import "./abort-signal-QqAmLWTl.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { n as resolveRuntimeProcessEntrypointUrl } from "./runtime-process-url-D3H3_asn.mjs"; import { r as WorkerTaskError } from "./src-WCJCjrey.mjs"; import { t as WorkerTaskPool } from "./worker-task-pool-iI3pfrsK.mjs"; import { t as runBestEffortCleanup } from "./non-fatal-cleanup-BoCq8AND.mjs"; import { n as PluginRuntimeCloseRetainedError } from "./runtime-close-error-DPmgR5gM.mjs"; import { r as getBoundLegacyPluginSdkResourceHost } from "./legacy-sdk-resource-host-DC4n8Jcn.mjs"; import { t as CODE_MODE_WORKER_WATCHDOG_GRACE_MS } from "./code-mode-worker-types-BrXHJGAa.mjs"; import { n as EMPTY_CODE_MODE_OUTPUT } from "./code-mode-json-DLneEPkt.mjs"; import { t as CodeModeNodeProgress } from "./code-mode-node-progress-DlOYoVlG.mjs"; import { n as CodeModeHeadlessTimeoutError, r as codeModeFailureCode, t as CodeModeHeadlessAbortError } from "./code-mode-errors-0N1EOS4P.mjs"; import { channel } from "node:diagnostics_channel"; //#region src/agents/code-mode-node.ts const retiringPools = /* @__PURE__ */ new Set(); const idlePools = /* @__PURE__ */ new Map(); const poolLifetimes = /* @__PURE__ */ new WeakMap(); let nextPoolId = 0; const MAX_IDLE_POOLS = 4; const memoryPressure = channel("openclaw.memory.critical"); function removeIdlePool(owner) { idlePools.get(owner)?.cancel(); idlePools.delete(owner); if (!idlePools.size) memoryPressure.unsubscribe(retireIdlePools); } function retireIdlePool(owner) { if (!idlePools.has(owner)) return; runBestEffortCleanup({ cleanup: () => closePool(owner), onError: (error) => process.emitWarning(`Code Mode worker retirement failed: ${formatErrorMessage(error)}`) }); } function retireIdlePools() { for (const owner of idlePools.keys()) retireIdlePool(owner); } async function closePool(owner) { removeIdlePool(owner); retiringPools.add(owner); await owner.tasks.close(); retiringPools.delete(owner); owner.lifetime?.pools.delete(owner); } function capturePoolLifetime() { const host = getBoundLegacyPluginSdkResourceHost(); if (!host) return; host.assertOpen(); let lifetime = poolLifetimes.get(host); if (!lifetime) { const scheduler = host.scheduler.scope(); const pools = /* @__PURE__ */ new Set(); lifetime = { scheduler, pools }; host.adopt(lifetime, { release: async () => { scheduler.beginClose(); const results = await Promise.allSettled([...pools].map(closePool)); await scheduler.stop(); const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []); if (failures.length) throw new PluginRuntimeCloseRetainedError(new AggregateError(failures, "Code Mode workers failed to retire")); } }); poolLifetimes.set(host, lifetime); } lifetime.scheduler.signal.throwIfAborted(); return lifetime; } async function takePool(memoryLimitBytes, signal) { const lifetime = capturePoolLifetime(); let workerUrl; for (;;) { signal.throwIfAborted(); lifetime?.scheduler.signal.throwIfAborted(); workerUrl = resolveRuntimeProcessEntrypointUrl("codeModeNode"); const retiring = /* @__PURE__ */ new Set([...[...retiringPools].filter((owner) => owner.lifetime === lifetime || owner.lifetime?.scheduler.signal.aborted), ...[...idlePools.keys()].filter((owner) => owner.lifetime === lifetime && (owner.url !== workerUrl.href || owner.tasks.isClosed))]); if (!retiring.size) break; await Promise.all([...retiring].map(closePool)); } for (const owner of idlePools.keys()) if (owner.memoryLimitBytes === memoryLimitBytes && owner.lifetime === lifetime && owner.url === workerUrl.href && !owner.tasks.isClosed) { removeIdlePool(owner); return owner; } const owner = { lifetime, url: workerUrl.href, memoryLimitBytes, tasks: new WorkerTaskPool({ workerUrl, maxWorkers: 1, idleTimeoutMs: 0, restartOnError: false, sharedCompute: true, onRetirementFailure: () => { retiringPools.add(owner); }, workerOptions: { env: {}, resourceLimits: { maxOldGenerationSizeMb: Math.max(1, Math.floor(memoryLimitBytes / 1048576) - 4), maxYoungGenerationSizeMb: 4 } } }) }; lifetime?.pools.add(owner); return owner; } async function releasePool(owner) { const { lifetime } = owner; if (idlePools.size >= MAX_IDLE_POOLS || !lifetime || lifetime.scheduler.signal.aborted || owner.tasks.isClosed || owner.url !== resolveRuntimeProcessEntrypointUrl("codeModeNode").href) { await closePool(owner); return; } idlePools.set(owner, runInDetachedAsyncContext(() => lifetime.scheduler.schedule({ id: `code-mode-worker-idle:${++nextPoolId}`, delayMs: 3e5, run: () => closePool(owner) }))); if (idlePools.size === 1) memoryPressure.subscribe(retireIdlePools); } function failure(error, code) { return { status: "failed", code, error: formatErrorMessage(error), failurePhase: "host", bridgeDispatchStarted: false, output: EMPTY_CODE_MODE_OUTPUT }; } function continuation(pool) { let state = "owned"; let closing; return { executor: "node", retainedBytes: pool.memoryLimitBytes, resume(input, options) { if (state !== "owned") return Promise.resolve(failure("code mode continuation is no longer available", "runtime_unavailable")); state = "resumed"; return run(pool, input, options); }, dispose() { if (state === "resumed" || state === "disposed") return Promise.resolve(); state = "disposing"; return closing ??= closePool(pool).then(() => { state = "disposed"; }).finally(() => { closing = void 0; }); } }; } async function run(pool, input, options, startedAt = performance.now()) { const inlineHost = options.inlineHost; const progress = new CodeModeNodeProgress(input.config.maxOutputBytes); const deadline = new AbortController(); const signal = options.signal ? AbortSignal.any([options.signal, deadline.signal]) : deadline.signal; let timer; let admittedTimeoutMs = 0; let retained = false; try { const preparationMs = performance.now() - startedAt; if (input.config.timeoutMs <= preparationMs || options.timeoutMs <= preparationMs) throw new CodeModeHeadlessTimeoutError(); const result = await pool.tasks.run(() => { admittedTimeoutMs = Math.max(0, input.config.timeoutMs - (performance.now() - startedAt)); if (admittedTimeoutMs <= 0) throw new CodeModeHeadlessTimeoutError(); return { ...input, progress: progress.buffer, inlineHost: Boolean(inlineHost), config: { ...input.config, timeoutMs: admittedTimeoutMs } }; }, { timeoutMs: Math.min(options.timeoutMs, input.config.timeoutMs) - preparationMs, hostTimeout: "owner", signal, inputBytes: input.kind === "exec" ? input.source.length * 2 : 0, onInputConsumed: () => { if (input.kind === "exec") timer = setTimeout(() => deadline.abort(new CodeModeHeadlessTimeoutError()), Math.max(0, progress.deadline - performance.timeOrigin - performance.now())); inlineHost?.onInputConsumed?.(); }, onRequest: async (value, context) => { clearTimeout(timer); if (!inlineHost || !isRecord(value) || value.status !== "boundary") throw new Error("invalid code mode worker boundary"); if (!Number.isFinite(admittedTimeoutMs) || admittedTimeoutMs <= 0) throw new Error("invalid code mode worker admission budget"); if (value.networkContentObserved === true) inlineHost?.onNetworkContent?.(); const response = inlineHost.onBoundary(value, { ...context, maxTimeoutMs: admittedTimeoutMs }); progress.resetOutput(); const { onConsumed, ...command } = await response; return { input: command, onConsumed, timeoutMs: command.kind === "continue" ? command.timeoutMs : CODE_MODE_WORKER_WATCHDOG_GRACE_MS }; } }); if (result.networkContentObserved === true) inlineHost?.onNetworkContent?.(); if (result.status === "waiting") { retained = true; return { ...result, continuation: continuation(pool) }; } if (result.status === "completed") { await releasePool(pool); retained = true; } return result; } catch (error) { const reason = signal.aborted ? signal.reason : error; if (reason instanceof CodeModeHeadlessTimeoutError || error instanceof WorkerTaskError && error.code === "timeout") { if (progress.networkContentObserved) inlineHost?.onNetworkContent?.(); return { ...failure("code mode timeout exceeded", "timeout"), failurePhase: progress.deadline ? "guest" : "host", output: progress.output(), ...progress.networkContentObserved ? { networkContentObserved: true } : {} }; } if (options.signal?.aborted || reason instanceof CodeModeHeadlessAbortError) return failure("code mode execution aborted", "aborted"); return failure(error, error instanceof WorkerTaskError ? "runtime_unavailable" : codeModeFailureCode(error)); } finally { clearTimeout(timer); if (!retained) await closePool(pool); } } const nodeCodeModeExecutor = { id: "node", async execute(input, options) { const startedAt = performance.now(); const deadline = new AbortController(); const signal = options.signal ? AbortSignal.any([options.signal, deadline.signal]) : deadline.signal; const timer = setTimeout(() => deadline.abort(new CodeModeHeadlessTimeoutError()), Math.max(0, Math.min(input.config.timeoutMs, options.timeoutMs))); const acquisition = takePool(input.config.memoryLimitBytes, signal); let pool; try { pool = await racePromiseWithAbortSignal(acquisition, signal); } catch (error) { runBestEffortCleanup({ cleanup: () => acquisition.then(closePool, () => void 0), onError: (cleanupError) => process.emitWarning(`Code Mode worker retirement failed: ${formatErrorMessage(cleanupError)}`) }); if (signal.aborted) { const timeout = signal.reason instanceof CodeModeHeadlessTimeoutError; return failure(timeout ? "code mode timeout exceeded" : "code mode execution aborted", timeout ? "timeout" : "aborted"); } throw error; } finally { clearTimeout(timer); } return run(pool, input, options, startedAt); } }; //#endregion export { nodeCodeModeExecutor };