From 4a0f78f9fb3baeffbe255e607b9111ae4859f147 Mon Sep 17 00:00:00 2001 From: pi Date: Sun, 12 Apr 2026 06:54:48 +0100 Subject: [PATCH] refactor: simplify subagent tool contract --- src/extension.test.ts | 6 +++ src/schema.ts | 15 ------ src/tool-chain.test.ts | 42 ++++------------ src/tool-parallel.test.ts | 25 +++------ src/tool.test.ts | 60 ++++------------------ src/tool.ts | 103 ++++++++------------------------------ 6 files changed, 57 insertions(+), 194 deletions(-) diff --git a/src/extension.test.ts b/src/extension.test.ts index 4cb3c78..2db97d8 100644 --- a/src/extension.test.ts +++ b/src/extension.test.ts @@ -37,10 +37,16 @@ test("the extension entrypoint registers the subagent tool with the currently av assert.equal(registeredTools.length, 1); assert.equal(registeredTools[0]?.name, "subagent"); assert.deepEqual(registeredTools[0]?.parameters.required, ["model"]); + assert.equal("agent" in registeredTools[0]?.parameters.properties, false); + assert.equal("agentScope" in registeredTools[0]?.parameters.properties, false); + assert.equal("confirmProjectAgents" in registeredTools[0]?.parameters.properties, false); + assert.equal("task" in registeredTools[0]?.parameters.properties, true); assert.deepEqual(registeredTools[0]?.parameters.properties.model.enum, [ "anthropic/claude-sonnet-4-5", "openai/gpt-5", ]); + assert.equal("agent" in registeredTools[0]?.parameters.properties.tasks.items.properties, false); + assert.equal("task" in registeredTools[0]?.parameters.properties.tasks.items.properties, true); assert.deepEqual(registeredTools[0]?.parameters.properties.tasks.items.properties.model.enum, [ "anthropic/claude-sonnet-4-5", "openai/gpt-5", diff --git a/src/schema.ts b/src/schema.ts index 8179969..a148f49 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -11,7 +11,6 @@ function createTaskModelSchema(availableModels: readonly string[]) { export function createTaskItemSchema(availableModels: readonly string[]) { return Type.Object({ - agent: Type.String({ description: "Name of the agent to invoke" }), task: Type.String({ description: "Task to delegate to the child agent" }), model: createTaskModelSchema(availableModels), cwd: Type.Optional(Type.String({ description: "Optional working directory override" })), @@ -20,7 +19,6 @@ export function createTaskItemSchema(availableModels: readonly string[]) { export function createChainItemSchema(availableModels: readonly string[]) { return Type.Object({ - agent: Type.String({ description: "Name of the agent to invoke" }), task: Type.String({ description: "Task with optional {previous} placeholder" }), model: createTaskModelSchema(availableModels), cwd: Type.Optional(Type.String({ description: "Optional working directory override" })), @@ -30,22 +28,14 @@ export function createChainItemSchema(availableModels: readonly string[]) { export const TaskItemSchema = createTaskItemSchema([]); export const ChainItemSchema = createChainItemSchema([]); -export const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, { - description: "Which markdown agent sources to use", - default: "user", -}); - export function createSubagentParamsSchema(availableModels: readonly string[]) { return Type.Object({ - agent: Type.Optional(Type.String({ description: "Single-mode agent name" })), task: Type.Optional(Type.String({ description: "Single-mode delegated task" })), model: StringEnum(availableModels, { description: "Required top-level child model. Must be one of the currently available models.", }), tasks: Type.Optional(Type.Array(createTaskItemSchema(availableModels), { description: "Parallel tasks" })), chain: Type.Optional(Type.Array(createChainItemSchema(availableModels), { description: "Sequential tasks" })), - agentScope: Type.Optional(AgentScopeSchema), - confirmProjectAgents: Type.Optional(Type.Boolean({ default: true })), cwd: Type.Optional(Type.String({ description: "Single-mode working directory override" })), }); } @@ -55,12 +45,9 @@ export const SubagentParamsSchema = createSubagentParamsSchema([]); export type TaskItem = Static; export type ChainItem = Static; export type SubagentParams = Static; -export type AgentScope = Static; export interface SubagentRunResult { runId: string; - agent: string; - agentSource: "builtin" | "user" | "project" | "unknown"; task: string; requestedModel?: string; resolvedModel?: string; @@ -80,7 +67,5 @@ export interface SubagentRunResult { export interface SubagentToolDetails { mode: "single" | "parallel" | "chain"; - agentScope: AgentScope; - projectAgentsDir: string | null; results: SubagentRunResult[]; } diff --git a/src/tool-chain.test.ts b/src/tool-chain.test.ts index 5b9c9cc..47005cd 100644 --- a/src/tool-chain.test.ts +++ b/src/tool-chain.test.ts @@ -6,22 +6,13 @@ test("chain mode substitutes {previous} into the next task", async () => { const seenTasks: string[] = []; const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [ - { name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }, - { name: "planner", description: "Planner", source: "builtin", systemPrompt: "Planner prompt" }, - ], - projectAgentsDir: null, - }), runSingleTask: async ({ meta }: any) => { seenTasks.push(meta.task); return { - runId: `${meta.agent}-${seenTasks.length}`, - agent: meta.agent, - agentSource: meta.agentSource, + runId: `run-${seenTasks.length}`, task: meta.task, exitCode: 0, - finalText: meta.agent === "scout" ? "Scout output" : "Plan output", + finalText: seenTasks.length === 1 ? "Inspection output" : "Plan output", }; }, } as any); @@ -31,8 +22,8 @@ test("chain mode substitutes {previous} into the next task", async () => { { model: "anthropic/claude-sonnet-4-5", chain: [ - { agent: "scout", task: "inspect auth" }, - { agent: "planner", task: "use this context: {previous}" }, + { task: "inspect auth" }, + { task: "use this context: {previous}" }, ], }, undefined, @@ -46,25 +37,16 @@ test("chain mode substitutes {previous} into the next task", async () => { } as any, ); - assert.deepEqual(seenTasks, ["inspect auth", "use this context: Scout output"]); + assert.deepEqual(seenTasks, ["inspect auth", "use this context: Inspection output"]); assert.equal(result.content[0]?.type === "text" ? result.content[0].text : "", "Plan output"); }); test("chain mode stops on the first failed step", async () => { const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [ - { name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }, - { name: "planner", description: "Planner", source: "builtin", systemPrompt: "Planner prompt" }, - ], - projectAgentsDir: null, - }), runSingleTask: async ({ meta }: any) => { - if (meta.agent === "planner") { + if (meta.task.includes("Inspection output")) { return { - runId: "planner-2", - agent: meta.agent, - agentSource: meta.agentSource, + runId: "run-2", task: meta.task, exitCode: 1, finalText: "", @@ -72,12 +54,10 @@ test("chain mode stops on the first failed step", async () => { }; } return { - runId: "scout-1", - agent: meta.agent, - agentSource: meta.agentSource, + runId: "run-1", task: meta.task, exitCode: 0, - finalText: "Scout output", + finalText: "Inspection output", }; }, } as any); @@ -87,8 +67,8 @@ test("chain mode stops on the first failed step", async () => { { model: "anthropic/claude-sonnet-4-5", chain: [ - { agent: "scout", task: "inspect auth" }, - { agent: "planner", task: "use this context: {previous}" }, + { task: "inspect auth" }, + { task: "use this context: {previous}" }, ], }, undefined, diff --git a/src/tool-parallel.test.ts b/src/tool-parallel.test.ts index b8f5d7b..e2b7a54 100644 --- a/src/tool-parallel.test.ts +++ b/src/tool-parallel.test.ts @@ -6,13 +6,6 @@ test("parallel mode runs each task and uses the top-level model unless a task ov const requestedModels: Array = []; const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [ - { name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }, - { name: "reviewer", description: "Reviewer", source: "builtin", systemPrompt: "Reviewer prompt" }, - ], - projectAgentsDir: null, - }), resolveChildModel: ({ taskModel, topLevelModel }: any) => ({ requestedModel: taskModel ?? topLevelModel, resolvedModel: taskModel ?? topLevelModel, @@ -20,14 +13,12 @@ test("parallel mode runs each task and uses the top-level model unless a task ov runSingleTask: async ({ meta }: any) => { requestedModels.push(meta.requestedModel); return { - runId: `${meta.agent}-${meta.task}`, - agent: meta.agent, - agentSource: meta.agentSource, + runId: `run-${requestedModels.length}`, task: meta.task, requestedModel: meta.requestedModel, resolvedModel: meta.requestedModel, exitCode: 0, - finalText: `${meta.agent}:${meta.task}`, + finalText: `done:${meta.task}`, }; }, } as any); @@ -37,8 +28,8 @@ test("parallel mode runs each task and uses the top-level model unless a task ov { model: "openai/gpt-5", tasks: [ - { agent: "scout", task: "find auth code" }, - { agent: "reviewer", task: "review auth code", model: "anthropic/claude-opus-4-5" }, + { task: "find auth code" }, + { task: "review auth code", model: "anthropic/claude-opus-4-5" }, ], }, undefined, @@ -57,6 +48,8 @@ test("parallel mode runs each task and uses the top-level model unless a task ov const text = result.content[0]?.type === "text" ? result.content[0].text : ""; assert.match(text, /2\/2 succeeded/); + assert.match(text, /\[task 1\] completed: done:find auth code/); + assert.match(text, /\[task 2\] completed: done:review auth code/); assert.deepEqual(requestedModels, ["openai/gpt-5", "anthropic/claude-opus-4-5"]); }); @@ -64,10 +57,6 @@ test("parallel mode rejects per-task model overrides that are not currently avai let didRun = false; const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [{ name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }], - projectAgentsDir: null, - }), runSingleTask: async () => { didRun = true; throw new Error("should not run"); @@ -78,7 +67,7 @@ test("parallel mode rejects per-task model overrides that are not currently avai "tool-1", { model: "anthropic/claude-sonnet-4-5", - tasks: [{ agent: "scout", task: "find auth code", model: "openai/gpt-5" }], + tasks: [{ task: "find auth code", model: "openai/gpt-5" }], }, undefined, undefined, diff --git a/src/tool.test.ts b/src/tool.test.ts index 799c43b..bde60bb 100644 --- a/src/tool.test.ts +++ b/src/tool.test.ts @@ -6,24 +6,10 @@ test("single-mode subagent uses the required top-level model, emits progress, an const updates: string[] = []; const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [ - { - name: "scout", - description: "Scout", - model: "claude-haiku-4-5", - systemPrompt: "Scout prompt", - source: "builtin", - }, - ], - projectAgentsDir: null, - }), runSingleTask: async ({ onEvent, meta }: any) => { onEvent?.({ type: "tool_call", toolName: "read", args: { path: "src/auth.ts" } }); return { runId: "run-1", - agent: "scout", - agentSource: "builtin", task: "inspect auth", requestedModel: meta.requestedModel, resolvedModel: meta.resolvedModel, @@ -39,7 +25,6 @@ test("single-mode subagent uses the required top-level model, emits progress, an const result = await tool.execute( "tool-1", { - agent: "scout", task: "inspect auth", model: "anthropic/claude-sonnet-4-5", }, @@ -59,19 +44,18 @@ test("single-mode subagent uses the required top-level model, emits progress, an const text = result.content[0]?.type === "text" ? result.content[0].text : ""; assert.equal(text, "Auth code is in src/auth.ts"); + assert.equal(result.details.mode, "single"); + assert.equal(result.details.results[0]?.task, "inspect auth"); assert.equal(result.details.results[0]?.paneId, "%3"); assert.equal(result.details.results[0]?.requestedModel, "anthropic/claude-sonnet-4-5"); - assert.match(updates.join("\n"), /Running scout/); + assert.equal("agent" in (result.details.results[0] ?? {}), false); + assert.match(updates.join("\n"), /Running subagent/); }); test("single-mode subagent requires a top-level model even when execute is called directly", async () => { let didRun = false; const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [{ name: "scout", description: "Scout", systemPrompt: "Scout prompt", source: "builtin" }], - projectAgentsDir: null, - }), runSingleTask: async () => { didRun = true; throw new Error("should not run"); @@ -80,7 +64,7 @@ test("single-mode subagent requires a top-level model even when execute is calle const result = await tool.execute( "tool-1", - { agent: "scout", task: "inspect auth" }, + { task: "inspect auth" }, undefined, undefined, { @@ -101,10 +85,6 @@ test("single-mode subagent rejects models that are not currently available", asy let didRun = false; const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [{ name: "scout", description: "Scout", systemPrompt: "Scout prompt", source: "builtin" }], - projectAgentsDir: null, - }), runSingleTask: async () => { didRun = true; throw new Error("should not run"); @@ -114,7 +94,6 @@ test("single-mode subagent rejects models that are not currently available", asy const result = await tool.execute( "tool-1", { - agent: "scout", task: "inspect auth", model: "openai/gpt-5", }, @@ -134,31 +113,15 @@ test("single-mode subagent rejects models that are not currently available", asy assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /available models/i); }); -test("single-mode subagent asks before running a project-local agent", async () => { - const tool = createSubagentTool({ - discoverAgents: () => ({ - agents: [ - { - name: "reviewer", - description: "Reviewer", - systemPrompt: "Review prompt", - source: "project", - }, - ], - projectAgentsDir: "/repo/.pi/agents", - }), - runSingleTask: async () => { - throw new Error("should not run"); - }, - } as any); +test("subagent rejects requests that combine single and parallel modes", async () => { + const tool = createSubagentTool(); const result = await tool.execute( "tool-1", { - agent: "reviewer", - task: "review auth", + task: "inspect auth", model: "anthropic/claude-sonnet-4-5", - agentScope: "both", + tasks: [{ task: "review auth" }], }, undefined, undefined, @@ -167,11 +130,10 @@ test("single-mode subagent asks before running a project-local agent", async () modelRegistry: { getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], }, - hasUI: true, - ui: { confirm: async () => false }, + hasUI: false, } as any, ); assert.equal(result.isError, true); - assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /not approved/); + assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /exactly one mode/i); }); diff --git a/src/tool.ts b/src/tool.ts index 750b01b..b6f4988 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -1,5 +1,4 @@ import { Text } from "@mariozechner/pi-tui"; -import { discoverAgents } from "./agents.ts"; import { listAvailableModelReferences, normalizeAvailableModelReference, @@ -7,7 +6,6 @@ import { } from "./models.ts"; import { SubagentParamsSchema, - type AgentScope, type SubagentRunResult, type SubagentToolDetails, } from "./schema.ts"; @@ -42,28 +40,20 @@ function isFailure(result: Pick) { function makeDetails( mode: "single" | "parallel" | "chain", - agentScope: AgentScope, - projectAgentsDir: string | null, results: SubagentRunResult[], ): SubagentToolDetails { - return { mode, agentScope, projectAgentsDir, results }; + return { mode, results }; } -function makeErrorResult( - text: string, - mode: "single" | "parallel" | "chain", - agentScope: AgentScope, - projectAgentsDir: string | null, -) { +function makeErrorResult(text: string, mode: "single" | "parallel" | "chain") { return { content: [{ type: "text" as const, text }], - details: makeDetails(mode, agentScope, projectAgentsDir, []), + details: makeDetails(mode, []), isError: true, }; } export function createSubagentTool(deps: { - discoverAgents?: typeof discoverAgents; listAvailableModelReferences?: typeof listAvailableModelReferences; normalizeAvailableModelReference?: typeof normalizeAvailableModelReference; parameters?: typeof SubagentParamsSchema; @@ -77,21 +67,19 @@ export function createSubagentTool(deps: { return { name: "subagent", label: "Subagent", - description: "Delegate tasks to specialized agents running in separate child sessions.", + description: "Delegate tasks to generic subagents running in separate child sessions.", parameters: deps.parameters ?? SubagentParamsSchema, async execute(_toolCallId: string, params: any, _signal: AbortSignal | undefined, onUpdate: any, ctx: any) { - const hasSingle = Boolean(params.agent && params.task); + const hasSingle = Boolean(params.task); const hasParallel = Boolean(params.tasks?.length); const hasChain = Boolean(params.chain?.length); const modeCount = Number(hasSingle) + Number(hasParallel) + Number(hasChain); const mode = hasParallel ? "parallel" : hasChain ? "chain" : "single"; - const agentScope = (params.agentScope ?? "user") as AgentScope; if (modeCount !== 1) { - return makeErrorResult("Provide exactly one mode: single, parallel, or chain.", "single", agentScope, null); + return makeErrorResult("Provide exactly one mode: single, parallel, or chain.", "single"); } - const discovery = (deps.discoverAgents ?? discoverAgents)(ctx.cwd, { scope: agentScope }); const availableModelReferences = (deps.listAvailableModelReferences ?? listAvailableModelReferences)(ctx.modelRegistry); const availableModelsText = availableModelReferences.join(", ") || "(none)"; const normalizeModelReference = (requestedModel?: string) => @@ -101,8 +89,6 @@ export function createSubagentTool(deps: { return makeErrorResult( "No available models are configured. Configure at least one model before using subagent.", mode, - agentScope, - discovery.projectAgentsDir, ); } @@ -112,7 +98,7 @@ export function createSubagentTool(deps: { typeof params.model !== "string" || params.model.trim().length === 0 ? `Subagent requires a top-level model chosen from the available models: ${availableModelsText}` : `Invalid top-level model "${params.model}". Choose one of the available models: ${availableModelsText}`; - return makeErrorResult(message, mode, agentScope, discovery.projectAgentsDir); + return makeErrorResult(message, mode); } params.model = topLevelModel; @@ -122,10 +108,8 @@ export function createSubagentTool(deps: { const normalizedTaskModel = normalizeModelReference(task.model); if (!normalizedTaskModel) { return makeErrorResult( - `Invalid model for parallel task ${index + 1} (${task.agent}): "${task.model}". Choose one of the available models: ${availableModelsText}`, + `Invalid model for parallel task ${index + 1}: "${task.model}". Choose one of the available models: ${availableModelsText}`, mode, - agentScope, - discovery.projectAgentsDir, ); } task.model = normalizedTaskModel; @@ -137,49 +121,14 @@ export function createSubagentTool(deps: { const normalizedStepModel = normalizeModelReference(step.model); if (!normalizedStepModel) { return makeErrorResult( - `Invalid model for chain step ${index + 1} (${step.agent}): "${step.model}". Choose one of the available models: ${availableModelsText}`, + `Invalid model for chain step ${index + 1}: "${step.model}". Choose one of the available models: ${availableModelsText}`, mode, - agentScope, - discovery.projectAgentsDir, ); } step.model = normalizedStepModel; } - const requestedAgentNames = [ - ...(hasSingle ? [params.agent] : []), - ...((params.tasks ?? []).map((task: any) => task.agent)), - ...((params.chain ?? []).map((step: any) => step.agent)), - ]; - const projectAgents = requestedAgentNames - .map((name) => discovery.agents.find((candidate) => candidate.name === name)) - .filter((agent): agent is NonNullable => Boolean(agent && agent.source === "project")); - - if (projectAgents.length > 0 && (params.confirmProjectAgents ?? true) && ctx.hasUI) { - const ok = await ctx.ui.confirm( - "Run project-local agents?", - `Agents: ${projectAgents.map((agent) => agent.name).join(", ")}\nSource: ${ - discovery.projectAgentsDir ?? "(unknown)" - }`, - ); - if (!ok) { - return makeErrorResult( - "Canceled: project-local agents not approved.", - mode, - agentScope, - discovery.projectAgentsDir, - ); - } - } - - const resolveAgent = (name: string) => { - const agent = discovery.agents.find((candidate) => candidate.name === name); - if (!agent) throw new Error(`Unknown agent: ${name}`); - return agent; - }; - const runTask = async (input: { - agentName: string; task: string; cwd?: string; taskModel?: string; @@ -187,7 +136,6 @@ export function createSubagentTool(deps: { step?: number; mode: "single" | "parallel" | "chain"; }) => { - const agent = resolveAgent(input.agentName); const model = (deps.resolveChildModel ?? resolveChildModel)({ taskModel: input.taskModel, topLevelModel: params.model, @@ -197,22 +145,18 @@ export function createSubagentTool(deps: { cwd: input.cwd ?? ctx.cwd, onEvent(event) { onUpdate?.({ - content: [{ type: "text", text: `Running ${input.agentName}: ${event.type}` }], - details: makeDetails(input.mode, agentScope, discovery.projectAgentsDir, []), + content: [{ type: "text", text: `Running subagent: ${event.type}` }], + details: makeDetails(input.mode, []), }); }, meta: { mode: input.mode, taskIndex: input.taskIndex, step: input.step, - agent: agent.name, - agentSource: agent.source, task: input.task, cwd: input.cwd ?? ctx.cwd, requestedModel: model.requestedModel, resolvedModel: model.resolvedModel, - systemPrompt: agent.systemPrompt, - tools: agent.tools, }, }) as Promise; }; @@ -220,7 +164,6 @@ export function createSubagentTool(deps: { if (hasSingle) { try { const result = await runTask({ - agentName: params.agent, task: params.task, cwd: params.cwd, mode: "single", @@ -228,13 +171,13 @@ export function createSubagentTool(deps: { return { content: [{ type: "text" as const, text: result.finalText }], - details: makeDetails("single", agentScope, discovery.projectAgentsDir, [result]), + details: makeDetails("single", [result]), isError: isFailure(result), }; } catch (error) { return { content: [{ type: "text" as const, text: (error as Error).message }], - details: makeDetails("single", agentScope, discovery.projectAgentsDir, []), + details: makeDetails("single", []), isError: true, }; } @@ -249,7 +192,7 @@ export function createSubagentTool(deps: { text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`, }, ], - details: makeDetails("parallel", agentScope, discovery.projectAgentsDir, []), + details: makeDetails("parallel", []), isError: true, }; } @@ -257,7 +200,6 @@ export function createSubagentTool(deps: { const liveResults: SubagentRunResult[] = []; const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (task: any, index) => { const result = await runTask({ - agentName: task.agent, task: task.task, cwd: task.cwd, taskModel: task.model, @@ -267,19 +209,19 @@ export function createSubagentTool(deps: { liveResults[index] = result; onUpdate?.({ content: [{ type: "text", text: `Parallel: ${liveResults.filter(Boolean).length}/${params.tasks.length} finished` }], - details: makeDetails("parallel", agentScope, discovery.projectAgentsDir, liveResults.filter(Boolean)), + details: makeDetails("parallel", liveResults.filter(Boolean)), }); return result; }); const successCount = results.filter((result) => !isFailure(result)).length; const summary = results - .map((result) => `[${result.agent}] ${isFailure(result) ? "failed" : "completed"}: ${result.finalText || "(no output)"}`) + .map((result, index) => `[task ${index + 1}] ${isFailure(result) ? "failed" : "completed"}: ${result.finalText || "(no output)"}`) .join("\n\n"); return { content: [{ type: "text" as const, text: `Parallel: ${successCount}/${results.length} succeeded\n\n${summary}` }], - details: makeDetails("parallel", agentScope, discovery.projectAgentsDir, results), + details: makeDetails("parallel", results), isError: successCount !== results.length, }; } @@ -290,7 +232,6 @@ export function createSubagentTool(deps: { const item = params.chain[index]; const task = item.task.replaceAll("{previous}", previous); const result = await runTask({ - agentName: item.agent, task, cwd: item.cwd, taskModel: item.model, @@ -299,7 +240,7 @@ export function createSubagentTool(deps: { }); onUpdate?.({ content: [{ type: "text", text: `Chain: completed step ${index + 1}/${params.chain.length}` }], - details: makeDetails("chain", agentScope, discovery.projectAgentsDir, [...results, result]), + details: makeDetails("chain", [...results, result]), }); results.push(result); if (isFailure(result)) { @@ -307,10 +248,10 @@ export function createSubagentTool(deps: { content: [ { type: "text" as const, - text: `Chain stopped at step ${index + 1} (${item.agent}): ${result.finalText || result.stopReason || "failed"}`, + text: `Chain stopped at step ${index + 1}: ${result.finalText || result.stopReason || "failed"}`, }, ], - details: makeDetails("chain", agentScope, discovery.projectAgentsDir, results), + details: makeDetails("chain", results), isError: true, }; } @@ -320,13 +261,13 @@ export function createSubagentTool(deps: { const finalResult = results[results.length - 1]; return { content: [{ type: "text" as const, text: finalResult?.finalText ?? "" }], - details: makeDetails("chain", agentScope, discovery.projectAgentsDir, results), + details: makeDetails("chain", results), }; }, renderCall(args: any) { if (args.tasks?.length) return new Text(`subagent parallel (${args.tasks.length} tasks)`, 0, 0); if (args.chain?.length) return new Text(`subagent chain (${args.chain.length} steps)`, 0, 0); - return new Text(`subagent ${args.agent ?? ""}`.trim(), 0, 0); + return new Text("subagent", 0, 0); }, renderResult(result: { content: Array<{ type: string; text?: string }> }) { const first = result.content[0];