From d89e257d8ab4451e045ccf0f60bb27a03b70247e Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 16 Apr 2026 23:13:44 +0900 Subject: [PATCH] refactor(task): align continuation ids with task_id --- src/agents/atlas/default-prompt-sections.ts | 12 +++---- src/agents/atlas/gemini-prompt-sections.ts | 4 +-- src/agents/atlas/gpt-prompt-sections.ts | 4 +-- src/agents/dynamic-agent-core-sections.ts | 2 +- src/agents/hephaestus/gpt-5-3-codex.ts | 6 ++-- src/agents/hephaestus/gpt-5-4.ts | 8 ++--- src/agents/hephaestus/gpt.ts | 8 ++--- src/agents/sisyphus.ts | 16 ++++----- src/agents/sisyphus/default.ts | 16 ++++----- src/agents/sisyphus/gpt-5-4.ts | 8 ++--- .../task-metadata-contract.test.ts | 4 +-- src/hooks/atlas/verification-reminders.ts | 4 +-- .../keyword-detector/ultrawork/default.ts | 12 +++---- .../keyword-detector/ultrawork/gemini.ts | 8 ++--- src/hooks/task-resume-info/hook.ts | 7 ++-- src/hooks/task-resume-info/index.test.ts | 4 ++- src/plugin/tool-execute-before.test.ts | 17 ++++++++++ src/plugin/tool-execute-before.ts | 15 +++++++-- .../create-background-cancel.ts | 2 +- .../delegate-task/background-continuation.ts | 22 ++++++++++--- .../delegate-task/background-task.test.ts | 9 +++-- src/tools/delegate-task/background-task.ts | 11 ++++++- src/tools/delegate-task/error-formatting.ts | 6 ++-- src/tools/delegate-task/sync-continuation.ts | 33 ++++++++++++------- src/tools/delegate-task/sync-task.ts | 11 +++++-- src/tools/delegate-task/task-id.ts | 5 +++ src/tools/delegate-task/task-schema.test.ts | 1 + src/tools/delegate-task/tools.ts | 19 +++++++---- src/tools/delegate-task/types.ts | 2 ++ 29 files changed, 180 insertions(+), 96 deletions(-) create mode 100644 src/tools/delegate-task/task-id.ts diff --git a/src/agents/atlas/default-prompt-sections.ts b/src/agents/atlas/default-prompt-sections.ts index 46e2634f7..24ba9f807 100644 --- a/src/agents/atlas/default-prompt-sections.ts +++ b/src/agents/atlas/default-prompt-sections.ts @@ -150,16 +150,16 @@ task( ### 3.5 Handle Failures (USE RESUME) -**CRITICAL: When re-delegating, ALWAYS use \`session_id\` parameter.** +**CRITICAL: When re-delegating, ALWAYS use \`task_id\` parameter.** -Every \`task()\` output includes a session_id. STORE IT. +Every \`task()\` output includes a task_id. STORE IT. If task fails: 1. Identify what went wrong 2. **Resume the SAME session** - subagent has full context already: \`\`\`typescript task( - session_id="ses_xyz789", // Session from failed task + task_id="ses_xyz789", // Task ID from failed task load_skills=[...], prompt="FAILED: {error}. Fix by: {specific instruction}" ) @@ -167,7 +167,7 @@ If task fails: 3. Maximum 3 retry attempts with the SAME session 4. If blocked after 3 attempts: Document and continue to independent tasks -**Why session_id is MANDATORY for failures:** +**Why task_id is MANDATORY for failures:** - Subagent already read all files, knows the context - No repeated exploration = 70%+ token savings - Subagent knows what approaches already failed @@ -292,6 +292,6 @@ export const DEFAULT_ATLAS_CRITICAL_RULES = ` - Pass inherited wisdom to every subagent - Parallelize independent tasks - Verify with your own tools -- **Store session_id from every delegation output** -- **Use \`session_id="{session_id}"\` for retries, fixes, and follow-ups** +- **Store task_id from every delegation output** +- **Use \`task_id="{task_id}"\` for retries, fixes, and follow-ups** ` diff --git a/src/agents/atlas/gemini-prompt-sections.ts b/src/agents/atlas/gemini-prompt-sections.ts index 7a84e3d73..2ca4c2bc2 100644 --- a/src/agents/atlas/gemini-prompt-sections.ts +++ b/src/agents/atlas/gemini-prompt-sections.ts @@ -164,10 +164,10 @@ Count remaining **top-level task** checkboxes. Ignore nested verification/eviden ### 3.5 Handle Failures -**CRITICAL: Use \`session_id\` for retries.** +**CRITICAL: Use \`task_id\` for retries.** \`\`\`typescript -task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") +task(task_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") \`\`\` - Maximum 3 retries per task diff --git a/src/agents/atlas/gpt-prompt-sections.ts b/src/agents/atlas/gpt-prompt-sections.ts index 96977f777..1a9f39c26 100644 --- a/src/agents/atlas/gpt-prompt-sections.ts +++ b/src/agents/atlas/gpt-prompt-sections.ts @@ -169,10 +169,10 @@ Count remaining **top-level task** checkboxes. Ignore nested verification/eviden ### 3.5 Handle Failures -**CRITICAL: Use \`session_id\` for retries.** +**CRITICAL: Use \`task_id\` for retries.** \`\`\`typescript -task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") +task(task_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") \`\`\` - Maximum 3 retries per task diff --git a/src/agents/dynamic-agent-core-sections.ts b/src/agents/dynamic-agent-core-sections.ts index dc91fd480..416750a54 100644 --- a/src/agents/dynamic-agent-core-sections.ts +++ b/src/agents/dynamic-agent-core-sections.ts @@ -182,7 +182,7 @@ Multi-step task? **ALWAYS consult Plan Agent first.** Do NOT start implementatio - Single-file fix or trivial change → proceed directly - Anything else (2+ steps, unclear scope, architecture) → \`task(subagent_type="plan", ...)\` FIRST -- Use \`session_id\` to resume the same Plan Agent - ask follow-up questions aggressively +- Use \`task_id\` to resume the same Plan Agent - ask follow-up questions aggressively - If ANY part of the task is ambiguous, ask Plan Agent before guessing Plan Agent returns a structured work breakdown with parallel execution opportunities. Follow it.` diff --git a/src/agents/hephaestus/gpt-5-3-codex.ts b/src/agents/hephaestus/gpt-5-3-codex.ts index 2ca2964f7..28127bcc8 100644 --- a/src/agents/hephaestus/gpt-5-3-codex.ts +++ b/src/agents/hephaestus/gpt-5-3-codex.ts @@ -409,9 +409,9 @@ After delegation, ALWAYS verify: works as expected? follows codebase pattern? MU Every \`task()\` output includes a session_id. **USE IT for follow-ups.** -- **Task failed/incomplete** - \`session_id="{id}", prompt="Fix: {error}"\` -- **Follow-up on result** - \`session_id="{id}", prompt="Also: {question}"\` -- **Verification failed** - \`session_id="{id}", prompt="Failed: {error}. Fix."\` +- **Task failed/incomplete** - \`task_id="{id}", prompt="Fix: {error}"\` +- **Follow-up on result** - \`task_id="{id}", prompt="Also: {question}"\` +- **Verification failed** - \`task_id="{id}", prompt="Failed: {error}. Fix."\` ${ oracleSection diff --git a/src/agents/hephaestus/gpt-5-4.ts b/src/agents/hephaestus/gpt-5-4.ts index a88b6ea0f..eec4e18b4 100644 --- a/src/agents/hephaestus/gpt-5-4.ts +++ b/src/agents/hephaestus/gpt-5-4.ts @@ -312,10 +312,10 @@ Every delegation prompt needs these 6 sections: After delegation, verify by reading every file the subagent touched. Check: works as expected? follows codebase pattern? Do not trust self-reports. -Every \`task()\` returns a session_id. Use it for all follow-ups: -- Task failed/incomplete: \`session_id="{id}", prompt="Fix: {error}"\` -- Follow-up on result: \`session_id="{id}", prompt="Also: {question}"\` -- Verification failed: \`session_id="{id}", prompt="Failed: {error}. Fix."\` +Every \`task()\` returns a task_id. Use it for all follow-ups: +- Task failed/incomplete: \`task_id="{id}", prompt="Fix: {error}"\` +- Follow-up on result: \`task_id="{id}", prompt="Also: {question}"\` +- Verification failed: \`task_id="{id}", prompt="Failed: {error}. Fix."\` This preserves full context, avoids repeated exploration, saves 70%+ tokens. diff --git a/src/agents/hephaestus/gpt.ts b/src/agents/hephaestus/gpt.ts index cf1a3ea91..712bb9536 100644 --- a/src/agents/hephaestus/gpt.ts +++ b/src/agents/hephaestus/gpt.ts @@ -277,11 +277,11 @@ After delegation, ALWAYS verify: works as expected? follows codebase pattern? MU ### Session Continuity -Every \`task()\` output includes a session_id. **USE IT for follow-ups.** +Every \`task()\` output includes a task_id. **USE IT for follow-ups.** -- **Task failed/incomplete** - \`session_id="{id}", prompt="Fix: {error}"\` -- **Follow-up on result** - \`session_id="{id}", prompt="Also: {question}"\` -- **Verification failed** - \`session_id="{id}", prompt="Failed: {error}. Fix."\` +- **Task failed/incomplete** - \`task_id="{id}", prompt="Fix: {error}"\` +- **Follow-up on result** - \`task_id="{id}", prompt="Also: {question}"\` +- **Verification failed** - \`task_id="{id}", prompt="Failed: {error}. Fix."\` ${ oracleSection diff --git a/src/agents/sisyphus.ts b/src/agents/sisyphus.ts index 55b6c1c21..81a863d54 100644 --- a/src/agents/sisyphus.ts +++ b/src/agents/sisyphus.ts @@ -317,15 +317,15 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING: ### Session Continuity (MANDATORY) -Every \`task()\` output includes a session_id. **USE IT.** +Every \`task()\` output includes a task_id. **USE IT.** **ALWAYS continue when:** -- Task failed/incomplete → \`session_id=\"{session_id}\", prompt=\"Fix: {specific error}\"\` -- Follow-up question on result → \`session_id=\"{session_id}\", prompt=\"Also: {question}\"\` -- Multi-turn with same agent → \`session_id=\"{session_id}\"\` - NEVER start fresh -- Verification failed → \`session_id=\"{session_id}\", prompt=\"Failed verification: {error}. Fix.\"\` +- Task failed/incomplete → \`task_id=\"{task_id}\", prompt=\"Fix: {specific error}\"\` +- Follow-up question on result → \`task_id=\"{task_id}\", prompt=\"Also: {question}\"\` +- Multi-turn with same agent → \`task_id=\"{task_id}\"\` - NEVER start fresh +- Verification failed → \`task_id=\"{task_id}\", prompt=\"Failed verification: {error}. Fix.\"\` -**Why session_id is CRITICAL:** +**Why task_id is CRITICAL:** - Subagent has FULL conversation context preserved - No repeated file reads, exploration, or setup - Saves 70%+ tokens on follow-ups @@ -336,10 +336,10 @@ Every \`task()\` output includes a session_id. **USE IT.** task(category="quick", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix the type error in auth.ts...") // CORRECT: Resume preserves everything -task(session_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") +task(task_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") \`\`\` -**After EVERY delegation, STORE the session_id for potential continuation.** +**After EVERY delegation, STORE the task_id for potential continuation.** ### Code Changes: - Match existing patterns (if codebase is disciplined) diff --git a/src/agents/sisyphus/default.ts b/src/agents/sisyphus/default.ts index 14895b124..52e237f7c 100644 --- a/src/agents/sisyphus/default.ts +++ b/src/agents/sisyphus/default.ts @@ -389,15 +389,15 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING: ### Session Continuity (MANDATORY) -Every \`task()\` output includes a session_id. **USE IT.** +Every \`task()\` output includes a task_id. **USE IT.** **ALWAYS continue when:** -- Task failed/incomplete → \`session_id="{session_id}", prompt="Fix: {specific error}"\` -- Follow-up question on result → \`session_id="{session_id}", prompt="Also: {question}"\` -- Multi-turn with same agent → \`session_id="{session_id}"\` - NEVER start fresh -- Verification failed → \`session_id="{session_id}", prompt="Failed verification: {error}. Fix."\` +- Task failed/incomplete → \`task_id="{task_id}", prompt="Fix: {specific error}"\` +- Follow-up question on result → \`task_id="{task_id}", prompt="Also: {question}"\` +- Multi-turn with same agent → \`task_id="{task_id}"\` - NEVER start fresh +- Verification failed → \`task_id="{task_id}", prompt="Failed verification: {error}. Fix."\` -**Why session_id is CRITICAL:** +**Why task_id is CRITICAL:** - Subagent has FULL conversation context preserved - No repeated file reads, exploration, or setup - Saves 70%+ tokens on follow-ups @@ -408,10 +408,10 @@ Every \`task()\` output includes a session_id. **USE IT.** task(category="quick", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix the type error in auth.ts...") // CORRECT: Resume preserves everything -task(session_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") +task(task_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") \`\`\` -**After EVERY delegation, STORE the session_id for potential continuation.** +**After EVERY delegation, STORE the task_id for potential continuation.** ### Code Changes: - Match existing patterns (if codebase is disciplined) diff --git a/src/agents/sisyphus/gpt-5-4.ts b/src/agents/sisyphus/gpt-5-4.ts index 9e8219015..4667e3466 100644 --- a/src/agents/sisyphus/gpt-5-4.ts +++ b/src/agents/sisyphus/gpt-5-4.ts @@ -387,10 +387,10 @@ Post-delegation: delegation never substitutes for verification. Always run \` { // given const link = { sessionId: "ses_bg_123", - taskId: "bg_123", + taskId: "ses_bg_123", backgroundTaskId: "bg_123", agent: "explore", category: "quick", @@ -29,7 +29,7 @@ describe("buildTaskMetadataBlock", () => { // then expect(block).toBe( - "\nsession_id: ses_bg_123\ntask_id: bg_123\nbackground_task_id: bg_123\nsubagent: explore\ncategory: quick\n" + "\nsession_id: ses_bg_123\ntask_id: ses_bg_123\nbackground_task_id: bg_123\nsubagent: explore\ncategory: quick\n" ) }) }) diff --git a/src/hooks/atlas/verification-reminders.ts b/src/hooks/atlas/verification-reminders.ts index b00618ecd..9bde55b9d 100644 --- a/src/hooks/atlas/verification-reminders.ts +++ b/src/hooks/atlas/verification-reminders.ts @@ -29,7 +29,7 @@ Your completion will NOT be recorded until you complete ALL of the following: If anything fails while closing this out, resume the same session immediately: \`\`\`typescript -task(session_id="${sessionId}", load_skills=[], prompt="fix: checkbox not recorded correctly") +task(task_id="${sessionId}", load_skills=[], prompt="fix: checkbox not recorded correctly") \`\`\` **Your completion is NOT tracked until the checkbox is marked in the plan file.** @@ -47,7 +47,7 @@ ${VERIFICATION_REMINDER} **If ANY verification fails, use this immediately:** \`\`\` -task(session_id="${sessionId}", load_skills=[], prompt="fix: [describe the specific failure]") +task(task_id="${sessionId}", load_skills=[], prompt="fix: [describe the specific failure]") \`\`\` ${buildReuseHint(sessionId)}` diff --git a/src/hooks/keyword-detector/ultrawork/default.ts b/src/hooks/keyword-detector/ultrawork/default.ts index 37790d9ea..0c95c5f1e 100644 --- a/src/hooks/keyword-detector/ultrawork/default.ts +++ b/src/hooks/keyword-detector/ultrawork/default.ts @@ -115,15 +115,15 @@ task(subagent_type="plan", load_skills=[], run_in_background=false, prompt=" { await afterHook(input, output) expect(output.output).toContain("to continue:") + expect(output.output).toContain('task(task_id="ses_abc123"') expect(output.output).toContain("ses_abc123") }) @@ -74,6 +75,7 @@ describe("createTaskResumeInfoHook", () => { await afterHook(input, output) expect(output.output).toContain("run_in_background=false") + expect(output.output).toContain('task_id="ses_abc123"') }) }) }) @@ -120,7 +122,7 @@ describe("createTaskResumeInfoHook", () => { const output = { title: "task", output: - 'Done.\nSession ID: ses_abc123\nto continue: task(session_id="ses_abc123", load_skills=[], prompt="...")', + 'Done.\nSession ID: ses_abc123\nto continue: task(task_id="ses_abc123", load_skills=[], run_in_background=false, prompt="...")', metadata: {}, } diff --git a/src/plugin/tool-execute-before.test.ts b/src/plugin/tool-execute-before.test.ts index 80daa79a8..f1f3a30b5 100644 --- a/src/plugin/tool-execute-before.test.ts +++ b/src/plugin/tool-execute-before.test.ts @@ -161,6 +161,23 @@ describe("createToolExecuteBeforeHandler", () => { expect(output.args.subagent_type).toBe("explore") }) + test("normalizes task_id into the canonical resume argument", async () => { + //#given + const ctx = createCtxWithSessionMessages([ + { info: { role: "assistant", agent: "oracle" } }, + ]) + const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks }) + const input = { tool: "task", sessionID: "ses_123", callID: "call_1" } + const output = { args: { task_id: "ses_resume_123", description: "Continue task", prompt: "fix it" } as Record } + + //#when + await handler(input, output) + + //#then + expect(output.args.task_id).toBe("ses_resume_123") + expect(output.args.subagent_type).toBe("oracle") + }) + test("falls back to 'continue' when session has no agent info", async () => { //#given const ctx = createCtxWithSessionMessages([ diff --git a/src/plugin/tool-execute-before.ts b/src/plugin/tool-execute-before.ts index 68b1545ea..dcd47f222 100644 --- a/src/plugin/tool-execute-before.ts +++ b/src/plugin/tool-execute-before.ts @@ -100,12 +100,21 @@ export function createToolExecuteBeforeHandler(args: { const argsObject = output.args const category = typeof argsObject.category === "string" ? argsObject.category : undefined const subagentType = typeof argsObject.subagent_type === "string" ? argsObject.subagent_type : undefined - const sessionId = typeof argsObject.session_id === "string" ? argsObject.session_id : undefined + const taskId = + typeof argsObject.task_id === "string" + ? argsObject.task_id + : typeof argsObject.session_id === "string" + ? argsObject.session_id + : undefined + + if (taskId && typeof argsObject.task_id !== "string") { + argsObject.task_id = taskId + } if (category) { argsObject.subagent_type = "sisyphus-junior" - } else if (!subagentType && sessionId) { - const resolvedAgent = await resolveSessionAgent(ctx.client, sessionId) + } else if (!subagentType && taskId) { + const resolvedAgent = await resolveSessionAgent(ctx.client, taskId) argsObject.subagent_type = resolvedAgent ?? "continue" } diff --git a/src/tools/background-task/create-background-cancel.ts b/src/tools/background-task/create-background-cancel.ts index 49fc4e5bb..a61c280f8 100644 --- a/src/tools/background-task/create-background-cancel.ts +++ b/src/tools/background-task/create-background-cancel.ts @@ -59,7 +59,7 @@ export function createBackgroundCancel(manager: BackgroundManager, _client: Back To continue a cancelled task, use: \`\`\` -task(session_id="", prompt="Continue: ") +task(task_id="", prompt="Continue: ") \`\`\` Continuable sessions: diff --git a/src/tools/delegate-task/background-continuation.ts b/src/tools/delegate-task/background-continuation.ts index dd0850439..becf732b9 100644 --- a/src/tools/delegate-task/background-continuation.ts +++ b/src/tools/delegate-task/background-continuation.ts @@ -3,6 +3,8 @@ import type { ExecutorContext, ParentContext } from "./executor-types" import { publishToolMetadata } from "../../features/tool-metadata-store" import { formatDetailedError } from "./error-formatting" import { getSessionTools } from "../../shared/session-tools-store" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" +import { getTaskID } from "./task-id" export async function executeBackgroundContinuation( args: DelegateTaskArgs, @@ -13,8 +15,13 @@ export async function executeBackgroundContinuation( const { manager } = executorCtx try { + const taskID = getTaskID(args) + if (!taskID) { + throw new Error("task_id is required to continue a background task") + } + const task = await manager.resume({ - sessionId: args.session_id!, + sessionId: taskID, prompt: args.prompt, parentSessionID: parentContext.sessionID, parentMessageID: parentContext.messageID, @@ -31,6 +38,8 @@ export async function executeBackgroundContinuation( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, + taskId: task.sessionID, + backgroundTaskId: task.id, sessionId: task.sessionID, command: args.command, model: task.model ? { providerID: task.model.providerID, modelID: task.model.modelID } : undefined, @@ -50,14 +59,17 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id Do NOT call background_output now. Wait for notification first. - -session_id: ${task.sessionID} -${task.agent ? `subagent: ${task.agent}\n` : ""}` +${buildTaskMetadataBlock({ + sessionId: task.sessionID, + taskId: task.sessionID, + backgroundTaskId: task.id, + agent: task.agent, + })}` } catch (error) { return formatDetailedError(error, { operation: "Continue background task", args, - sessionID: args.session_id, + sessionID: getTaskID(args), }) } } diff --git a/src/tools/delegate-task/background-task.test.ts b/src/tools/delegate-task/background-task.test.ts index 84a7bc644..3837dbb6e 100644 --- a/src/tools/delegate-task/background-task.test.ts +++ b/src/tools/delegate-task/background-task.test.ts @@ -104,11 +104,14 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () => //#then - output and metadata should include canonical session linkage expectFn(result).toContain("") expectFn(result).toContain("session_id: ses_sub_123") - expectFn(result).toContain("task_id: bg_resolved") + expectFn(result).toContain("task_id: ses_sub_123") expectFn(result).toContain("background_task_id: bg_resolved") + expectFn(result).toContain("subagent: explore") expectFn(result).toContain("Background Task ID: bg_resolved") expectFn(metadataCalls).toHaveLength(1) expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_sub_123") + expectFn(metadataCalls[0].metadata.taskId).toBe("ses_sub_123") + expectFn(metadataCalls[0].metadata.backgroundTaskId).toBe("bg_resolved") }) testFn("captures late-resolved session id and emits synced metadata", async () => { @@ -152,10 +155,12 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () => //#then - late session id still propagates to task metadata contract expectFn(result).toContain("session_id: ses_late_123") - expectFn(result).toContain("task_id: bg_late") + expectFn(result).toContain("task_id: ses_late_123") expectFn(result).toContain("background_task_id: bg_late") expectFn(metadataCalls).toHaveLength(1) expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_late_123") + expectFn(metadataCalls[0].metadata.taskId).toBe("ses_late_123") + expectFn(metadataCalls[0].metadata.backgroundTaskId).toBe("bg_late") }) testFn("passes question-deny session permission when launching delegate task", async () => { diff --git a/src/tools/delegate-task/background-task.ts b/src/tools/delegate-task/background-task.ts index 73d43ad02..17b3bd632 100644 --- a/src/tools/delegate-task/background-task.ts +++ b/src/tools/delegate-task/background-task.ts @@ -10,6 +10,7 @@ import { SessionCategoryRegistry } from "../../shared/session-category-registry" import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission" import { setSessionFallbackChain } from "../../hooks/model-fallback/hook" import { stripAgentListSortPrefix } from "../../shared/agent-display-names" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" function continueSessionSetup(args: { taskID: string @@ -125,6 +126,8 @@ export async function executeBackgroundTask( description: args.description, run_in_background: args.run_in_background, command: args.command, + ...(sessionId ? { taskId: sessionId } : {}), + backgroundTaskId: task.id, ...(sessionId ? { sessionId } : {}), ...(categoryModel ? { model: { providerID: categoryModel.providerID, modelID: categoryModel.modelID } } : {}), } @@ -136,7 +139,13 @@ export async function executeBackgroundTask( await publishToolMetadata(ctx, unstableMeta) const taskMetadataBlock = sessionId - ? `\n\n\nsession_id: ${sessionId}\ntask_id: ${task.id}\nbackground_task_id: ${task.id}\n` + ? `\n\n${buildTaskMetadataBlock({ + sessionId, + taskId: sessionId, + backgroundTaskId: task.id, + agent: task.agent, + category: args.category, + })}` : "" return `Background task launched. diff --git a/src/tools/delegate-task/error-formatting.ts b/src/tools/delegate-task/error-formatting.ts index f2c24abc5..9b7b98ab2 100644 --- a/src/tools/delegate-task/error-formatting.ts +++ b/src/tools/delegate-task/error-formatting.ts @@ -1,4 +1,5 @@ import type { DelegateTaskArgs } from "./types" +import { getTaskID } from "./task-id" /** * Context for error formatting. @@ -35,8 +36,9 @@ export 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.session_id) { - lines.push(`- session_id: ${ctx.args.session_id}`) + const taskID = getTaskID(ctx.args) + if (taskID) { + lines.push(`- task_id: ${taskID}`) } } diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts index a8e412ece..45b06ea88 100644 --- a/src/tools/delegate-task/sync-continuation.ts +++ b/src/tools/delegate-task/sync-continuation.ts @@ -12,6 +12,8 @@ import { syncContinuationDeps, type SyncContinuationDeps } from "./sync-continua import { setSessionTools } from "../../shared/session-tools-store" import { normalizeSDKResponse } from "../../shared" import { buildTaskPrompt } from "./prompt-builder" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" +import { getTaskID } from "./task-id" export async function executeSyncContinuation( args: DelegateTaskArgs, @@ -21,7 +23,11 @@ export async function executeSyncContinuation( ): Promise { const { client, syncPollTimeoutMs, sisyphusAgentConfig } = executorCtx const toastManager = getTaskToastManager() - const taskId = `resume_sync_${args.session_id!.slice(0, 8)}` + const continuationID = getTaskID(args) + if (!continuationID) { + throw new Error("task_id is required to continue a sync task") + } + const taskId = `resume_sync_${continuationID.slice(0, 8)}` const startTime = new Date() if (toastManager) { @@ -42,7 +48,7 @@ export async function executeSyncContinuation( try { try { - const messagesResp = await client.session.messages({ path: { id: args.session_id! } }) + const messagesResp = await client.session.messages({ path: { id: continuationID } }) const messages = normalizeSDKResponse(messagesResp, [] as SessionMessage[]) anchorMessageCount = messages.length for (let i = messages.length - 1; i >= 0; i--) { @@ -55,7 +61,7 @@ export async function executeSyncContinuation( } } } catch { - const resumeMessageDir = getMessageDir(args.session_id!) + const resumeMessageDir = getMessageDir(continuationID) const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null resumeAgent = resumeMessage?.agent resumeModel = resumeMessage?.model?.providerID && resumeMessage?.model?.modelID @@ -71,7 +77,8 @@ export async function executeSyncContinuation( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, - sessionId: args.session_id, + taskId: continuationID, + sessionId: continuationID, sync: true, command: args.command, model: resumeModel, @@ -88,10 +95,10 @@ export async function executeSyncContinuation( question: false, ...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}), } - setSessionTools(args.session_id!, tools) + setSessionTools(continuationID, tools) await promptWithModelSuggestionRetry(client, { - path: { id: args.session_id! }, + path: { id: continuationID }, body: { ...(resumeAgent !== undefined ? { agent: resumeAgent } : {}), ...(resumeModel !== undefined ? { model: resumeModel } : {}), @@ -105,12 +112,12 @@ export async function executeSyncContinuation( toastManager.removeTask(taskId) } const errorMessage = promptError instanceof Error ? promptError.message : String(promptError) - return `Failed to send continuation prompt: ${errorMessage}\n\nSession ID: ${args.session_id}` + return `Failed to send continuation prompt: ${errorMessage}\n\nTask ID: ${continuationID}` } try { const pollError = await deps.pollSyncSession(ctx, client, { - sessionID: args.session_id!, + sessionID: continuationID, agentToUse: resumeAgent ?? "continue", toastManager, taskId, @@ -120,7 +127,7 @@ export async function executeSyncContinuation( return pollError } - const result = await deps.fetchSyncResult(client, args.session_id!, anchorMessageCount) + const result = await deps.fetchSyncResult(client, continuationID, anchorMessageCount) if (!result.ok) { return result.error } @@ -133,9 +140,11 @@ export async function executeSyncContinuation( ${result.textContent || "(No text output)"} - -session_id: ${args.session_id} -${resumeAgent ? `subagent: ${resumeAgent}\n` : ""}` +${buildTaskMetadataBlock({ + sessionId: continuationID, + taskId: continuationID, + agent: resumeAgent, + })}` } finally { if (toastManager) { toastManager.removeTask(taskId) diff --git a/src/tools/delegate-task/sync-task.ts b/src/tools/delegate-task/sync-task.ts index 4ec84696c..6cd42bb93 100644 --- a/src/tools/delegate-task/sync-task.ts +++ b/src/tools/delegate-task/sync-task.ts @@ -11,6 +11,7 @@ import { formatDetailedError } from "./error-formatting" import { syncTaskDeps, type SyncTaskDeps } from "./sync-task-deps" import { setSessionFallbackChain, clearSessionFallbackChain } from "../../hooks/model-fallback/hook" import { retrySyncPromptWithFallbacks } from "./sync-task-fallback" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" export async function executeSyncTask( args: DelegateTaskArgs, @@ -122,6 +123,7 @@ export async function executeSyncTask( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, + taskId: sessionID, sessionId: sessionID, sync: true, spawnDepth: spawnContext.childDepth, @@ -210,9 +212,12 @@ Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${mod ${result.textContent || "(No text output)"} - -session_id: ${sessionID} -` +${buildTaskMetadataBlock({ + sessionId: sessionID, + taskId: sessionID, + agent: agentToUse, + category: args.category, + })}` } finally { if (toastManager && taskId !== undefined) { toastManager.removeTask(taskId) diff --git a/src/tools/delegate-task/task-id.ts b/src/tools/delegate-task/task-id.ts new file mode 100644 index 000000000..71ec223a6 --- /dev/null +++ b/src/tools/delegate-task/task-id.ts @@ -0,0 +1,5 @@ +import type { DelegateTaskArgs } from "./types" + +export function getTaskID(args: Pick): string | undefined { + return args.task_id ?? args.session_id +} diff --git a/src/tools/delegate-task/task-schema.test.ts b/src/tools/delegate-task/task-schema.test.ts index 0a6f1c7ee..c50d175bc 100644 --- a/src/tools/delegate-task/task-schema.test.ts +++ b/src/tools/delegate-task/task-schema.test.ts @@ -41,6 +41,7 @@ function createDelegateTask(...args: Parameters