fix(delegate-task): make description optional with auto-generation from prompt (#3162)

When weaker models (GLM-5, MiniMax) omit the description parameter on
delegate_task, the tool now auto-generates it from the first 4 words of
the prompt instead of throwing an error.

Changes:
- Schema: description is now optional (tool.schema.string().optional())
- Runtime: auto-generates from prompt when missing/empty/whitespace
- DelegateTaskArgs.description type stays as string (guaranteed by auto-gen)
- Tests: 3 new cases - missing/empty/explicit description handling
- Metadata title set after description resolution (correct ordering)
This commit is contained in:
YeonGyu-Kim
2026-04-07 09:30:22 +09:00
parent 3e8fd5ff18
commit bd37e6676a
2 changed files with 115 additions and 15 deletions
+109 -10
View File
@@ -1366,9 +1366,10 @@ describe("sisyphus-task", () => {
)).rejects.toThrow("Invalid arguments: 'run_in_background' parameter is REQUIRED")
})
test("#given category without description #when executing #then throws required parameter error", async () => {
test("#given category without description #when executing #then auto-generates description from prompt", async () => {
// given
const { createDelegateTask } = require("./tools")
let capturedTitle: string | undefined
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [] }) },
@@ -1383,16 +1384,114 @@ describe("sisyphus-task", () => {
const tool = createDelegateTask({ manager: mockManager, client: mockClient })
// when
// then
await expect(tool.execute(
{
prompt: "Do something",
category: "quick",
run_in_background: false,
load_skills: [],
try {
await tool.execute(
{
prompt: "Fix the broken unit tests in parser module",
category: "quick",
run_in_background: false,
load_skills: [],
},
{
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
metadata: async (meta: { title?: string }) => { capturedTitle = meta.title },
}
)
} catch {
// execution may fail due to incomplete mocks — we only care about the title
}
// then — description auto-generated from first 4 words of prompt
expect(capturedTitle).toBe("Fix the broken unit")
})
test("#given empty description #when executing #then auto-generates description from prompt", async () => {
// given
const { createDelegateTask } = require("./tools")
let capturedTitle: string | undefined
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: [] }),
},
{ sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal }
)).rejects.toThrow("Invalid arguments: 'description' parameter is REQUIRED")
}
const tool = createDelegateTask({ manager: mockManager, client: mockClient })
// when
try {
await tool.execute(
{
description: " ",
prompt: "Refactor authentication module completely",
category: "quick",
run_in_background: false,
load_skills: [],
},
{
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
metadata: async (meta: { title?: string }) => { capturedTitle = meta.title },
}
)
} catch {
// execution may fail due to incomplete mocks
}
// then
expect(capturedTitle).toBe("Refactor authentication module completely")
})
test("#given explicit description #when executing #then preserves provided description", async () => {
// given
const { createDelegateTask } = require("./tools")
let capturedTitle: string | undefined
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
try {
await tool.execute(
{
description: "My custom task name",
prompt: "Do something else entirely",
category: "quick",
run_in_background: false,
load_skills: [],
},
{
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
metadata: async (meta: { title?: string }) => { capturedTitle = meta.title },
}
)
} catch {
// execution may fail due to incomplete mocks
}
// then — explicit description preserved
expect(capturedTitle).toBe("My custom task name")
})
test("#given explicit run_in_background=false #when executing #then sync execution succeeds", async () => {
+6 -5
View File
@@ -98,7 +98,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
description,
args: {
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)"),
description: tool.schema.string().optional().describe("Short task description (3-5 words). Auto-generated from prompt if omitted."),
prompt: tool.schema.string().describe("Full detailed prompt for the agent"),
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.`),
@@ -118,13 +118,14 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
}
args.subagent_type = SISYPHUS_JUNIOR_AGENT
}
// Auto-generate description from prompt when missing or empty
if (!args.description || typeof args.description !== "string" || args.description.trim() === "") {
const words = (args.prompt || "").trim().split(/\s+/)
args.description = words.slice(0, 4).join(" ") || "Delegated task"
}
await ctx.metadata?.({
title: args.description,
})
if (!args.description || typeof args.description !== "string") {
throw new Error(`Invalid arguments: 'description' parameter is REQUIRED. Provide a short (3-5 words) task description.`)
}
if (args.run_in_background === undefined) {
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.`)
}