From d6b0e564bfa5755fb2e67a7fe27e0e35aa5e270c Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 4 Mar 2026 16:35:06 +0900 Subject: [PATCH] feat(delegate-task): unify TUI metadata by adding model field to all 5 executor paths --- .../delegate-task/background-continuation.ts | 1 + src/tools/delegate-task/background-task.ts | 1 + .../metadata-model-unification.test.ts | 172 ++++++++++++++++++ src/tools/delegate-task/sync-continuation.ts | 35 ++-- src/tools/delegate-task/sync-task.ts | 1 + .../delegate-task/unstable-agent-task.ts | 1 + 6 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 src/tools/delegate-task/metadata-model-unification.test.ts diff --git a/src/tools/delegate-task/background-continuation.ts b/src/tools/delegate-task/background-continuation.ts index a6e382930..f7f79390f 100644 --- a/src/tools/delegate-task/background-continuation.ts +++ b/src/tools/delegate-task/background-continuation.ts @@ -33,6 +33,7 @@ export async function executeBackgroundContinuation( run_in_background: args.run_in_background, sessionId: task.sessionID, command: args.command, + model: task.model ? { providerID: task.model.providerID, modelID: task.model.modelID } : undefined, }, } await ctx.metadata?.(bgContMeta) diff --git a/src/tools/delegate-task/background-task.ts b/src/tools/delegate-task/background-task.ts index 8e49daff5..c01538a59 100644 --- a/src/tools/delegate-task/background-task.ts +++ b/src/tools/delegate-task/background-task.ts @@ -67,6 +67,7 @@ export async function executeBackgroundTask( run_in_background: args.run_in_background, sessionId: sessionId ?? "pending", command: args.command, + model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined, }, } await ctx.metadata?.(unstableMeta) diff --git a/src/tools/delegate-task/metadata-model-unification.test.ts b/src/tools/delegate-task/metadata-model-unification.test.ts new file mode 100644 index 000000000..2cb67a8d2 --- /dev/null +++ b/src/tools/delegate-task/metadata-model-unification.test.ts @@ -0,0 +1,172 @@ +const { describe, test, expect, mock } = require("bun:test") + +import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" +import type { ParentContext } from "./executor-types" + +const MODEL = { providerID: "anthropic", modelID: "claude-sonnet-4-6" } + +function makeMockCtx(): ToolContextWithMetadata & { captured: any[] } { + const captured: any[] = [] + return { + sessionID: "ses_parent", + messageID: "msg_parent", + agent: "sisyphus", + abort: new AbortController().signal, + callID: "call_001", + metadata: async (input: any) => { captured.push(input) }, + captured, + } +} + +const parentContext: ParentContext = { + sessionID: "ses_parent", + messageID: "msg_parent", + agent: "sisyphus", + model: MODEL, +} + +describe("metadata model unification", () => { + describe("#given delegate-task executors", () => { + describe("#when metadata is set during execution", () => { + + test("#then sync-task metadata includes model", async () => { + const { executeSyncTask } = require("./sync-task") + const ctx = makeMockCtx() + const deps = { + createSyncSession: async () => ({ ok: true, sessionID: "ses_sync" }), + sendSyncPrompt: async () => null, + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), + } + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + category: "quick", load_skills: [], run_in_background: false, + } + + await executeSyncTask(args, ctx, { + client: { session: { create: async () => ({ data: { id: "ses_sync" } }) } }, + directory: "/tmp", + onSyncSessionCreated: null, + }, parentContext, "explore", MODEL, undefined, undefined, undefined, deps) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then background-task metadata includes model", async () => { + const { executeBackgroundTask } = require("./background-task") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + load_skills: [], run_in_background: true, subagent_type: "explore", + } + + await executeBackgroundTask(args, ctx, { + manager: { + launch: async () => ({ + id: "bg_1", description: "test", agent: "explore", + status: "pending", sessionID: "ses_bg", model: MODEL, + }), + getTask: () => undefined, + }, + } as any, parentContext, "explore", MODEL, undefined) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then unstable-agent-task metadata includes model", async () => { + const { executeUnstableAgentTask } = require("./unstable-agent-task") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + category: "quick", load_skills: [], run_in_background: false, + } + + const launchedTask = { + id: "bg_unstable", description: "test", agent: "explore", + status: "completed", sessionID: "ses_unstable", model: MODEL, + } + const result = await executeUnstableAgentTask( + args, ctx, + { + manager: { + launch: async () => launchedTask, + getTask: () => launchedTask, + }, + client: { + session: { + status: async () => ({ data: { ses_unstable: { type: "idle" } } }), + messages: async () => ({ + data: [{ + info: { role: "assistant", time: { created: 1 } }, + parts: [{ type: "text", text: "done" }], + }], + }), + }, + }, + syncPollTimeoutMs: 100, + } as any, + parentContext, "explore", MODEL, undefined, "anthropic/claude-sonnet-4-6", + ) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then background-continuation metadata includes model from task", async () => { + const { executeBackgroundContinuation } = require("./background-continuation") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "continue", prompt: "keep going", + load_skills: [], run_in_background: true, session_id: "ses_resumed", + } + + await executeBackgroundContinuation(args, ctx, { + manager: { + resume: async () => ({ + id: "bg_2", description: "continue", agent: "explore", + status: "running", sessionID: "ses_resumed", model: MODEL, + }), + }, + } as any, parentContext) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then sync-continuation metadata includes model from resumed session", async () => { + const { executeSyncContinuation } = require("./sync-continuation") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "continue", prompt: "keep going", + load_skills: [], run_in_background: false, session_id: "ses_cont", + } + + const deps = { + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), + } + + await executeSyncContinuation(args, ctx, { + client: { + session: { + messages: async () => ({ + data: [{ info: { agent: "explore", model: MODEL, providerID: "anthropic", modelID: "claude-sonnet-4-6" } }], + }), + prompt: async () => ({}), + }, + }, + } as any, deps) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + }) + }) +}) diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts index a65b20613..b716b3948 100644 --- a/src/tools/delegate-task/sync-continuation.ts +++ b/src/tools/delegate-task/sync-continuation.ts @@ -32,22 +32,7 @@ export async function executeSyncContinuation( }) } - const syncContMeta = { - 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.session_id, - sync: true, - command: args.command, - }, - } - await ctx.metadata?.(syncContMeta) - if (ctx.callID) { - storeToolMetadata(ctx.sessionID, ctx.callID, syncContMeta) - } + let syncContMeta: { title: string; metadata: Record } | undefined let resumeAgent: string | undefined let resumeModel: { providerID: string; modelID: string } | undefined @@ -78,6 +63,24 @@ export async function executeSyncContinuation( resumeVariant = resumeMessage?.model?.variant } + syncContMeta = { + 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.session_id, + sync: true, + command: args.command, + model: resumeModel, + }, + } + await ctx.metadata?.(syncContMeta) + if (ctx.callID) { + storeToolMetadata(ctx.sessionID, ctx.callID, syncContMeta) + } + const allowTask = isPlanFamily(resumeAgent) const tools = { ...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}), diff --git a/src/tools/delegate-task/sync-task.ts b/src/tools/delegate-task/sync-task.ts index 2ff600d09..115d2c57d 100644 --- a/src/tools/delegate-task/sync-task.ts +++ b/src/tools/delegate-task/sync-task.ts @@ -91,6 +91,7 @@ export async function executeSyncTask( sessionId: sessionID, sync: true, command: args.command, + model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined, }, } await ctx.metadata?.(syncTaskMeta) diff --git a/src/tools/delegate-task/unstable-agent-task.ts b/src/tools/delegate-task/unstable-agent-task.ts index 6f588482b..9a0b16c69 100644 --- a/src/tools/delegate-task/unstable-agent-task.ts +++ b/src/tools/delegate-task/unstable-agent-task.ts @@ -66,6 +66,7 @@ export async function executeUnstableAgentTask( run_in_background: args.run_in_background, sessionId: sessionID, command: args.command, + model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined, }, } await ctx.metadata?.(bgTaskMeta)