From 79ecea913d78ac92cb377f7f435585640562109b Mon Sep 17 00:00:00 2001 From: pi Date: Sun, 12 Apr 2026 13:05:05 +0100 Subject: [PATCH] tool: avoid mutating discovered presets; use local normalized model values/shallow copies --- src/tool.ts | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/tool.ts b/src/tool.ts index fd7bb05..f766652 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -104,6 +104,10 @@ export function createSubagentTool(deps: { const discovery = (deps.discoverSubagentPresets ?? discoverSubagentPresets)(ctx.cwd); const presets = discovery.presets; + // Note: presets are discovered/owned by the discovery layer and should be treated + // as immutable. Do not mutate preset objects in place. When a canonical/normalized + // model value is needed, use a local variable or a shallow copy of the preset. + // Adapter: accept only the flattened shape { callModel?, presetModel? } // to keep the tool logic simple. If a resolver was injected with the // older test/hooks shape, we call it with both key names so it can read @@ -207,7 +211,9 @@ export function createSubagentTool(deps: { params.model = normalized; } - // Validate preset default model if present + // Validate preset default model if present. Do not mutate discovered preset objects — + // keep a local normalized value (or a shallow copy when passing to runTask). + let normalizedPresetModelForSelection: string | undefined = undefined; if (preset.model !== undefined) { const normalizedPresetModel = normalizeModelReference(preset.model); if (!normalizedPresetModel) { @@ -216,12 +222,11 @@ export function createSubagentTool(deps: { "single", ); } - // Use canonical preset model - preset.model = normalizedPresetModel; + normalizedPresetModelForSelection = normalizedPresetModel; } // Ensure an effective model exists for this run (explicit override wins) - const singleSelection = callResolveChildModel({ callModel: params.model, presetModel: preset.model }); + const singleSelection = callResolveChildModel({ callModel: params.model, presetModel: normalizedPresetModelForSelection }); const singleRequested = singleSelection.requestedModel ?? params.model ?? preset.model; if (!singleRequested) { return makeErrorResult( @@ -231,10 +236,11 @@ export function createSubagentTool(deps: { } try { + const presetForRun = normalizedPresetModelForSelection === undefined ? preset : { ...preset, model: normalizedPresetModelForSelection }; const result = await runTask({ task: params.task, cwd: params.cwd, - preset, + preset: presetForRun, taskModel: params.model, mode: "single", }); @@ -273,6 +279,8 @@ export function createSubagentTool(deps: { t.model = normalized; } + // Validate preset default model. Do not mutate the discovered preset object. + let normalizedPresetModelForTask: string | undefined = undefined; if (preset.model !== undefined) { const normalizedPresetModel = normalizeModelReference(preset.model); if (!normalizedPresetModel) { @@ -281,12 +289,12 @@ export function createSubagentTool(deps: { "parallel", ); } - preset.model = normalizedPresetModel; + normalizedPresetModelForTask = normalizedPresetModel; } // Ensure an effective model exists for this task - const sel = callResolveChildModel({ callModel: t.model, presetModel: preset.model }); - const requested = sel.requestedModel ?? t.model ?? preset.model; + const sel = callResolveChildModel({ callModel: t.model, presetModel: normalizedPresetModelForTask }); + const requested = sel.requestedModel ?? t.model ?? normalizedPresetModelForTask; if (!requested) { return makeErrorResult( `Parallel task ${index + 1} has no model. Provide an explicit 'model' or set a default model on preset "${preset.name}". Available models: ${availableModelsText}`, @@ -311,12 +319,14 @@ export function createSubagentTool(deps: { const liveResults: SubagentRunResult[] = []; const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (task: any, index) => { const preset = presets.find((p) => p.name === task.preset)!; + const normalizedPresetModel = preset.model === undefined ? undefined : normalizeModelReference(preset.model); + const presetForRun = normalizedPresetModel === undefined ? preset : { ...preset, model: normalizedPresetModel }; const result = await runTask({ task: task.task, cwd: task.cwd, taskModel: task.model, taskIndex: index, - preset, + preset: presetForRun, mode: "parallel", }); liveResults[index] = result;