From 7bc23f6f9d2f026decfdc4b248b6c9a832faaf76 Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:05:11 +0100 Subject: [PATCH 1/3] plugin(hook): trigger tool.execute.before/after hooks for subtask execution and pass command name through --- packages/opencode/src/session/message-v2.ts | 1 + packages/opencode/src/session/prompt.ts | 65 ++++++++++++++------- packages/opencode/src/tool/task.ts | 1 + 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 76162c79780..4781b0c47b1 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -160,6 +160,7 @@ export namespace MessageV2 { prompt: z.string(), description: z.string(), agent: z.string(), + command: z.string().optional(), }) export type SubtaskPart = z.infer diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index e71162d0b5d..5108190132a 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -325,42 +325,62 @@ export namespace SessionPrompt { prompt: task.prompt, description: task.description, subagent_type: task.agent, + command: task.command, }, time: { start: Date.now(), }, }, })) as MessageV2.ToolPart + const taskArgs = { + prompt: task.prompt, + description: task.description, + subagent_type: task.agent, + command: task.command, + } + await Plugin.trigger( + "tool.execute.before", + { + tool: "task", + sessionID, + callID: part.id, + }, + { args: taskArgs }, + ) let executionError: Error | undefined const result = await taskTool - .execute( - { - prompt: task.prompt, - description: task.description, - subagent_type: task.agent, - }, - { - agent: task.agent, - messageID: assistantMessage.id, - sessionID: sessionID, - abort, - async metadata(input) { - await Session.updatePart({ - ...part, - type: "tool", - state: { - ...part.state, - ...input, - }, - } satisfies MessageV2.ToolPart) - }, + .execute(taskArgs, { + agent: task.agent, + messageID: assistantMessage.id, + sessionID: sessionID, + abort, + async metadata(input) { + await Session.updatePart({ + ...part, + type: "tool", + state: { + ...part.state, + ...input, + }, + } satisfies MessageV2.ToolPart) }, - ) + }) .catch((error) => { executionError = error log.error("subtask execution failed", { error, agent: task.agent, description: task.description }) return undefined }) + if (result) { + await Plugin.trigger( + "tool.execute.after", + { + tool: "task", + sessionID, + callID: part.id, + }, + result, + ) + } assistantMessage.finish = "tool-calls" assistantMessage.time.completed = Date.now() await Session.updateMessage(assistantMessage) @@ -1297,6 +1317,7 @@ export namespace SessionPrompt { type: "subtask" as const, agent: agent.name, description: command.description ?? "", + command: input.command, // TODO: how can we make task tool accept a more complex input? prompt: await resolvePromptParts(template).then((x) => x.find((y) => y.type === "text")?.text ?? ""), }, diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index fdbae41dd08..bc93f497a91 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -26,6 +26,7 @@ export const TaskTool = Tool.define("task", async () => { prompt: z.string().describe("The task for the agent to perform"), subagent_type: z.string().describe("The type of specialized agent to use for this task"), session_id: z.string().describe("Existing Task session to continue").optional(), + command: z.string().describe("The command that triggered this task").optional(), }), async execute(params, ctx) { const agent = await Agent.get(params.subagent_type) From c711526a74d921df9ea4b6a9fc6424eb98eac4c7 Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:55:06 +0100 Subject: [PATCH 2/3] sdk --- packages/sdk/js/src/v2/gen/types.gen.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index ca8d25fd517..00f209c6d88 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -417,6 +417,7 @@ export type Part = prompt: string description: string agent: string + command?: string } | ReasoningPart | FilePart @@ -1600,6 +1601,7 @@ export type SubtaskPartInput = { prompt: string description: string agent: string + command?: string } export type Command = { From ea4bc43e15e985e87ed6d7d5ad430607e611737a Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:31:48 +0100 Subject: [PATCH 3/3] fix: execute.after triggers unconditionally --- packages/opencode/src/session/prompt.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5108190132a..e63d54787f2 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -370,17 +370,15 @@ export namespace SessionPrompt { log.error("subtask execution failed", { error, agent: task.agent, description: task.description }) return undefined }) - if (result) { - await Plugin.trigger( - "tool.execute.after", - { - tool: "task", - sessionID, - callID: part.id, - }, - result, - ) - } + await Plugin.trigger( + "tool.execute.after", + { + tool: "task", + sessionID, + callID: part.id, + }, + result, + ) assistantMessage.finish = "tool-calls" assistantMessage.time.completed = Date.now() await Session.updateMessage(assistantMessage)