diff --git a/src/tools/delegate-task/tools.test.ts b/src/tools/delegate-task/tools.test.ts index 594735834..550e87b78 100644 --- a/src/tools/delegate-task/tools.test.ts +++ b/src/tools/delegate-task/tools.test.ts @@ -1258,6 +1258,211 @@ describe("sisyphus-task", () => { }, { timeout: 20000 }) }) + describe("run_in_background parameter", () => { + test("#given category without run_in_background #when executing #then throws required parameter error", async () => { + // given + const { createDelegateTask } = require("./tools") + const mockManager = { launch: async () => ({}) } + const mockClient = { + app: { agents: async () => ({ data: [] }) }, + config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, + session: { + create: async () => ({ data: { id: "test-session" } }), + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + }, + } + const tool = createDelegateTask({ manager: mockManager, client: mockClient }) + + // when + // then + await expect(tool.execute( + { + description: "Category without run flag", + prompt: "Do something", + category: "quick", + load_skills: [], + }, + { sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal } + )).rejects.toThrow("Invalid arguments: 'run_in_background' parameter is REQUIRED") + }) + + test("#given subagent_type without run_in_background #when executing #then throws required parameter error", async () => { + // given + const { createDelegateTask } = require("./tools") + const mockManager = { launch: async () => ({}) } + const mockClient = { + app: { agents: async () => ({ data: [{ name: "explore", mode: "subagent" }] }) }, + config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, + session: { + create: async () => ({ data: { id: "test-session" } }), + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + }, + } + const tool = createDelegateTask({ manager: mockManager, client: mockClient }) + + // when + // then + await expect(tool.execute( + { + description: "Subagent without run flag", + prompt: "Find patterns", + subagent_type: "explore", + load_skills: [], + }, + { sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal } + )).rejects.toThrow("Invalid arguments: 'run_in_background' parameter is REQUIRED") + }) + + test("#given session_id without run_in_background #when executing #then throws required parameter error", async () => { + // given + const { createDelegateTask } = require("./tools") + const mockManager = { resume: async () => ({ id: "task-1", sessionID: "ses_1", status: "running" }) } + const mockClient = { + app: { agents: async () => ({ data: [] }) }, + config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, + session: { + create: async () => ({ data: { id: "test-session" } }), + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + }, + } + const tool = createDelegateTask({ manager: mockManager, client: mockClient }) + + // when + // then + await expect(tool.execute( + { + description: "Continue without run flag", + prompt: "Continue", + session_id: "ses_existing", + load_skills: [], + }, + { sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal } + )).rejects.toThrow("Invalid arguments: 'run_in_background' parameter is REQUIRED") + }) + + test("#given no category no subagent_type no session_id and no run_in_background #when executing #then throws required parameter error", async () => { + // given + const { createDelegateTask } = require("./tools") + const mockManager = { launch: async () => ({}) } + const mockClient = { + app: { agents: async () => ({ data: [] }) }, + config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, + session: { + create: async () => ({ data: { id: "test-session" } }), + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + }, + } + const tool = createDelegateTask({ manager: mockManager, client: mockClient }) + + // when + // then + await expect(tool.execute( + { + description: "Missing required args", + prompt: "Do something", + load_skills: [], + }, + { sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal } + )).rejects.toThrow("Invalid arguments: 'run_in_background' parameter is REQUIRED") + }) + + test("#given explicit run_in_background=false #when executing #then sync execution succeeds", async () => { + // given + const { createDelegateTask } = require("./tools") + let promptCalled = false + const mockManager = { launch: async () => ({}) } + const mockClient = { + app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-6" } }] }) }, + config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, + session: { + get: async () => ({ data: { directory: "/project" } }), + create: async () => ({ data: { id: "ses_explicit_false" } }), + prompt: async () => { + promptCalled = true + return { data: {} } + }, + promptAsync: async () => { + promptCalled = true + return { data: {} } + }, + messages: async () => ({ data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }] }), + status: async () => ({ data: { ses_explicit_false: { type: "idle" } } }), + }, + } + const tool = createDelegateTask({ manager: mockManager, client: mockClient }) + + // when + const result = await tool.execute( + { + description: "Explicit false", + prompt: "Run sync", + subagent_type: "oracle", + run_in_background: false, + load_skills: [], + }, + { sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal } + ) + + // then + expect(promptCalled).toBe(true) + expect(result).toContain("Done") + }, { timeout: 10000 }) + + test("#given explicit run_in_background=true #when executing #then background execution succeeds", async () => { + // given + const { createDelegateTask } = require("./tools") + let launchCalled = false + const mockManager = { + launch: async () => { + launchCalled = true + return { + id: "bg_explicit_true", + sessionID: "ses_bg_explicit_true", + description: "Explicit true", + 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: "ses_bg_explicit_true" } }), + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + }, + } + const tool = createDelegateTask({ manager: mockManager, client: mockClient }) + + // when + const result = await tool.execute( + { + description: "Explicit true", + prompt: "Run background", + category: "quick", + run_in_background: true, + load_skills: [], + }, + { sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal } + ) + + // then + expect(launchCalled).toBe(true) + expect(result).toContain("Background task launched") + }, { timeout: 10000 }) + }) + describe("session_id with background parameter", () => { test("session_id with background=false should wait for result and return content", async () => { // Note: This test needs extended timeout because the implementation has MIN_STABILITY_TIME_MS = 5000 diff --git a/src/tools/delegate-task/tools.ts b/src/tools/delegate-task/tools.ts index 7d9b54bc0..2ab27abba 100644 --- a/src/tools/delegate-task/tools.ts +++ b/src/tools/delegate-task/tools.ts @@ -83,7 +83,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini Available categories: ${categoryList} - subagent_type: Use specific agent directly (explore, librarian, oracle, metis, momus) - - run_in_background: true=async (returns task_id), false=sync (waits). Default: false. Use background=true ONLY for parallel exploration with 5+ independent queries. + - run_in_background: REQUIRED. true=async (returns task_id), false=sync (waits). Use background=true ONLY for parallel exploration with 5+ independent queries. - session_id: Existing Task session to continue (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity. - command: The command that triggered this task (optional, for slash command tracking). @@ -100,7 +100,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini load_skills: tool.schema.array(tool.schema.string()).describe("Skill names to inject. REQUIRED - pass [] if no skills needed."), description: tool.schema.string().describe("Short task description (3-5 words)"), prompt: tool.schema.string().describe("Full detailed prompt for the agent"), - run_in_background: tool.schema.boolean().describe("true=async (returns task_id), false=sync (waits). Default: false"), + run_in_background: tool.schema.boolean().describe("REQUIRED. true=async (returns task_id), false=sync (waits). Use false for task delegation, true ONLY for parallel exploration."), category: tool.schema.string().optional().describe(`REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type.`), subagent_type: tool.schema.string().optional().describe("REQUIRED if category not provided. Do NOT provide both category and subagent_type."), session_id: tool.schema.string().optional().describe("Existing Task session to continue"), @@ -123,11 +123,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini }) if (args.run_in_background === undefined) { - if (args.category || args.subagent_type || args.session_id) { - args.run_in_background = false - } else { - throw new Error(`Invalid arguments: 'run_in_background' parameter is REQUIRED. Use run_in_background=false for task delegation, run_in_background=true only for parallel exploration.`) - } + throw new Error(`Invalid arguments: 'run_in_background' parameter is REQUIRED. Specify run_in_background=false for task delegation, or run_in_background=true for parallel exploration.`) } if (typeof args.load_skills === "string") { try {