UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

339 lines (338 loc) 15.9 kB
import { t as createLazyImportLoader } from "./lazy-promise-BONnzNfb.js"; import { a as logWarn } from "./logger-lqqYRtFw.js"; import { g as normalizeVerboseLevel } from "./thinking-OG7pb4lf.js"; import { u as registerAgentRunContext } from "./agent-events-C1B8VhOg.js"; import { o as resolveSessionTranscriptPath } from "./paths-NEwU8m3X.js"; import { h as isCliProvider } from "./model-selection-CA_65iXi.js"; import { a as resolveCliRuntimeExecutionProvider } from "./model-runtime-aliases-6XX4uD6a.js"; import { f as LiveSessionModelSwitchError, o as runWithModelFallback } from "./model-fallback-BvQwEryh.js"; import { r as resolveCronAgentLane } from "./lanes-CI0_P-yC.js"; import { l as resolveBootstrapWarningSignaturesSeen } from "./bootstrap-budget-bmmzkOyn.js"; import { t as ensureSelectedAgentHarnessPlugin } from "./runtime-plugin-CzEDIZOU.js"; import { a as syncCronSessionLiveSelection, c as getCliSessionId, d as resolveCurrentChannelTarget, l as runCliAgent, o as resolveCronFallbacksOverride, u as resolveCronChannelOutputPolicy } from "./run-session-state-D2OnW1-O.js"; import { i as resolveCronPayloadOutcome } from "./helpers-CYQZyDV5.js"; import { n as isLikelyInterimCronMessage } from "./subagent-followup-hints-WJCMG0pw.js"; import { createHash } from "node:crypto"; //#region src/cron/isolated-agent/run-executor.ts /** Executes isolated cron prompts with model fallbacks and interim-ack retries. */ const cronEmbeddedRuntimeLoader = createLazyImportLoader(() => import("./run-embedded.runtime.js")); const cronSubagentRegistryRuntimeLoader = createLazyImportLoader(() => import("./run-subagent-registry.runtime.js")); async function loadCronEmbeddedRuntime() { return await cronEmbeddedRuntimeLoader.load(); } async function loadCronSubagentRegistryRuntime() { return await cronSubagentRegistryRuntimeLoader.load(); } const COMMAND_STYLE_CRON_PREFIX = /^(?:(?:[A-Z_][A-Z0-9_]*=\S+\s+)+)?(?:cd\s+\S+|(?:\.{1,2}|~)?\/\S+|[A-Za-z]:[\\/]\S+|(?:bash|bun|cargo|deno|docker|gh|git|go|make|node|npm|npx|pnpm|python|python3|ruby|sh|tsx|uv|zsh)\b)/u; function resolveIsolatedCronPromptCacheKey(params) { if (params.job.sessionTarget !== "isolated") return; const material = JSON.stringify({ version: 1, kind: "isolated-cron", jobId: params.job.id, agentId: params.agentId, agentSessionKey: params.agentSessionKey, provider: params.provider, model: params.model }); return `openclaw-cron-${createHash("sha256").update(material).digest("hex").slice(0, 32)}`; } /** Detects single-line cron prompts that look like shell commands or command invocations. */ function isCommandStyleCronMessage(message) { const trimmed = message.trim(); if (!trimmed || trimmed.includes("\n")) return false; return COMMAND_STYLE_CRON_PREFIX.test(trimmed); } function resolveCronBootstrapContextMode(payload) { if (payload?.lightContext === true) return "lightweight"; if (payload?.lightContext === false) return; return isCommandStyleCronMessage(payload?.message ?? "") ? "lightweight" : void 0; } /** Creates the model-fallback executor for one isolated cron prompt run. */ function createCronPromptExecutor(params) { const sessionFile = params.cronSession.sessionEntry.sessionFile?.trim() || resolveSessionTranscriptPath(params.cronSession.sessionEntry.sessionId, params.agentId); if (!params.cronSession.sessionEntry.sessionFile?.trim()) params.cronSession.sessionEntry.sessionFile = sessionFile; const cronFallbacksOverride = params.modelFallbacksOverride ?? resolveCronFallbacksOverride({ cfg: params.cfg, job: params.job, agentId: params.agentId, useSubagentFallbacks: params.useSubagentFallbacks, inheritDefaultFallbacksForAgentStringModel: params.inheritDefaultFallbacksForAgentStringModel }); let runResult; let fallbackProvider = params.liveSelection.provider; let fallbackModel = params.liveSelection.model; let runEndedAt = Date.now(); let bootstrapPromptWarningSignaturesSeen = resolveBootstrapWarningSignaturesSeen(params.cronSession.sessionEntry.systemPromptReport); const bootstrapContextMode = resolveCronBootstrapContextMode(params.agentPayload); const sourceReplyDeliveryMode = params.sourceDelivery.sourceReplyDeliveryMode; const messageChannel = params.sourceDelivery.target.channel ?? params.resolvedDelivery.channel; const runPrompt = async (promptText) => { const fallbackResult = await runWithModelFallback({ cfg: params.cfgWithAgentDefaults, provider: params.liveSelection.provider, model: params.liveSelection.model, runId: params.cronSession.sessionEntry.sessionId, sessionId: params.cronSession.sessionEntry.sessionId, lane: resolveCronAgentLane(params.lane), agentDir: params.agentDir, agentId: params.agentId, sessionKey: params.runSessionKey, prepareAgentHarnessRuntime: async ({ provider, model, agentHarnessRuntimeOverride }) => { await ensureSelectedAgentHarnessPlugin({ config: params.cfgWithAgentDefaults, provider, modelId: model, agentId: params.agentId, sessionKey: params.runSessionKey, agentHarnessRuntimeOverride, workspaceDir: params.workspaceDir }); }, fallbacksOverride: cronFallbacksOverride, run: async (providerOverride, modelOverride, runOptions) => { if (params.abortSignal?.aborted) throw new Error(params.abortReason()); const executionProvider = resolveCliRuntimeExecutionProvider({ provider: providerOverride, cfg: params.cfgWithAgentDefaults, agentId: params.agentId, modelId: modelOverride }) ?? providerOverride; const bootstrapPromptWarningSignature = bootstrapPromptWarningSignaturesSeen[bootstrapPromptWarningSignaturesSeen.length - 1]; if (isCliProvider(executionProvider, params.cfgWithAgentDefaults)) { const cliSessionId = params.cronSession.isNewSession ? void 0 : await getCliSessionId(params.cronSession.sessionEntry, executionProvider); const result = await runCliAgent({ sessionId: params.cronSession.sessionEntry.sessionId, sessionKey: params.runSessionKey, sessionEntry: params.cronSession.sessionEntry, agentId: params.agentId, trigger: "cron", jobId: params.job.id, sessionFile, workspaceDir: params.workspaceDir, config: params.cfgWithAgentDefaults, prompt: promptText, provider: executionProvider, model: modelOverride, thinkLevel: params.thinkLevel, timeoutMs: params.timeoutMs, runId: params.cronSession.sessionEntry.sessionId, lane: resolveCronAgentLane(params.lane), cliSessionId, skillsSnapshot: params.skillsSnapshot, messageChannel, sourceReplyDeliveryMode, abortSignal: params.abortSignal, onExecutionStarted: params.onExecutionStarted, onExecutionPhase: params.onExecutionPhase, bootstrapContextMode, bootstrapContextRunKind: "cron", bootstrapPromptWarningSignaturesSeen, bootstrapPromptWarningSignature }); bootstrapPromptWarningSignaturesSeen = resolveBootstrapWarningSignaturesSeen(result.meta?.systemPromptReport); return result; } const { resolveFastModeState, runEmbeddedAgent } = await loadCronEmbeddedRuntime(); const promptCacheKey = resolveIsolatedCronPromptCacheKey({ job: params.job, agentId: params.agentId, agentSessionKey: params.agentSessionKey, provider: providerOverride, model: modelOverride }); const currentChannelId = await resolveCurrentChannelTarget({ channel: messageChannel, to: params.resolvedDelivery.to, threadId: params.resolvedDelivery.threadId }); const result = await runEmbeddedAgent({ sessionId: params.cronSession.sessionEntry.sessionId, sessionKey: params.runSessionKey, promptCacheKey, agentId: params.agentId, trigger: "cron", jobId: params.job.id, cleanupBundleMcpOnRunEnd: params.job.sessionTarget === "isolated", allowGatewaySubagentBinding: true, messageChannel, agentAccountId: params.resolvedDelivery.accountId, messageTo: params.resolvedDelivery.to, messageThreadId: params.resolvedDelivery.threadId, currentChannelId, sessionFile, agentDir: params.agentDir, workspaceDir: params.workspaceDir, config: params.cfgWithAgentDefaults, skillsSnapshot: params.skillsSnapshot, prompt: promptText, lane: resolveCronAgentLane(params.lane), provider: providerOverride, model: modelOverride, modelFallbacksOverride: cronFallbacksOverride, authProfileId: params.liveSelection.authProfileId, authProfileIdSource: params.liveSelection.authProfileId ? params.liveSelection.authProfileIdSource : void 0, thinkLevel: params.thinkLevel, fastMode: resolveFastModeState({ cfg: params.cfgWithAgentDefaults, provider: providerOverride, model: modelOverride, agentId: params.agentId, sessionEntry: params.cronSession.sessionEntry }).enabled, verboseLevel: params.resolvedVerboseLevel, timeoutMs: params.timeoutMs, runTimeoutOverrideMs: params.runTimeoutOverrideMs, bootstrapContextMode, bootstrapContextRunKind: "cron", toolsAllow: params.agentPayload?.toolsAllow, execOverrides: params.suppressExecNotifyOnExit ? { notifyOnExit: false, notifyOnExitEmptySuccess: false } : void 0, sourceReplyDeliveryMode, runId: params.cronSession.sessionEntry.sessionId, requireExplicitMessageTarget: params.sourceDelivery.messageTool.requireExplicitTarget, disableMessageTool: !params.sourceDelivery.messageTool.enabled, forceMessageTool: params.sourceDelivery.messageTool.force, allowTransientCooldownProbe: runOptions?.allowTransientCooldownProbe, abortSignal: params.abortSignal, onExecutionStarted: params.onExecutionStarted, onExecutionPhase: params.onExecutionPhase, bootstrapPromptWarningSignaturesSeen, bootstrapPromptWarningSignature }); bootstrapPromptWarningSignaturesSeen = resolveBootstrapWarningSignaturesSeen(result.meta?.systemPromptReport); return result; } }); runResult = fallbackResult.result; fallbackProvider = fallbackResult.provider; fallbackModel = fallbackResult.model; params.liveSelection.provider = fallbackResult.provider; params.liveSelection.model = fallbackResult.model; runEndedAt = Date.now(); }; return { runPrompt, getState: () => ({ runResult, fallbackProvider, fallbackModel, runEndedAt, liveSelection: params.liveSelection }) }; } /** Executes an isolated cron prompt, including live model-switch and interim-ack retries. */ async function executeCronRun(params) { const resolvedVerboseLevel = normalizeVerboseLevel(params.cronSession.sessionEntry.verboseLevel) ?? normalizeVerboseLevel(params.agentVerboseDefault) ?? "off"; registerAgentRunContext(params.cronSession.sessionEntry.sessionId, { sessionKey: params.runSessionKey, sessionId: params.cronSession.sessionEntry.sessionId, verboseLevel: resolvedVerboseLevel }); const executor = createCronPromptExecutor({ cfg: params.cfg, cfgWithAgentDefaults: params.cfgWithAgentDefaults, job: params.job, agentId: params.agentId, agentDir: params.agentDir, agentSessionKey: params.agentSessionKey, runSessionKey: params.runSessionKey, workspaceDir: params.workspaceDir, lane: params.lane, resolvedVerboseLevel, thinkLevel: params.thinkLevel, timeoutMs: params.timeoutMs, runTimeoutOverrideMs: params.runTimeoutOverrideMs, suppressExecNotifyOnExit: params.suppressExecNotifyOnExit, resolvedDelivery: params.resolvedDelivery, deliveryRequested: params.deliveryRequested, sourceDelivery: params.sourceDelivery, skillsSnapshot: params.skillsSnapshot, agentPayload: params.agentPayload, useSubagentFallbacks: params.useSubagentFallbacks, inheritDefaultFallbacksForAgentStringModel: params.inheritDefaultFallbacksForAgentStringModel, modelFallbacksOverride: params.modelFallbacksOverride, liveSelection: params.liveSelection, cronSession: params.cronSession, abortSignal: params.abortSignal, abortReason: params.abortReason, onExecutionStarted: params.onExecutionStarted, onExecutionPhase: params.onExecutionPhase }); const runStartedAt = params.runStartedAt ?? Date.now(); const MAX_MODEL_SWITCH_RETRIES = 2; let modelSwitchRetries = 0; while (true) try { await executor.runPrompt(params.commandBody); break; } catch (err) { if (!(err instanceof LiveSessionModelSwitchError)) throw err; modelSwitchRetries += 1; if (modelSwitchRetries > MAX_MODEL_SWITCH_RETRIES) { logWarn(`[cron:${params.job.id}] LiveSessionModelSwitchError retry limit reached (${MAX_MODEL_SWITCH_RETRIES}); aborting`); throw err; } params.liveSelection.provider = err.provider; params.liveSelection.model = err.model; params.liveSelection.authProfileId = err.authProfileId; params.liveSelection.authProfileIdSource = err.authProfileId ? err.authProfileIdSource : void 0; syncCronSessionLiveSelection({ entry: params.cronSession.sessionEntry, liveSelection: params.liveSelection }); try { await params.persistSessionEntry(); } catch (persistErr) { logWarn(`[cron:${params.job.id}] Failed to persist model switch session entry: ${String(persistErr)}`); } continue; } let { runResult, fallbackProvider, fallbackModel, runEndedAt } = executor.getState(); if (!runResult) throw new Error("cron isolated run returned no result"); if (!params.isAborted()) { const interimPayloads = runResult.payloads ?? []; const { deliveryPayloadHasStructuredContent: interimPayloadHasStructuredContent, hasFatalErrorPayload: interimHasFatalErrorPayload, outputText: interimOutputText } = resolveCronPayloadOutcome({ payloads: interimPayloads, runLevelError: runResult.meta?.error, failureSignal: runResult.meta?.failureSignal, finalAssistantVisibleText: runResult.meta?.finalAssistantVisibleText, preferFinalAssistantVisibleText: (await resolveCronChannelOutputPolicy(params.resolvedDelivery.channel, { deliveryRequested: params.deliveryRequested })).preferFinalAssistantVisibleText }); const interimText = interimOutputText?.trim() ?? ""; const shouldRetryInterimAck = !runResult.meta?.error && !interimHasFatalErrorPayload && !runResult.didSendViaMessagingTool && !interimPayloadHasStructuredContent && !interimPayloads.some((payload) => payload?.isError === true) && isLikelyInterimCronMessage(interimText); let hasFreshDescendants = false; let hasActiveDescendants = false; if (shouldRetryInterimAck) { const { countActiveDescendantRuns, listDescendantRunsForRequester } = await loadCronSubagentRegistryRuntime(); hasFreshDescendants = listDescendantRunsForRequester(params.runSessionKey).some((entry) => { const descendantStartedAt = typeof entry.startedAt === "number" ? entry.startedAt : entry.createdAt; return typeof descendantStartedAt === "number" && descendantStartedAt >= runStartedAt; }); hasActiveDescendants = countActiveDescendantRuns(params.runSessionKey) > 0; } if (shouldRetryInterimAck && !hasFreshDescendants && !hasActiveDescendants) { const continuationPrompt = [ "Your previous response was only an acknowledgement and did not complete this cron task.", "Complete the original task now.", "Do not send a status update like 'on it'.", "Use tools when needed, including sessions_spawn for parallel subtasks, wait for spawned subagents to finish, then return only the final summary." ].join(" "); await executor.runPrompt(continuationPrompt); ({runResult, fallbackProvider, fallbackModel, runEndedAt} = executor.getState()); } } if (!runResult) throw new Error("cron isolated run returned no result"); return { runResult, fallbackProvider, fallbackModel, runStartedAt, runEndedAt, liveSelection: params.liveSelection }; } //#endregion export { executeCronRun };