fix: honor subagent spawn model overrides · openclaw/openclaw@6c0cdf4
steipete
·
2026-04-28
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai
|
40 | 40 | ### Fixes |
41 | 41 | |
42 | 42 | - Gateway/startup: keep value-option foreground starts on the gateway fast path and skip proxy bootstrap unless proxy env is configured, reducing normal gateway startup RSS and avoiding full CLI graph loading. Thanks @vincentkoc. |
| 43 | +- Subagents/models: persist `sessions_spawn.model` and configured subagent models as child-session model overrides before the first turn, so spawned subagents actually run on the requested provider/model instead of reverting to the target agent default. Fixes #73180. Thanks @danielzinhu99. |
43 | 44 | - Backup: skip installed plugin `extensions/*/node_modules` dependency trees while keeping plugin manifests and source files in archives, so local backups avoid rebuildable npm payload bloat. Fixes #64144. Thanks @BrilliantWang. |
44 | 45 | - Cron/models: fail isolated cron runs closed when an explicit `payload.model` is not allowed or cannot be resolved, so scheduled jobs do not silently fall back to an unrelated agent default or paid route before configured provider proxies such as LiteLLM can run. Fixes #73146. Thanks @oneandrewwang. |
45 | 46 | - Memory/QMD: back off repeated chat-turn QMD open failures while still letting memory status and CLI probes recheck immediately, so a broken sidecar dependency cannot trigger active-memory or cron retry storms. Fixes #73188 and #73176. Thanks @leonlushgit and @w3i-William. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -4,6 +4,7 @@ import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
4 | 4 | import { |
5 | 5 | resolveConfiguredSubagentRunTimeoutSeconds, |
6 | 6 | resolveSubagentModelAndThinkingPlan, |
| 7 | +splitModelRef, |
7 | 8 | } from "./subagent-spawn-plan.js"; |
8 | 9 | |
9 | 10 | function createConfig(overrides?: Record<string, unknown>): OpenClawConfig { |
@@ -26,10 +27,18 @@ describe("subagent spawn model + thinking plan", () => {
|
26 | 27 | modelApplied: true, |
27 | 28 | initialSessionPatch: { |
28 | 29 | model: "claude-haiku-4-5", |
| 30 | +modelOverrideSource: "user", |
29 | 31 | }, |
30 | 32 | }); |
31 | 33 | }); |
32 | 34 | |
| 35 | +it("preserves model ids containing slashes", () => { |
| 36 | +expect(splitModelRef("openrouter/meta-llama/llama-3.3-70b:free")).toEqual({ |
| 37 | +provider: "openrouter", |
| 38 | +model: "meta-llama/llama-3.3-70b:free", |
| 39 | +}); |
| 40 | +}); |
| 41 | + |
33 | 42 | it("normalizes thinking overrides into the initial patch", () => { |
34 | 43 | const plan = resolveSubagentModelAndThinkingPlan({ |
35 | 44 | cfg: createConfig(), |
@@ -69,7 +78,7 @@ describe("subagent spawn model + thinking plan", () => {
|
69 | 78 | expect(plan).toMatchObject({ |
70 | 79 | status: "ok", |
71 | 80 | resolvedModel: "minimax/MiniMax-M2.7", |
72 | | -initialSessionPatch: { model: "minimax/MiniMax-M2.7" }, |
| 81 | +initialSessionPatch: { model: "minimax/MiniMax-M2.7", modelOverrideSource: "auto" }, |
73 | 82 | }); |
74 | 83 | }); |
75 | 84 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -11,7 +11,14 @@ export function splitModelRef(ref?: string) {
|
11 | 11 | if (!trimmed) { |
12 | 12 | return { provider: undefined, model: undefined }; |
13 | 13 | } |
14 | | -const [provider, model] = trimmed.split("/", 2); |
| 14 | +const slash = trimmed.indexOf("/"); |
| 15 | +if (slash > 0 && slash < trimmed.length - 1) { |
| 16 | +const provider = trimmed.slice(0, slash); |
| 17 | +const model = trimmed.slice(slash + 1); |
| 18 | +return { provider, model }; |
| 19 | +} |
| 20 | +const provider = undefined; |
| 21 | +const model = trimmed; |
15 | 22 | if (model) { |
16 | 23 | return { provider, model }; |
17 | 24 | } |
@@ -66,7 +73,12 @@ export function resolveSubagentModelAndThinkingPlan(params: {
|
66 | 73 | modelApplied: Boolean(resolvedModel), |
67 | 74 | thinkingOverride: thinkingPlan.thinkingOverride, |
68 | 75 | initialSessionPatch: { |
69 | | - ...(resolvedModel ? { model: resolvedModel } : {}), |
| 76 | + ...(resolvedModel |
| 77 | + ? { |
| 78 | +model: resolvedModel, |
| 79 | +modelOverrideSource: params.modelOverride?.trim() ? "user" : "auto", |
| 80 | +} |
| 81 | + : {}), |
70 | 82 | ...thinkingPlan.initialSessionPatch, |
71 | 83 | }, |
72 | 84 | }; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -89,6 +89,7 @@ describe("spawnSubagentDirect runtime model persistence", () => {
|
89 | 89 | sessionKey: /^agent:main:subagent:/, |
90 | 90 | provider: "openai-codex", |
91 | 91 | model: "gpt-5.4", |
| 92 | +overrideSource: "user", |
92 | 93 | }); |
93 | 94 | expect(pruneLegacyStoreKeysMock).toHaveBeenCalledTimes(3); |
94 | 95 | expect(operations.indexOf("store:update")).toBeGreaterThan(-1); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -97,6 +97,7 @@ export function expectPersistedRuntimeModel(params: {
|
97 | 97 | sessionKey: string | RegExp; |
98 | 98 | provider: string; |
99 | 99 | model: string; |
| 100 | +overrideSource?: "auto" | "user"; |
100 | 101 | }) { |
101 | 102 | const [persistedKey, persistedEntry] = Object.entries(params.persistedStore ?? {})[0] ?? []; |
102 | 103 | if (typeof params.sessionKey === "string") { |
@@ -107,6 +108,9 @@ export function expectPersistedRuntimeModel(params: {
|
107 | 108 | expect(persistedEntry).toMatchObject({ |
108 | 109 | modelProvider: params.provider, |
109 | 110 | model: params.model, |
| 111 | +providerOverride: params.provider, |
| 112 | +modelOverride: params.model, |
| 113 | + ...(params.overrideSource ? { modelOverrideSource: params.overrideSource } : {}), |
110 | 114 | }); |
111 | 115 | } |
112 | 116 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -239,6 +239,7 @@ describe("spawnSubagentDirect seam flow", () => {
|
239 | 239 | sessionKey: childSessionKey, |
240 | 240 | provider: "openai-codex", |
241 | 241 | model: "gpt-5.4", |
| 242 | +overrideSource: "user", |
242 | 243 | }); |
243 | 244 | expect(operations.indexOf("store:update")).toBeGreaterThan(-1); |
244 | 245 | expect(operations.indexOf("gateway:agent")).toBeGreaterThan( |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -241,8 +241,11 @@ function buildDirectChildSessionPatch(patch: Record<string, unknown>): Partial<S
|
241 | 241 | const { provider, model } = splitModelRef(patch.model.trim()); |
242 | 242 | if (model) { |
243 | 243 | entry.model = model; |
| 244 | +entry.modelOverride = model; |
| 245 | +entry.modelOverrideSource = patch.modelOverrideSource === "auto" ? "auto" : "user"; |
244 | 246 | if (provider) { |
245 | 247 | entry.modelProvider = provider; |
| 248 | +entry.providerOverride = provider; |
246 | 249 | } |
247 | 250 | } |
248 | 251 | } |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。