From 16d66c209ded6475967cdd5d20815eafb2be6a1d Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 13 Sep 2025 12:47:18 -0500 Subject: [PATCH] respect subagent in command, add `subtask` flag (#2569) --- packages/opencode/src/command/index.ts | 2 + packages/opencode/src/config/config.ts | 1 + packages/opencode/src/session/prompt.ts | 125 ++++++++++++++++++++++-- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index a9356cdd..8ee1df2b 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -10,6 +10,7 @@ export namespace Command { agent: z.string().optional(), model: z.string().optional(), template: z.string(), + subtask: z.boolean().optional(), }) .openapi({ ref: "Command", @@ -28,6 +29,7 @@ export namespace Command { model: command.model, description: command.description, template: command.template, + subtask: command.subtask, } } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index f4b8608d..22ff58db 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -244,6 +244,7 @@ export namespace Config { description: z.string().optional(), agent: z.string().optional(), model: z.string().optional(), + subtask: z.boolean().optional(), }) export type Command = z.infer diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 8e70fa73..ef3deeb3 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -38,6 +38,7 @@ import { MCP } from "../mcp" import { LSP } from "../lsp" import { ReadTool } from "../tool/read" import { ListTool } from "../tool/ls" +import { TaskTool } from "../tool/task" import { FileTime } from "../file/time" import { Permission } from "../permission" import { Snapshot } from "../snapshot" @@ -1315,7 +1316,7 @@ export namespace SessionPrompt { export async function command(input: CommandInput) { log.info("command", input) const command = await Command.get(input.command) - const agent = command.agent ?? input.agent ?? "build" + const agentName = command.agent ?? input.agent ?? "build" let template = command.template.replace("$ARGUMENTS", input.arguments) @@ -1385,22 +1386,134 @@ export namespace SessionPrompt { return Provider.parseModel(command.model) } if (command.agent) { - const agent = await Agent.get(command.agent) - if (agent.model) { - return agent.model + const cmdAgent = await Agent.get(command.agent) + if (cmdAgent.model) { + return cmdAgent.model } } if (input.model) { return Provider.parseModel(input.model) } - return undefined + return await Provider.defaultModel() })() + const agent = await Agent.get(agentName) + if (agent.mode === "subagent" || command.subtask) { + using abort = lock(input.sessionID) + + const userMsg: MessageV2.User = { + id: Identifier.ascending("message"), + sessionID: input.sessionID, + time: { + created: Date.now(), + }, + role: "user", + } + await Session.updateMessage(userMsg) + const userPart: MessageV2.Part = { + type: "text", + id: Identifier.ascending("part"), + messageID: userMsg.id, + sessionID: input.sessionID, + text: "The following tool was executed by the user", + synthetic: true, + } + await Session.updatePart(userPart) + + const assistantMsg: MessageV2.Assistant = { + id: Identifier.ascending("message"), + sessionID: input.sessionID, + system: [], + mode: agentName, + cost: 0, + path: { + cwd: Instance.directory, + root: Instance.worktree, + }, + time: { + created: Date.now(), + }, + role: "assistant", + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: model.modelID, + providerID: model.providerID, + } + await Session.updateMessage(assistantMsg) + + const args = { + description: "Consulting " + agent.name, + subagent_type: agent.name, + prompt: template, + } + const toolPart: MessageV2.ToolPart = { + type: "tool", + id: Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: input.sessionID, + tool: "task", + callID: ulid(), + state: { + status: "running", + time: { + start: Date.now(), + }, + input: { + description: args.description, + subagent_type: args.subagent_type, + // truncate prompt to preserve context + prompt: args.prompt.length > 100 ? args.prompt.substring(0, 97) + "..." : args.prompt, + }, + }, + } + await Session.updatePart(toolPart) + + const result = await TaskTool.init().then((t) => + t.execute(args, { + sessionID: input.sessionID, + abort: abort.signal, + agent: agent.name, + messageID: assistantMsg.id, + extra: {}, + metadata: async (metadata) => { + if (toolPart.state.status === "running") { + toolPart.state.metadata = metadata.metadata + toolPart.state.title = metadata.title + await Session.updatePart(toolPart) + } + }, + }), + ) + + assistantMsg.time.completed = Date.now() + await Session.updateMessage(assistantMsg) + if (toolPart.state.status === "running") { + toolPart.state = { + status: "completed", + time: { + ...toolPart.state.time, + end: Date.now(), + }, + input: toolPart.state.input, + title: "", + metadata: result.metadata, + output: result.output, + } + await Session.updatePart(toolPart) + } + + return { info: assistantMsg, parts: [toolPart] } + } + return prompt({ sessionID: input.sessionID, messageID: input.messageID, model, - agent, + agent: agentName, parts, }) }