From 91d20781ed031ac0c247f343d608c7e857f50cbb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 4 Apr 2026 14:41:37 +0100 Subject: [PATCH] refactor: extract isolated cron execution seams --- .../isolated-agent/run-execution.runtime.ts | 17 ++++ .../isolated-agent/run-fallback-policy.ts | 22 +++++ src/cron/isolated-agent/run-session-state.ts | 96 +++++++++++++++++++ src/cron/isolated-agent/run.runtime.ts | 22 +---- 4 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 src/cron/isolated-agent/run-execution.runtime.ts create mode 100644 src/cron/isolated-agent/run-fallback-policy.ts create mode 100644 src/cron/isolated-agent/run-session-state.ts diff --git a/src/cron/isolated-agent/run-execution.runtime.ts b/src/cron/isolated-agent/run-execution.runtime.ts new file mode 100644 index 00000000000..9688bff4661 --- /dev/null +++ b/src/cron/isolated-agent/run-execution.runtime.ts @@ -0,0 +1,17 @@ +export { resolveEffectiveModelFallbacks } from "../../agents/agent-scope.js"; +export { resolveBootstrapWarningSignaturesSeen } from "../../agents/bootstrap-budget.js"; +export { getCliSessionId, runCliAgent } from "../../agents/cli-runner.runtime.js"; +export { resolveFastModeState } from "../../agents/fast-mode.js"; +export { resolveNestedAgentLane } from "../../agents/lanes.js"; +export { LiveSessionModelSwitchError } from "../../agents/live-model-switch.js"; +export { runWithModelFallback } from "../../agents/model-fallback.js"; +export { isCliProvider } from "../../agents/model-selection.js"; +export { runEmbeddedPiAgent } from "../../agents/pi-embedded.js"; +export { + countActiveDescendantRuns, + listDescendantRunsForRequester, +} from "../../agents/subagent-registry.js"; +export { normalizeVerboseLevel } from "../../auto-reply/thinking.js"; +export { resolveSessionTranscriptPath } from "../../config/sessions.js"; +export { registerAgentRunContext } from "../../infra/agent-events.js"; +export { logWarn } from "../../logger.js"; diff --git a/src/cron/isolated-agent/run-fallback-policy.ts b/src/cron/isolated-agent/run-fallback-policy.ts new file mode 100644 index 00000000000..1a56c2bf4c6 --- /dev/null +++ b/src/cron/isolated-agent/run-fallback-policy.ts @@ -0,0 +1,22 @@ +import type { OpenClawConfig } from "../../config/config.js"; +import type { CronJob } from "../types.js"; +import { resolveEffectiveModelFallbacks } from "./run-execution.runtime.js"; + +export function resolveCronFallbacksOverride(params: { + cfg: OpenClawConfig; + job: CronJob; + agentId: string; +}): string[] | undefined { + const payload = params.job.payload.kind === "agentTurn" ? params.job.payload : undefined; + const payloadFallbacks = Array.isArray(payload?.fallbacks) ? payload.fallbacks : undefined; + const hasCronPayloadModelOverride = + typeof payload?.model === "string" && payload.model.trim().length > 0; + return ( + payloadFallbacks ?? + resolveEffectiveModelFallbacks({ + cfg: params.cfg, + agentId: params.agentId, + hasSessionModelOverride: hasCronPayloadModelOverride, + }) + ); +} diff --git a/src/cron/isolated-agent/run-session-state.ts b/src/cron/isolated-agent/run-session-state.ts new file mode 100644 index 00000000000..80824f29497 --- /dev/null +++ b/src/cron/isolated-agent/run-session-state.ts @@ -0,0 +1,96 @@ +import type { LiveSessionModelSelection } from "../../agents/live-model-switch.js"; +import type { SkillSnapshot } from "../../agents/skills.js"; +import type { SessionEntry } from "../../config/sessions.js"; +import type { resolveCronSession } from "./session.js"; + +type MutableSessionStore = Record; + +export type MutableCronSessionEntry = SessionEntry; +export type MutableCronSession = ReturnType & { + store: MutableSessionStore; + sessionEntry: MutableCronSessionEntry; +}; +export type CronLiveSelection = LiveSessionModelSelection; + +type UpdateSessionStore = ( + storePath: string, + update: (store: MutableSessionStore) => void, +) => Promise; + +export type PersistCronSessionEntry = () => Promise; + +export function createPersistCronSessionEntry(params: { + isFastTestEnv: boolean; + cronSession: MutableCronSession; + agentSessionKey: string; + runSessionKey: string; + updateSessionStore: UpdateSessionStore; +}): PersistCronSessionEntry { + return async () => { + if (params.isFastTestEnv) { + return; + } + params.cronSession.store[params.agentSessionKey] = params.cronSession.sessionEntry; + if (params.runSessionKey !== params.agentSessionKey) { + params.cronSession.store[params.runSessionKey] = params.cronSession.sessionEntry; + } + await params.updateSessionStore(params.cronSession.storePath, (store) => { + store[params.agentSessionKey] = params.cronSession.sessionEntry; + if (params.runSessionKey !== params.agentSessionKey) { + store[params.runSessionKey] = params.cronSession.sessionEntry; + } + }); + }; +} + +export async function persistCronSkillsSnapshotIfChanged(params: { + isFastTestEnv: boolean; + cronSession: MutableCronSession; + skillsSnapshot: SkillSnapshot; + nowMs: number; + persistSessionEntry: PersistCronSessionEntry; +}) { + if ( + params.isFastTestEnv || + params.skillsSnapshot === params.cronSession.sessionEntry.skillsSnapshot + ) { + return; + } + params.cronSession.sessionEntry = { + ...params.cronSession.sessionEntry, + updatedAt: params.nowMs, + skillsSnapshot: params.skillsSnapshot, + }; + await params.persistSessionEntry(); +} + +export function markCronSessionPreRun(params: { + entry: MutableCronSessionEntry; + provider: string; + model: string; +}) { + params.entry.modelProvider = params.provider; + params.entry.model = params.model; + params.entry.systemSent = true; +} + +export function syncCronSessionLiveSelection(params: { + entry: MutableCronSessionEntry; + liveSelection: CronLiveSelection; +}) { + params.entry.modelProvider = params.liveSelection.provider; + params.entry.model = params.liveSelection.model; + if (params.liveSelection.authProfileId) { + params.entry.authProfileOverride = params.liveSelection.authProfileId; + params.entry.authProfileOverrideSource = params.liveSelection.authProfileIdSource; + if (params.liveSelection.authProfileIdSource === "auto") { + params.entry.authProfileOverrideCompactionCount = params.entry.compactionCount ?? 0; + } else { + delete params.entry.authProfileOverrideCompactionCount; + } + return; + } + delete params.entry.authProfileOverride; + delete params.entry.authProfileOverrideSource; + delete params.entry.authProfileOverrideCompactionCount; +} diff --git a/src/cron/isolated-agent/run.runtime.ts b/src/cron/isolated-agent/run.runtime.ts index 0f15a6462cd..a2080565004 100644 --- a/src/cron/isolated-agent/run.runtime.ts +++ b/src/cron/isolated-agent/run.runtime.ts @@ -1,24 +1,17 @@ export { resolveAgentConfig, resolveAgentDir, - resolveEffectiveModelFallbacks, resolveAgentModelFallbacksOverride, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveAgentSkillsFilter, } from "../../agents/agent-scope.js"; export { resolveSessionAuthProfileOverride } from "../../agents/auth-profiles/session-override.js"; -export { resolveBootstrapWarningSignaturesSeen } from "../../agents/bootstrap-budget.js"; -export { runCliAgent } from "../../agents/cli-runner.js"; -export { getCliSessionId, setCliSessionId } from "../../agents/cli-session.js"; +export { setCliSessionId } from "../../agents/cli-session.js"; export { lookupContextTokens } from "../../agents/context.js"; export { resolveCronStyleNow } from "../../agents/current-time.js"; export { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js"; -export { resolveFastModeState } from "../../agents/fast-mode.js"; -export { resolveNestedAgentLane } from "../../agents/lanes.js"; -export { LiveSessionModelSwitchError } from "../../agents/live-model-switch.js"; export { loadModelCatalog } from "../../agents/model-catalog.js"; -export { runWithModelFallback } from "../../agents/model-fallback.js"; export { getModelRefStatus, isCliProvider, @@ -28,30 +21,19 @@ export { resolveHooksGmailModel, resolveThinkingDefault, } from "../../agents/model-selection.js"; -export { runEmbeddedPiAgent } from "../../agents/pi-embedded.js"; export { buildWorkspaceSkillSnapshot } from "../../agents/skills.js"; export { getSkillsSnapshotVersion } from "../../agents/skills/refresh.js"; -export { - countActiveDescendantRuns, - listDescendantRunsForRequester, -} from "../../agents/subagent-registry.js"; export { runSubagentAnnounceFlow } from "../../agents/subagent-announce.js"; export { resolveAgentTimeoutMs } from "../../agents/timeout.js"; export { deriveSessionTotalTokens, hasNonzeroUsage } from "../../agents/usage.js"; export { DEFAULT_IDENTITY_FILENAME, ensureAgentWorkspace } from "../../agents/workspace.js"; -export { - normalizeThinkLevel, - normalizeVerboseLevel, - supportsXHighThinking, -} from "../../auto-reply/thinking.js"; +export { normalizeThinkLevel, supportsXHighThinking } from "../../auto-reply/thinking.js"; export { createOutboundSendDeps } from "../../cli/outbound-send-deps.js"; export { resolveAgentMainSessionKey, - resolveSessionTranscriptPath, setSessionRuntimeModel, updateSessionStore, } from "../../config/sessions.js"; -export { registerAgentRunContext } from "../../infra/agent-events.js"; export { deliverOutboundPayloads } from "../../infra/outbound/deliver.js"; export { getRemoteSkillEligibility } from "../../infra/skills-remote.js"; export { logWarn } from "../../logger.js";