UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

373 lines (372 loc) • 18.3 kB
import { C as parseStrictNonNegativeInteger } from "./number-coercion-Dq1l5muL.mjs"; import { a as writeRuntimeJson, o as writeRuntimeStdout } from "./runtime-BC29JSZp.mjs"; import { t as formatErrorMessage } from "./errors-BSdPMFao.mjs"; import { t as formatCliCommand } from "./command-format-Cte1SWM4.mjs"; import { n as getInstallationTarget, t as LOCAL_INSTALLATION_TARGET_UNSUPPORTED } from "./installation-target-context-CIQIaD3U.mjs"; import { n as recordAgentCleanupFailure, t as createAgentCleanupScope } from "./run-cleanup-timeout-BptAdbpQ.mjs"; import { n as isExecutionIdentityCollectionEnabled } from "./audit-config-BXFCjLO0.mjs"; import { n as findAgentRunTerminalOutcome } from "./agent-run-terminal-error-BYNAyPjs.mjs"; import { a as createAgentToolExecutionBudget } from "./agent-tool-source-execution-guard-DJwXOEPL.mjs"; import { n as resolveAgentExecPrompt, r as resolveExecBaseConfig, t as buildExecRunConfig } from "./agent-exec-input-6jeA5tEP.mjs"; import path from "node:path"; import fs from "node:fs/promises"; import os from "node:os"; import { randomUUID } from "node:crypto"; //#region src/commands/agent-exec-result.ts function projectAgentExecPayload(payload) { return { ...typeof payload.text === "string" ? { text: payload.text } : {}, ...payload.mediaUrl !== void 0 ? { mediaUrl: payload.mediaUrl } : {}, ...Array.isArray(payload.mediaUrls) ? { mediaUrls: [...payload.mediaUrls] } : {}, ...payload.isError === true ? { isError: true } : {}, ...payload.isReasoning === true ? { isReasoning: true } : {}, ...payload.isCommentary === true ? { isCommentary: true } : {} }; } function finalTextFromResult(result, payloads, allowMetadataFallback) { return payloads.filter((payload) => payload.isError !== true && payload.isReasoning !== true && payload.isCommentary !== true && typeof payload.text === "string" && payload.text.trim().length > 0).map((payload) => payload.text.trimEnd()).join("\n") || (allowMetadataFallback ? result.meta.finalAssistantVisibleText?.trimEnd() : "") || ""; } /** Classify an embedded result into the strict `agent exec` process contract. */ function classifyAgentExecResult(result, fallbackExhausted = false, projectedErrorPayload) { const meta = result.meta; const errorPayload = result.payloads?.find((payload) => payload.isError === true); const errorPayloadMessage = typeof projectedErrorPayload === "string" ? projectedErrorPayload : typeof errorPayload?.text === "string" && errorPayload.text.trim() ? errorPayload.text : void 0; const hasErrorPayload = projectedErrorPayload !== void 0 || errorPayload !== void 0; const payloads = (result.payloads ?? []).map(projectAgentExecPayload); if (typeof projectedErrorPayload === "string") { const projectedErrorIndex = payloads.findIndex((payload) => payload.isError !== true && payload.text === projectedErrorPayload); if (projectedErrorIndex >= 0) payloads[projectedErrorIndex] = { ...payloads[projectedErrorIndex], isError: true }; } const timeout = meta.stopReason === "timeout" || meta.timeoutPhase !== void 0; const failed = fallbackExhausted || meta.aborted === true || meta.error !== void 0 || meta.stopReason === "error" || hasErrorPayload; const status = timeout ? "timeout" : failed ? "error" : "ok"; const errorMessage = timeout ? meta.error?.message ?? errorPayloadMessage ?? "Agent run timed out" : fallbackExhausted ? meta.error?.message ?? errorPayloadMessage ?? "All model fallback candidates failed" : meta.error?.message ?? errorPayloadMessage ?? (failed ? "Agent run failed" : void 0); const errorKind = timeout ? "timeout" : fallbackExhausted ? "fallback_exhausted" : meta.error?.kind ? meta.error.kind : meta.aborted ? "aborted" : hasErrorPayload ? "error_payload" : failed ? "agent_error" : void 0; const agentMeta = meta.agentMeta; return { ok: status === "ok", status, final: finalTextFromResult(result, payloads, !hasErrorPayload), payloads, ...agentMeta?.usage ? { usage: agentMeta.usage } : {}, ...agentMeta?.costUsd !== void 0 ? { costUsd: agentMeta.costUsd } : {}, ...agentMeta?.codeModeEngaged !== void 0 ? { codeModeEngaged: agentMeta.codeModeEngaged } : {}, ...agentMeta?.assistantTurns !== void 0 ? { assistantTurns: agentMeta.assistantTurns } : {}, ...agentMeta?.bridgeCalls ? { bridgeCalls: agentMeta.bridgeCalls } : {}, ...meta.toolSummary ? { toolSummary: meta.toolSummary } : {}, model: agentMeta?.model ?? null, provider: agentMeta?.provider ?? null, sessionId: agentMeta?.sessionId ?? "", ...errorMessage && errorKind ? { error: { message: errorMessage, kind: errorKind } } : {} }; } //#endregion //#region src/commands/agent-exec.ts const AGENT_EXEC_DEFAULT_TIMEOUT_SECONDS = 600; function normalizeCodeMode(value) { if (value === void 0) return; if (value === "direct") return false; if (value === "auto") return "auto"; if (value === "code") return true; throw new Error("--code-mode must be one of direct, auto, code."); } function normalizeTimeoutSeconds(value) { const raw = value ?? String(AGENT_EXEC_DEFAULT_TIMEOUT_SECONDS); if (parseStrictNonNegativeInteger(raw) === void 0) throw new Error("--timeout must be a non-negative integer in seconds."); return raw; } function normalizeFallbacks(model, values) { const fallbacks = (values ?? []).map((value) => value.trim()).filter(Boolean); if (fallbacks.length > 0 && !model?.trim()) throw new Error("--fallback requires --model so the primary model is explicit."); return fallbacks; } async function requireDirectory(value, label) { const resolved = path.resolve(value); let stat; try { stat = await fs.stat(resolved); } catch (error) { throw new Error(`${label} does not exist: ${resolved}`, { cause: error }); } if (!stat.isDirectory()) throw new Error(`${label} is not a directory: ${resolved}`); return resolved; } function setAgentExecEnvironment(params) { const previous = { OPENCLAW_STATE_DIR: process.env.OPENCLAW_STATE_DIR, OPENCLAW_CONFIG_PATH: process.env.OPENCLAW_CONFIG_PATH, OPENCLAW_WORKSPACE_DIR: process.env.OPENCLAW_WORKSPACE_DIR }; process.env.OPENCLAW_STATE_DIR = params.stateDir; delete process.env.OPENCLAW_CONFIG_PATH; process.env.OPENCLAW_WORKSPACE_DIR = params.cwd; return () => { for (const [key, value] of Object.entries(previous)) if (value === void 0) delete process.env[key]; else process.env[key] = value; }; } function formatActiveGatewayExecRefusal(identity) { return `A Gateway is running for this state directory (pid ${identity.pid}, port ${identity.port}). Omit --state-dir to use isolated temporary state, or stop the Gateway first (${formatCliCommand("openclaw gateway stop")}).`; } function isStructuredTimeoutError(error) { if (findAgentRunTerminalOutcome(error)?.status === "timeout") return true; let candidate = error; for (let depth = 0; depth < 4; depth += 1) { if (!candidate || typeof candidate !== "object") return false; const record = candidate; if (record.name === "TimeoutError" || record.code === "ETIMEDOUT" || record.reason === "timeout") return true; candidate = record.cause; } return false; } function errorEnvelope(error, sessionId) { const status = isStructuredTimeoutError(error) ? "timeout" : "error"; return { ok: false, status, final: "", payloads: [], model: null, provider: null, sessionId, error: { message: formatErrorMessage(error), kind: status === "timeout" ? "timeout" : "exception" } }; } function writeAgentExecOutput(runtime, envelope, json) { if (json) writeRuntimeJson(runtime, envelope); else if (envelope.final) writeRuntimeStdout(runtime, envelope.final); if (!envelope.ok && envelope.error) runtime.error(envelope.error.message); } /** Run one isolated embedded agent turn and project its stable CLI result. */ async function agentExecCommand(positionalMessage, opts, runtime, deps = {}) { const sessionId = randomUUID(); const abortController = new AbortController(); const signal = deps.abortSignal ? AbortSignal.any([abortController.signal, deps.abortSignal]) : abortController.signal; const toolBudget = createAgentToolExecutionBudget({ maxToolCalls: deps.maxToolCalls, signal, abort: (reason) => abortController.abort(reason), isCurrent: deps.isCurrent }); const resultForEnvelope = (envelope) => ({ envelope, exitCode: envelope.status === "ok" ? 0 : envelope.status === "timeout" ? 2 : 1, toolCalls: toolBudget.toolCalls }); let timeoutTimer; let cleanupProcessScope; let commandResult; const runtimeCleanup = createAgentCleanupScope(); let temporaryStateDir; let restoreEnvironment; let restoreConfigEnvironment; let restoreRuntimeConfigSnapshot; let runtimePaths; let configIo; let stopLocalAuditWriter; let stateLock; let temporaryDatabaseScope; let abortSignal = signal; let signalBridge; try { signal.throwIfAborted(); if (deps.maxToolCalls !== void 0 && (!Number.isSafeInteger(deps.maxToolCalls) || deps.maxToolCalls < 0)) throw new Error("maxToolCalls must be a non-negative safe integer"); if (deps.timeoutMs !== void 0) { if (!Number.isSafeInteger(deps.timeoutMs) || deps.timeoutMs <= 0) throw new Error("timeoutMs must be a positive safe integer"); timeoutTimer = setTimeout(() => abortController.abort(new DOMException("Agent execution deadline elapsed", "TimeoutError")), deps.timeoutMs); timeoutTimer.unref(); } const codeModeOverride = normalizeCodeMode(opts.codeMode); const prompt = await resolveAgentExecPrompt(positionalMessage, opts.messageFile, deps.stdin ?? process.stdin); const cwd = await requireDirectory(opts.cwd ?? process.cwd(), "Working directory"); const stateDir = opts.stateDir ? await requireDirectory(opts.stateDir, "State directory") : await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-exec-")); temporaryStateDir = opts.stateDir ? void 0 : stateDir; configIo = await import("./io-BFXJQ9PP.mjs"); const previousRuntimeConfigSnapshot = configIo.getRuntimeConfigSnapshot(); const snapshotIo = configIo; restoreRuntimeConfigSnapshot = () => { if (previousRuntimeConfigSnapshot) snapshotIo.setRuntimeConfigSnapshot(previousRuntimeConfigSnapshot); else snapshotIo.clearRuntimeConfigSnapshot(); }; const { restoreEnvChangesIfUnchanged, snapshotEnv } = configIo; const envBeforeConfigLoad = snapshotEnv(process.env); const baseConfig = deps.baseConfig ?? await resolveExecBaseConfig(opts); const envAfterConfigLoad = snapshotEnv(process.env); restoreConfigEnvironment = () => restoreEnvChangesIfUnchanged({ env: process.env, before: envBeforeConfigLoad, after: envAfterConfigLoad }); const runConfig = buildExecRunConfig({ base: baseConfig, cwd, opts }); const pluginInstallContext = opts.isolated !== true && opts.authEnvOnly !== true ? await import("./install-root-context-B_i8bmdX.mjs") : void 0; const pluginInstallRoots = pluginInstallContext?.resolvePluginInstallRoots(); const timeout = normalizeTimeoutSeconds(deps.timeoutMs === void 0 ? opts.timeout : String(Math.ceil(deps.timeoutMs / 1e3))); const fallbacks = normalizeFallbacks(opts.model, deps.modelFallbacksOverride ?? opts.fallback); const { resolveAgentDir, resolveAmbientOwnerAgentId } = await import("./agent-scope-config-BHcWNs7o.mjs"); const execAgentId = resolveAmbientOwnerAgentId(baseConfig, deps.agentId, { surface: "agent exec", hint: "Set agents.defaults.systemAgent.agentId." }); if (getInstallationTarget()) { const [{ resolveSandboxConfigForAgent }, { resolveExecToolConfig }] = await Promise.all([import("./config-O85SxQ38.mjs"), import("./lazy-exec-tool-CJHuAbK_.mjs")]); const execHost = resolveExecToolConfig({ cfg: runConfig, agentId: execAgentId }).host; if (resolveSandboxConfigForAgent(runConfig, execAgentId).mode !== "off" || execHost === "node" || execHost === "sandbox") throw new Error(LOCAL_INSTALLATION_TARGET_UNSUPPORTED); } const storedAuthAgentDir = resolveAgentDir(baseConfig, execAgentId); runtimePaths = await import("./paths-Cwl4mMlv.mjs"); const storedAuthStateDir = runtimePaths.resolveStateDir(); const processScopeKey = deps.timeoutMs !== void 0 || deps.maxToolCalls !== void 0 ? `agent:${execAgentId}:agent-exec:${sessionId}` : void 0; if (processScopeKey) { const { getProcessSupervisor } = await import("./supervisor-CWZjFu59.mjs"); cleanupProcessScope = getProcessSupervisor().acquireScopeCleanup(processScopeKey, { processTree: "required-all" }); } restoreEnvironment = setAgentExecEnvironment({ stateDir, cwd }); runtimePaths.pinRuntimePaths(); if (temporaryStateDir) { const { createOpenClawDatabaseMaintenanceScope } = await import("./openclaw-state-db-async-lifecycle-CEf7ASy3.mjs"); temporaryDatabaseScope = createOpenClawDatabaseMaintenanceScope(); } if (opts.stateDir) { const { acquireEmbeddedStateLock, createEmbeddedStateSignalBridge } = await import("./embedded-state-lock-nNnw4cKv.mjs"); signalBridge = createEmbeddedStateSignalBridge(deps.process ?? process); abortSignal = AbortSignal.any([abortSignal, signalBridge.signal]); stateLock = await acquireEmbeddedStateLock({ options: deps.gatewayLockOptions, signal: abortSignal, formatActiveGatewayRefusal: formatActiveGatewayExecRefusal }); } snapshotIo.setRuntimeConfigSnapshot(runConfig); const [{ withAuthProfileStoreAgentDir, withEnvOnlyAuthProfileStore }, { withHostExecInheritedEnvOmitted }, { listKnownProviderAuthEnvVarNamesCore }, runAgent] = await Promise.all([ import("./auth-profiles-CphQlG7L.mjs"), import("./host-env-security-Bbb_apcd.mjs"), import("./provider-env-vars-DZVSKcdb.mjs"), deps.runAgent ? Promise.resolve(deps.runAgent) : import("./agent-DjNIl6p3.mjs").then((module) => module.agentCommand) ]); let fallbackExhausted = false; let resultErrorPayload; const silentRuntime = { log: () => {}, error: (...args) => runtime.error(...args), exit: (code, exitOpts) => runtime.exit(code, exitOpts) }; const invoke = async () => { abortSignal.throwIfAborted(); deps.assertSourceCurrent?.(); if (deps.isCurrent?.() === false) throw new Error("Agent execution scope is no longer active"); return await runAgent({ message: prompt, sessionId, ...processScopeKey ? { sessionKey: processScopeKey } : {}, agentId: execAgentId, workspaceDir: cwd, cwd, model: opts.model, codeModeOverride, thinking: opts.thinking, timeout, modelFallbacksOverride: fallbacks.length > 0 || deps.modelFallbacksOverride !== void 0 ? fallbacks : void 0, cleanupBundleMcpOnRunEnd: true, cleanupCliLiveSessionOnRunEnd: true, oneShotCliRun: true, abortSignal, assertSourceCurrent: deps.assertSourceCurrent, onModelFallbackExhausted: () => { fallbackExhausted = true; }, onResultErrorPayload: (message) => { resultErrorPayload = message ?? true; } }, silentRuntime); }; const runWithPluginInstallRoots = () => pluginInstallContext && pluginInstallRoots ? pluginInstallContext.withPluginInstallRoots(pluginInstallRoots, invoke) : invoke(); const runWithAuthScope = () => opts.authEnvOnly === true ? withEnvOnlyAuthProfileStore(runWithPluginInstallRoots) : withAuthProfileStoreAgentDir(storedAuthAgentDir, storedAuthStateDir, runWithPluginInstallRoots); const run = async () => { if (isExecutionIdentityCollectionEnabled(runConfig)) try { stopLocalAuditWriter = (await import("./agent-local-audit-Cd_igdob.mjs")).startAgentLocalAuditWriter(runConfig, { stateDir }); } catch {} return await toolBudget.run(() => withHostExecInheritedEnvOmitted(listKnownProviderAuthEnvVarNamesCore({ env: process.env }), runWithAuthScope)); }; const result = await runtimeCleanup.run(() => temporaryDatabaseScope ? temporaryDatabaseScope.run(run) : run()); signal.throwIfAborted(); if (!result) throw new Error("Agent run returned no result"); const envelope = classifyAgentExecResult(result, fallbackExhausted, resultErrorPayload); if (!envelope.sessionId) envelope.sessionId = sessionId; commandResult = resultForEnvelope(envelope); } catch (error) { commandResult = resultForEnvelope(errorEnvelope(error, sessionId)); } let cleanupError = runtimeCleanup.outcome === "uncertain" ? /* @__PURE__ */ new Error("Agent runtime cleanup did not settle; state ownership retained until this process exits") : void 0; clearTimeout(timeoutTimer); if (cleanupProcessScope) { abortController.abort(/* @__PURE__ */ new Error("Agent execution completed")); try { await cleanupProcessScope(); } catch (error) { cleanupError = error; } } const stopAudit = async () => await stopLocalAuditWriter?.(); await (temporaryDatabaseScope ? temporaryDatabaseScope.run(stopAudit) : stopAudit()).catch(() => void 0); if (!cleanupError) await temporaryDatabaseScope?.close().catch((error) => { cleanupError = error; }); if (!cleanupError) await stateLock?.release().catch((error) => { cleanupError = error; }); const runCleanupStep = (step) => { try { step(); } catch (error) { cleanupError ??= error; } }; runCleanupStep(() => restoreEnvironment?.()); runCleanupStep(() => restoreConfigEnvironment?.()); runCleanupStep(() => configIo?.clearConfigCache()); runCleanupStep(() => restoreRuntimeConfigSnapshot ? restoreRuntimeConfigSnapshot() : configIo?.clearRuntimeConfigSnapshot()); runCleanupStep(() => runtimePaths?.pinRuntimePaths()); if (temporaryStateDir && !cleanupError) try { await fs.rm(temporaryStateDir, { recursive: true, force: true }); } catch (error) { cleanupError ??= error; } if (cleanupError) { recordAgentCleanupFailure(); const cleanupFailure = /* @__PURE__ */ new Error(`Agent exec cleanup failed: ${formatErrorMessage(cleanupError)}`); if (commandResult.envelope.ok) commandResult = resultForEnvelope(errorEnvelope(cleanupFailure, sessionId)); else runtime.error(cleanupFailure.message); } const receivedSignal = signalBridge?.getReceivedSignal(); signalBridge?.dispose(); if (receivedSignal) { runtime.exit(receivedSignal === "SIGINT" ? 130 : 143, { resetStream: process.stderr }); return commandResult; } writeAgentExecOutput(runtime, commandResult.envelope, opts.json === true); return commandResult; } //#endregion export { agentExecCommand };