From c905e1cb7ac9aa20d8656635da85fbfcf3d4cd80 Mon Sep 17 00:00:00 2001 From: justsisyphus Date: Fri, 30 Jan 2026 11:45:19 +0900 Subject: [PATCH] fix(delegate-task): restore resolved.model to category userModel chain (#1227) PR #1227 incorrectly removed resolved.model from the userModel chain, assuming it was bypassing the fallback chain. However, resolved.model contained the category's DEFAULT_CATEGORIES model (e.g., quick -> claude-haiku-4-5), not the main session model. Without resolved.model, when connectedProvidersCache is null and availableModels is empty, category model resolution falls through to systemDefaultModel (opus) instead of using the category's default. This fix restores the original priority: 1. User category model override 2. Category default model (from resolved.model) 3. sisyphusJuniorModel 4. Fallback chain 5. System default --- src/tools/delegate-task/tools.test.ts | 67 +++++++++++++++++++++++++++ src/tools/delegate-task/tools.ts | 2 +- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/tools/delegate-task/tools.test.ts b/src/tools/delegate-task/tools.test.ts index bd74303f..c807954e 100644 --- a/src/tools/delegate-task/tools.test.ts +++ b/src/tools/delegate-task/tools.test.ts @@ -1474,6 +1474,73 @@ describe("sisyphus-task", () => { }, { timeout: 20000 }) }) + describe("category model resolution fallback", () => { + test("category uses resolved.model when connectedProvidersCache is null and availableModels is empty", async () => { + // #given - connectedProvidersCache returns null (simulates missing cache file) + // This is a regression test for PR #1227 which removed resolved.model from userModel chain + cacheSpy.mockReturnValue(null) + + const { createDelegateTask } = require("./tools") + let launchInput: any + + const mockManager = { + launch: async (input: any) => { + launchInput = input + return { + id: "task-fallback", + sessionID: "ses_fallback_test", + description: "Fallback test task", + agent: "sisyphus-junior", + status: "running", + } + }, + } + + const mockClient = { + app: { agents: async () => ({ data: [] }) }, + config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, + model: { list: async () => [] }, + session: { + create: async () => ({ data: { id: "test-session" } }), + prompt: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + }, + } + + // NO userCategories override, NO sisyphusJuniorModel + const tool = createDelegateTask({ + manager: mockManager, + client: mockClient, + // userCategories: undefined - use DEFAULT_CATEGORIES only + // sisyphusJuniorModel: undefined + }) + + const toolContext = { + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + abort: new AbortController().signal, + } + + // #when - using "quick" category which should use "anthropic/claude-haiku-4-5" + await tool.execute( + { + description: "Test category fallback", + prompt: "Do something quick", + category: "quick", + run_in_background: true, + load_skills: [], + }, + toolContext + ) + + // #then - model should be anthropic/claude-haiku-4-5 from DEFAULT_CATEGORIES + // NOT anthropic/claude-sonnet-4-5 (system default) + expect(launchInput.model.providerID).toBe("anthropic") + expect(launchInput.model.modelID).toBe("claude-haiku-4-5") + }) + }) + describe("browserProvider propagation", () => { test("should resolve agent-browser skill when browserProvider is passed", async () => { // #given - delegate_task configured with browserProvider: "agent-browser" diff --git a/src/tools/delegate-task/tools.ts b/src/tools/delegate-task/tools.ts index 5e96f605..6d152daa 100644 --- a/src/tools/delegate-task/tools.ts +++ b/src/tools/delegate-task/tools.ts @@ -541,7 +541,7 @@ To continue this session: session_id="${args.session_id}"` } } else { const resolution = resolveModelWithFallback({ - userModel: userCategories?.[args.category]?.model ?? sisyphusJuniorModel, + userModel: userCategories?.[args.category]?.model ?? resolved.model ?? sisyphusJuniorModel, fallbackChain: requirement.fallbackChain, availableModels, systemDefaultModel,