refactor: sync delegate_task schema with OpenCode Task tool (resume→session_id, add command param)
This commit is contained in:
@@ -594,16 +594,16 @@ describe("sisyphus-task", () => {
|
||||
}, { timeout: 20000 })
|
||||
})
|
||||
|
||||
describe("resume with background parameter", () => {
|
||||
test("resume with background=false should wait for result and return content", async () => {
|
||||
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
|
||||
// #given
|
||||
const { createDelegateTask } = require("./tools")
|
||||
|
||||
const mockTask = {
|
||||
id: "task-123",
|
||||
sessionID: "ses_resume_test",
|
||||
description: "Resumed task",
|
||||
sessionID: "ses_continue_test",
|
||||
description: "Continued task",
|
||||
agent: "explore",
|
||||
status: "running",
|
||||
}
|
||||
@@ -620,7 +620,7 @@ describe("sisyphus-task", () => {
|
||||
data: [
|
||||
{
|
||||
info: { role: "assistant", time: { created: Date.now() } },
|
||||
parts: [{ type: "text", text: "This is the resumed task result" }],
|
||||
parts: [{ type: "text", text: "This is the continued task result" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -646,28 +646,28 @@ describe("sisyphus-task", () => {
|
||||
// #when
|
||||
const result = await tool.execute(
|
||||
{
|
||||
description: "Resume test",
|
||||
description: "Continue test",
|
||||
prompt: "Continue the task",
|
||||
resume: "ses_resume_test",
|
||||
session_id: "ses_continue_test",
|
||||
run_in_background: false,
|
||||
load_skills: ["git-master"],
|
||||
},
|
||||
toolContext
|
||||
)
|
||||
|
||||
// #then - should contain actual result, not just "Background task resumed"
|
||||
expect(result).toContain("This is the resumed task result")
|
||||
expect(result).not.toContain("Background task resumed")
|
||||
// #then - should contain actual result, not just "Background task continued"
|
||||
expect(result).toContain("This is the continued task result")
|
||||
expect(result).not.toContain("Background task continued")
|
||||
}, { timeout: 10000 })
|
||||
|
||||
test("resume with background=true should return immediately without waiting", async () => {
|
||||
test("session_id with background=true should return immediately without waiting", async () => {
|
||||
// #given
|
||||
const { createDelegateTask } = require("./tools")
|
||||
|
||||
const mockTask = {
|
||||
id: "task-456",
|
||||
sessionID: "ses_bg_resume",
|
||||
description: "Background resumed task",
|
||||
sessionID: "ses_bg_continue",
|
||||
description: "Background continued task",
|
||||
agent: "explore",
|
||||
status: "running",
|
||||
}
|
||||
@@ -701,9 +701,9 @@ describe("sisyphus-task", () => {
|
||||
// #when
|
||||
const result = await tool.execute(
|
||||
{
|
||||
description: "Resume bg test",
|
||||
description: "Continue bg test",
|
||||
prompt: "Continue in background",
|
||||
resume: "ses_bg_resume",
|
||||
session_id: "ses_bg_continue",
|
||||
run_in_background: true,
|
||||
load_skills: ["git-master"],
|
||||
},
|
||||
@@ -711,7 +711,7 @@ describe("sisyphus-task", () => {
|
||||
)
|
||||
|
||||
// #then - should return background message
|
||||
expect(result).toContain("Background task resumed")
|
||||
expect(result).toContain("Background task continued")
|
||||
expect(result).toContain("task-456")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -86,8 +86,8 @@ function formatDetailedError(error: unknown, ctx: ErrorContext): string {
|
||||
lines.push(`- subagent_type: ${ctx.args.subagent_type ?? "(none)"}`)
|
||||
lines.push(`- run_in_background: ${ctx.args.run_in_background}`)
|
||||
lines.push(`- load_skills: [${ctx.args.load_skills?.join(", ") ?? ""}]`)
|
||||
if (ctx.args.resume) {
|
||||
lines.push(`- resume: ${ctx.args.resume}`)
|
||||
if (ctx.args.session_id) {
|
||||
lines.push(`- session_id: ${ctx.args.session_id}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
|
||||
|
||||
const description = `Spawn agent task with category-based or direct agent selection.
|
||||
|
||||
MUTUALLY EXCLUSIVE: Provide EITHER category OR subagent_type, not both (unless resuming).
|
||||
MUTUALLY EXCLUSIVE: Provide EITHER category OR subagent_type, not both (unless continuing a session).
|
||||
|
||||
- load_skills: ALWAYS REQUIRED. Pass at least one skill name (e.g., ["playwright"], ["git-master", "frontend-ui-ux"]).
|
||||
- category: Use predefined category → Spawns Sisyphus-Junior with category config
|
||||
@@ -202,12 +202,13 @@ MUTUALLY EXCLUSIVE: Provide EITHER category OR subagent_type, not both (unless r
|
||||
${categoryList}
|
||||
- subagent_type: Use specific agent directly (e.g., "oracle", "explore")
|
||||
- run_in_background: true=async (returns task_id), false=sync (waits for result). Default: false. Use background=true ONLY for parallel exploration with 5+ independent queries.
|
||||
- resume: Session ID to resume (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity.
|
||||
- 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).
|
||||
|
||||
**WHEN TO USE resume:**
|
||||
- Task failed/incomplete → resume with "fix: [specific issue]"
|
||||
- Need follow-up on previous result → resume with additional question
|
||||
- Multi-turn conversation with same agent → always resume instead of new task
|
||||
**WHEN TO USE session_id:**
|
||||
- Task failed/incomplete → session_id with "fix: [specific issue]"
|
||||
- Need follow-up on previous result → session_id with additional question
|
||||
- Multi-turn conversation with same agent → always session_id instead of new task
|
||||
|
||||
Prompts MUST be in English.`
|
||||
|
||||
@@ -220,7 +221,8 @@ Prompts MUST be in English.`
|
||||
run_in_background: tool.schema.boolean().describe("true=async (returns task_id), false=sync (waits). Default: false"),
|
||||
category: tool.schema.string().optional().describe(`Category (e.g., ${categoryExamples}). Mutually exclusive with subagent_type.`),
|
||||
subagent_type: tool.schema.string().optional().describe("Agent name (e.g., 'oracle', 'explore'). Mutually exclusive with category."),
|
||||
resume: tool.schema.string().optional().describe("Session ID to resume"),
|
||||
session_id: tool.schema.string().optional().describe("Existing Task session to continue"),
|
||||
command: tool.schema.string().optional().describe("The command that triggered this task"),
|
||||
},
|
||||
async execute(args: DelegateTaskArgs, toolContext) {
|
||||
const ctx = toolContext as ToolContextWithMetadata
|
||||
@@ -265,11 +267,11 @@ Prompts MUST be in English.`
|
||||
? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID }
|
||||
: undefined
|
||||
|
||||
if (args.resume) {
|
||||
if (args.session_id) {
|
||||
if (runInBackground) {
|
||||
try {
|
||||
const task = await manager.resume({
|
||||
sessionId: args.resume,
|
||||
sessionId: args.session_id,
|
||||
prompt: args.prompt,
|
||||
parentSessionID: ctx.sessionID,
|
||||
parentMessageID: ctx.messageID,
|
||||
@@ -278,7 +280,7 @@ Prompts MUST be in English.`
|
||||
})
|
||||
|
||||
ctx.metadata?.({
|
||||
title: `Resume: ${task.description}`,
|
||||
title: `Continue: ${task.description}`,
|
||||
metadata: {
|
||||
prompt: args.prompt,
|
||||
agent: task.agent,
|
||||
@@ -286,10 +288,11 @@ Prompts MUST be in English.`
|
||||
description: args.description,
|
||||
run_in_background: args.run_in_background,
|
||||
sessionId: task.sessionID,
|
||||
command: args.command,
|
||||
},
|
||||
})
|
||||
|
||||
return `Background task resumed.
|
||||
return `Background task continued.
|
||||
|
||||
Task ID: ${task.id}
|
||||
Session ID: ${task.sessionID}
|
||||
@@ -301,35 +304,36 @@ Agent continues with full previous context preserved.
|
||||
Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
} catch (error) {
|
||||
return formatDetailedError(error, {
|
||||
operation: "Resume background task",
|
||||
operation: "Continue background task",
|
||||
args,
|
||||
sessionID: args.resume,
|
||||
sessionID: args.session_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const toastManager = getTaskToastManager()
|
||||
const taskId = `resume_sync_${args.resume.slice(0, 8)}`
|
||||
const taskId = `resume_sync_${args.session_id.slice(0, 8)}`
|
||||
const startTime = new Date()
|
||||
|
||||
if (toastManager) {
|
||||
toastManager.addTask({
|
||||
id: taskId,
|
||||
description: args.description,
|
||||
agent: "resume",
|
||||
agent: "continue",
|
||||
isBackground: false,
|
||||
})
|
||||
}
|
||||
|
||||
ctx.metadata?.({
|
||||
title: `Resume: ${args.description}`,
|
||||
title: `Continue: ${args.description}`,
|
||||
metadata: {
|
||||
prompt: args.prompt,
|
||||
load_skills: args.load_skills,
|
||||
description: args.description,
|
||||
run_in_background: args.run_in_background,
|
||||
sessionId: args.resume,
|
||||
sessionId: args.session_id,
|
||||
sync: true,
|
||||
command: args.command,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -338,7 +342,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
let resumeModel: { providerID: string; modelID: string } | undefined
|
||||
|
||||
try {
|
||||
const messagesResp = await client.session.messages({ path: { id: args.resume } })
|
||||
const messagesResp = await client.session.messages({ path: { id: args.session_id } })
|
||||
const messages = (messagesResp.data ?? []) as Array<{
|
||||
info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string }
|
||||
}>
|
||||
@@ -351,7 +355,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
const resumeMessageDir = getMessageDir(args.resume)
|
||||
const resumeMessageDir = getMessageDir(args.session_id)
|
||||
const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null
|
||||
resumeAgent = resumeMessage?.agent
|
||||
resumeModel = resumeMessage?.model?.providerID && resumeMessage?.model?.modelID
|
||||
@@ -360,7 +364,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
}
|
||||
|
||||
await client.session.prompt({
|
||||
path: { id: args.resume },
|
||||
path: { id: args.session_id },
|
||||
body: {
|
||||
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
||||
...(resumeModel !== undefined ? { model: resumeModel } : {}),
|
||||
@@ -378,7 +382,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
toastManager.removeTask(taskId)
|
||||
}
|
||||
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError)
|
||||
return `Failed to send resume prompt: ${errorMessage}\n\nSession ID: ${args.resume}`
|
||||
return `Failed to send continuation prompt: ${errorMessage}\n\nSession ID: ${args.session_id}`
|
||||
}
|
||||
|
||||
// Wait for message stability after prompt completes
|
||||
@@ -395,7 +399,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
const elapsed = Date.now() - pollStart
|
||||
if (elapsed < MIN_STABILITY_TIME_MS) continue
|
||||
|
||||
const messagesCheck = await client.session.messages({ path: { id: args.resume } })
|
||||
const messagesCheck = await client.session.messages({ path: { id: args.session_id } })
|
||||
const msgs = ((messagesCheck as { data?: unknown }).data ?? messagesCheck) as Array<unknown>
|
||||
const currentMsgCount = msgs.length
|
||||
|
||||
@@ -409,14 +413,14 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
}
|
||||
|
||||
const messagesResult = await client.session.messages({
|
||||
path: { id: args.resume },
|
||||
path: { id: args.session_id },
|
||||
})
|
||||
|
||||
if (messagesResult.error) {
|
||||
if (toastManager) {
|
||||
toastManager.removeTask(taskId)
|
||||
}
|
||||
return `Error fetching result: ${messagesResult.error}\n\nSession ID: ${args.resume}`
|
||||
return `Error fetching result: ${messagesResult.error}\n\nSession ID: ${args.session_id}`
|
||||
}
|
||||
|
||||
const messages = ((messagesResult as { data?: unknown }).data ?? messagesResult) as Array<{
|
||||
@@ -434,7 +438,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
}
|
||||
|
||||
if (!lastMessage) {
|
||||
return `No assistant response found.\n\nSession ID: ${args.resume}`
|
||||
return `No assistant response found.\n\nSession ID: ${args.session_id}`
|
||||
}
|
||||
|
||||
// Extract text from both "text" and "reasoning" parts (thinking models use "reasoning")
|
||||
@@ -443,16 +447,16 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
|
||||
const duration = formatDuration(startTime)
|
||||
|
||||
return `Task resumed and completed in ${duration}.
|
||||
return `Task continued and completed in ${duration}.
|
||||
|
||||
Session ID: ${args.resume}
|
||||
Session ID: ${args.session_id}
|
||||
|
||||
---
|
||||
|
||||
${textContent || "(No text output)"}
|
||||
|
||||
---
|
||||
To resume this session: resume="${args.resume}"`
|
||||
To continue this session: session_id="${args.session_id}"`
|
||||
}
|
||||
|
||||
if (args.category && args.subagent_type) {
|
||||
@@ -618,6 +622,7 @@ To resume this session: resume="${args.resume}"`
|
||||
description: args.description,
|
||||
run_in_background: args.run_in_background,
|
||||
sessionId: sessionID,
|
||||
command: args.command,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -705,7 +710,7 @@ RESULT:
|
||||
${textContent || "(No text output)"}
|
||||
|
||||
---
|
||||
To resume this session: resume="${sessionID}"`
|
||||
To continue this session: session_id="${sessionID}"`
|
||||
} catch (error) {
|
||||
return formatDetailedError(error, {
|
||||
operation: "Launch monitored background task",
|
||||
@@ -788,6 +793,7 @@ Sisyphus-Junior is spawned automatically when you specify a category. Pick the a
|
||||
description: args.description,
|
||||
run_in_background: args.run_in_background,
|
||||
sessionId: task.sessionID,
|
||||
command: args.command,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -800,7 +806,7 @@ Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
|
||||
Status: ${task.status}
|
||||
|
||||
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.
|
||||
To resume this session: resume="${task.sessionID}"`
|
||||
To continue this session: session_id="${task.sessionID}"`
|
||||
} catch (error) {
|
||||
return formatDetailedError(error, {
|
||||
operation: "Launch background task",
|
||||
@@ -864,6 +870,7 @@ To resume this session: resume="${task.sessionID}"`
|
||||
run_in_background: args.run_in_background,
|
||||
sessionId: sessionID,
|
||||
sync: true,
|
||||
command: args.command,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1018,7 +1025,7 @@ Session ID: ${sessionID}
|
||||
${textContent || "(No text output)"}
|
||||
|
||||
---
|
||||
To resume this session: resume="${sessionID}"`
|
||||
To continue this session: session_id="${sessionID}"`
|
||||
} catch (error) {
|
||||
if (toastManager && taskId !== undefined) {
|
||||
toastManager.removeTask(taskId)
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface DelegateTaskArgs {
|
||||
category?: string
|
||||
subagent_type?: string
|
||||
run_in_background: boolean
|
||||
resume?: string
|
||||
session_id?: string
|
||||
command?: string
|
||||
load_skills: string[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user