mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 01:18:28 +00:00
fix(subagents): add model fallback support to sessions_spawn tool (#17197)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 5d20c2cd37
Co-authored-by: misterdas <170702047+misterdas@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight
This commit is contained in:
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Subagents/Models: preserve `agents.defaults.model.fallbacks` when subagent sessions carry a model override, so subagent runs fail over to configured fallback models instead of retrying only the overridden primary model.
|
||||||
- Group chats: always inject group chat context (name, participants, reply guidance) into the system prompt on every turn, not just the first. Prevents the model from losing awareness of which group it's in and incorrectly using the message tool to send to the same group. (#14447) Thanks @tyler6204.
|
- Group chats: always inject group chat context (name, participants, reply guidance) into the system prompt on every turn, not just the first. Prevents the model from losing awareness of which group it's in and incorrectly using the message tool to send to the same group. (#14447) Thanks @tyler6204.
|
||||||
- TUI: make searchable-select filtering and highlight rendering ANSI-aware so queries ignore hidden escape codes and no longer corrupt ANSI styling sequences during match highlighting. (#4519) Thanks @bee4come.
|
- TUI: make searchable-select filtering and highlight rendering ANSI-aware so queries ignore hidden escape codes and no longer corrupt ANSI styling sequences during match highlighting. (#4519) Thanks @bee4come.
|
||||||
- TUI/Windows: coalesce rapid single-line submit bursts in Git Bash into one multiline message as a fallback when bracketed paste is unavailable, preventing pasted multiline text from being split into multiple sends. (#4986) Thanks @adamkane.
|
- TUI/Windows: coalesce rapid single-line submit bursts in Git Bash into one multiline message as a fallback when bracketed paste is unavailable, preventing pasted multiline text from being split into multiple sends. (#4986) Thanks @adamkane.
|
||||||
|
|||||||
@@ -196,6 +196,71 @@ describe("agentCommand", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses default fallback list for session model overrides", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const store = path.join(home, "sessions.json");
|
||||||
|
fs.mkdirSync(path.dirname(store), { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
store,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
"agent:main:subagent:test": {
|
||||||
|
sessionId: "session-subagent",
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
providerOverride: "anthropic",
|
||||||
|
modelOverride: "claude-opus-4-5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
mockConfig(home, store, {
|
||||||
|
model: {
|
||||||
|
primary: "openai/gpt-4.1-mini",
|
||||||
|
fallbacks: ["openai/gpt-5.2"],
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
"anthropic/claude-opus-4-5": {},
|
||||||
|
"openai/gpt-4.1-mini": {},
|
||||||
|
"openai/gpt-5.2": {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(loadModelCatalog).mockResolvedValueOnce([
|
||||||
|
{ id: "claude-opus-4-5", name: "Opus", provider: "anthropic" },
|
||||||
|
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" },
|
||||||
|
{ id: "gpt-5.2", name: "GPT-5.2", provider: "openai" },
|
||||||
|
]);
|
||||||
|
vi.mocked(runEmbeddedPiAgent)
|
||||||
|
.mockRejectedValueOnce(Object.assign(new Error("rate limited"), { status: 429 }))
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
payloads: [{ text: "ok" }],
|
||||||
|
meta: {
|
||||||
|
durationMs: 5,
|
||||||
|
agentMeta: { sessionId: "session-subagent", provider: "openai", model: "gpt-5.2" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await agentCommand(
|
||||||
|
{
|
||||||
|
message: "hi",
|
||||||
|
sessionKey: "agent:main:subagent:test",
|
||||||
|
},
|
||||||
|
runtime,
|
||||||
|
);
|
||||||
|
|
||||||
|
const attempts = vi
|
||||||
|
.mocked(runEmbeddedPiAgent)
|
||||||
|
.mock.calls.map((call) => ({ provider: call[0]?.provider, model: call[0]?.model }));
|
||||||
|
expect(attempts).toEqual([
|
||||||
|
{ provider: "anthropic", model: "claude-opus-4-5" },
|
||||||
|
{ provider: "openai", model: "gpt-5.2" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps explicit sessionKey even when sessionId exists elsewhere", async () => {
|
it("keeps explicit sessionKey even when sessionId exists elsewhere", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const store = path.join(home, "sessions.json");
|
const store = path.join(home, "sessions.json");
|
||||||
|
|||||||
@@ -396,13 +396,17 @@ export async function agentCommand(
|
|||||||
opts.replyChannel ?? opts.channel,
|
opts.replyChannel ?? opts.channel,
|
||||||
);
|
);
|
||||||
const spawnedBy = opts.spawnedBy ?? sessionEntry?.spawnedBy;
|
const spawnedBy = opts.spawnedBy ?? sessionEntry?.spawnedBy;
|
||||||
// When a session has an explicit model override, prevent the fallback logic
|
// When a session has an explicit model override, keep the candidate chain
|
||||||
// from silently appending the global primary model as a backstop. Passing an
|
// anchored to that override (no implicit configured-primary append), while
|
||||||
// empty array (instead of undefined) tells resolveFallbackCandidates to skip
|
// still preserving configured fallback lists unless the agent explicitly
|
||||||
// the implicit primary append, so the session stays on its overridden model.
|
// overrides fallbacks with its own list (including an empty list to disable).
|
||||||
const agentFallbacksOverride = resolveAgentModelFallbacksOverride(cfg, sessionAgentId);
|
const agentFallbacksOverride = resolveAgentModelFallbacksOverride(cfg, sessionAgentId);
|
||||||
|
const defaultFallbacks =
|
||||||
|
typeof cfg.agents?.defaults?.model === "object"
|
||||||
|
? (cfg.agents.defaults.model.fallbacks ?? [])
|
||||||
|
: [];
|
||||||
const effectiveFallbacksOverride = storedModelOverride
|
const effectiveFallbacksOverride = storedModelOverride
|
||||||
? (agentFallbacksOverride ?? [])
|
? (agentFallbacksOverride ?? defaultFallbacks)
|
||||||
: agentFallbacksOverride;
|
: agentFallbacksOverride;
|
||||||
|
|
||||||
// Track model fallback attempts so retries on an existing session don't
|
// Track model fallback attempts so retries on an existing session don't
|
||||||
|
|||||||
Reference in New Issue
Block a user