From 45d3e62f5068d6e6595d57ce50f0f1a4adf48eba Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 00:30:29 +0000 Subject: [PATCH] refactor(cron): extract agent defaults merge helpers --- src/cron/isolated-agent/run.ts | 78 ++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 2055f4eddbd..fe8e9b86f40 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -94,6 +94,54 @@ export type RunCronAgentTurnResult = { } & CronRunOutcome & CronRunTelemetry; +type ResolvedAgentConfig = NonNullable>; + +function extractCronAgentDefaultsOverride(agentConfigOverride?: ResolvedAgentConfig) { + const { + model: overrideModel, + sandbox: _agentSandboxOverride, + ...agentOverrideRest + } = agentConfigOverride ?? {}; + return { + overrideModel, + definedOverrides: Object.fromEntries( + Object.entries(agentOverrideRest).filter(([, value]) => value !== undefined), + ) as Partial, + }; +} + +function mergeCronAgentModelOverride(params: { + defaults: AgentDefaultsConfig; + overrideModel: ResolvedAgentConfig["model"] | undefined; +}) { + const nextDefaults: AgentDefaultsConfig = { ...params.defaults }; + const existingModel = + nextDefaults.model && typeof nextDefaults.model === "object" ? nextDefaults.model : {}; + if (typeof params.overrideModel === "string") { + nextDefaults.model = { ...existingModel, primary: params.overrideModel }; + } else if (params.overrideModel) { + nextDefaults.model = { ...existingModel, ...params.overrideModel }; + } + return nextDefaults; +} + +function buildCronAgentDefaultsConfig(params: { + defaults?: AgentDefaultsConfig; + agentConfigOverride?: ResolvedAgentConfig; +}) { + const { overrideModel, definedOverrides } = extractCronAgentDefaultsOverride( + params.agentConfigOverride, + ); + // Keep sandbox overrides out of `agents.defaults` here. Sandbox resolution + // already merges global defaults with per-agent overrides using `agentId`; + // copying the agent sandbox into defaults clobbers global defaults and can + // double-apply nested agent overrides during isolated cron runs. + return mergeCronAgentModelOverride({ + defaults: Object.assign({}, params.defaults, definedOverrides), + overrideModel, + }); +} + export async function runCronIsolatedAgentTurn(params: { cfg: OpenClawConfig; deps: CliDeps; @@ -125,36 +173,14 @@ export async function runCronIsolatedAgentTurn(params: { const agentConfigOverride = normalizedRequested ? resolveAgentConfig(params.cfg, normalizedRequested) : undefined; - const { - model: overrideModel, - sandbox: _agentSandboxOverride, - ...agentOverrideRest - } = agentConfigOverride ?? {}; // Use the requested agentId even when there is no explicit agent config entry. // This ensures auth-profiles, workspace, and agentDir all resolve to the // correct per-agent paths (e.g. ~/.openclaw/agents//agent/). const agentId = normalizedRequested ?? defaultAgentId; - // Keep sandbox overrides out of `agents.defaults` here. Sandbox resolution - // already merges global defaults with per-agent overrides using `agentId`; - // copying the agent sandbox into defaults clobbers global defaults and can - // double-apply nested agent overrides during isolated cron runs. - const definedOverrides = Object.fromEntries( - Object.entries(agentOverrideRest).filter(([, value]) => value !== undefined), - ); - const agentCfg: AgentDefaultsConfig = Object.assign( - {}, - params.cfg.agents?.defaults, - definedOverrides as Partial, - ); - // Merge agent model override with defaults instead of replacing, so that - // `fallbacks` from `agents.defaults.model` are preserved when the agent - // (or its per-cron model pin) only specifies `primary`. - const existingModel = agentCfg.model && typeof agentCfg.model === "object" ? agentCfg.model : {}; - if (typeof overrideModel === "string") { - agentCfg.model = { ...existingModel, primary: overrideModel }; - } else if (overrideModel) { - agentCfg.model = { ...existingModel, ...overrideModel }; - } + const agentCfg = buildCronAgentDefaultsConfig({ + defaults: params.cfg.agents?.defaults, + agentConfigOverride, + }); const cfgWithAgentDefaults: OpenClawConfig = { ...params.cfg, agents: Object.assign({}, params.cfg.agents, { defaults: agentCfg }),