From e0292de723680b492fbff625562968ff21b1072b Mon Sep 17 00:00:00 2001 From: ArmirKS Date: Wed, 11 Mar 2026 01:28:26 +0100 Subject: [PATCH 1/2] feat: add parentAgent to hook inputs --- packages/opencode/src/session/message-v2.ts | 1 + packages/opencode/src/session/prompt.ts | 19 +++++- packages/opencode/src/tool/task.ts | 1 + .../opencode/test/plugin/parent-agent.test.ts | 59 +++++++++++++++++++ packages/plugin/src/index.ts | 6 +- 5 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 packages/opencode/test/plugin/parent-agent.test.ts diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 5b4e7bdbc044..4d5afd85845f 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -361,6 +361,7 @@ export namespace MessageV2 { }) .optional(), agent: z.string(), + parentAgent: z.string().optional(), model: z.object({ providerID: z.string(), modelID: z.string(), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 7698b78baba2..205ef5e04165 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -99,6 +99,7 @@ export namespace SessionPrompt { }) .optional(), agent: z.string().optional(), + parentAgent: z.string().optional(), noReply: z.boolean().optional(), tools: z .record(z.string(), z.boolean()) @@ -410,6 +411,8 @@ export namespace SessionPrompt { tool: "task", sessionID, callID: part.id, + agent: lastUser.agent, + parentAgent: lastUser.parentAgent, }, { args: taskArgs }, ) @@ -459,6 +462,8 @@ export namespace SessionPrompt { sessionID, callID: part.id, args: taskArgs, + agent: lastUser.agent, + parentAgent: lastUser.parentAgent, }, result, ) @@ -609,6 +614,7 @@ export namespace SessionPrompt { processor, bypassAgentCheck, messages: msgs, + parentAgent: lastUser.parentAgent, }) // Inject StructuredOutput tool if JSON schema mode enabled @@ -742,6 +748,7 @@ export namespace SessionPrompt { processor: SessionProcessor.Info bypassAgentCheck: boolean messages: MessageV2.WithParts[] + parentAgent?: string }) { using _ = log.time("resolveTools") const tools: Record = {} @@ -798,6 +805,8 @@ export namespace SessionPrompt { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID, + agent: ctx.agent, + parentAgent: input.parentAgent, }, { args, @@ -820,6 +829,8 @@ export namespace SessionPrompt { sessionID: ctx.sessionID, callID: ctx.callID, args, + agent: ctx.agent, + parentAgent: input.parentAgent, }, output, ) @@ -844,6 +855,8 @@ export namespace SessionPrompt { tool: key, sessionID: ctx.sessionID, callID: opts.toolCallId, + agent: ctx.agent, + parentAgent: input.parentAgent, }, { args, @@ -866,6 +879,8 @@ export namespace SessionPrompt { sessionID: ctx.sessionID, callID: opts.toolCallId, args, + agent: ctx.agent, + parentAgent: input.parentAgent, }, result, ) @@ -973,6 +988,7 @@ export namespace SessionPrompt { }, tools: input.tools, agent: agent.name, + parentAgent: input.parentAgent, model, system: input.system, format: input.format, @@ -1464,6 +1480,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the export const ShellInput = z.object({ sessionID: Identifier.schema("session"), agent: z.string(), + parentAgent: z.string().optional(), model: z .object({ providerID: z.string(), @@ -1623,7 +1640,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const cwd = Instance.directory const shellEnv = await Plugin.trigger( "shell.env", - { cwd, sessionID: input.sessionID, callID: part.callID }, + { cwd, sessionID: input.sessionID, callID: part.callID, agent: input.agent, parentAgent: input.parentAgent }, { env: {} }, ) const proc = spawn(shell, args, { diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 8c8cf827abaf..087eebdea0f8 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -133,6 +133,7 @@ export const TaskTool = Tool.define("task", async (ctx) => { providerID: model.providerID, }, agent: agent.name, + parentAgent: ctx.agent, tools: { todowrite: false, todoread: false, diff --git a/packages/opencode/test/plugin/parent-agent.test.ts b/packages/opencode/test/plugin/parent-agent.test.ts new file mode 100644 index 000000000000..b0c11a51f744 --- /dev/null +++ b/packages/opencode/test/plugin/parent-agent.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test } from "bun:test" +import { SessionPrompt } from "../../src/session/prompt" +import { MessageV2 } from "../../src/session/message-v2" + +describe("parentAgent hook input", () => { + test("PromptInput accepts parentAgent", () => { + const input = SessionPrompt.PromptInput.parse({ + sessionID: "ses_test", + agent: "scout", + parentAgent: "coder", + parts: [{ type: "text", text: "test" }], + }) + expect(input.parentAgent).toBe("coder") + }) + + test("PromptInput parentAgent is optional", () => { + const input = SessionPrompt.PromptInput.parse({ + sessionID: "ses_test", + agent: "scout", + parts: [{ type: "text", text: "test" }], + }) + expect(input.parentAgent).toBeUndefined() + }) + + test("ShellInput accepts parentAgent", () => { + const input = SessionPrompt.ShellInput.parse({ + sessionID: "ses_test", + agent: "coder", + parentAgent: "orchestrator", + command: "ls", + }) + expect(input.parentAgent).toBe("orchestrator") + }) + + test("UserMessage stores parentAgent", () => { + const msg = MessageV2.User.parse({ + id: "msg_test", + sessionID: "ses_test", + role: "user", + time: { created: Date.now() }, + agent: "scout", + parentAgent: "coder", + model: { providerID: "test", modelID: "test" }, + }) + expect(msg.parentAgent).toBe("coder") + }) + + test("UserMessage parentAgent is optional (top-level agent)", () => { + const msg = MessageV2.User.parse({ + id: "msg_test", + sessionID: "ses_test", + role: "user", + time: { created: Date.now() }, + agent: "coder", + model: { providerID: "test", modelID: "test" }, + }) + expect(msg.parentAgent).toBeUndefined() + }) +}) diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index b78bcae177d4..d85eac068477 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -182,15 +182,15 @@ export interface Hooks { output: { parts: Part[] }, ) => Promise "tool.execute.before"?: ( - input: { tool: string; sessionID: string; callID: string }, + input: { tool: string; sessionID: string; callID: string; agent?: string; parentAgent?: string }, output: { args: any }, ) => Promise "shell.env"?: ( - input: { cwd: string; sessionID?: string; callID?: string }, + input: { cwd: string; sessionID?: string; callID?: string; agent?: string; parentAgent?: string }, output: { env: Record }, ) => Promise "tool.execute.after"?: ( - input: { tool: string; sessionID: string; callID: string; args: any }, + input: { tool: string; sessionID: string; callID: string; args: any; agent?: string; parentAgent?: string }, output: { title: string output: string From ac8d7f01050ac544c0adc29eed644c13e1417afe Mon Sep 17 00:00:00 2001 From: ArmirKS Date: Wed, 11 Mar 2026 21:33:34 +0100 Subject: [PATCH 2/2] feat: add messageID to tool execute hooks for audit correlation --- packages/opencode/src/session/prompt.ts | 6 ++++++ packages/plugin/src/index.ts | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 205ef5e04165..d502a73a1216 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -410,6 +410,7 @@ export namespace SessionPrompt { { tool: "task", sessionID, + messageID: assistantMessage.id, callID: part.id, agent: lastUser.agent, parentAgent: lastUser.parentAgent, @@ -460,6 +461,7 @@ export namespace SessionPrompt { { tool: "task", sessionID, + messageID: assistantMessage.id, callID: part.id, args: taskArgs, agent: lastUser.agent, @@ -804,6 +806,7 @@ export namespace SessionPrompt { { tool: item.id, sessionID: ctx.sessionID, + messageID: input.processor.message.id, callID: ctx.callID, agent: ctx.agent, parentAgent: input.parentAgent, @@ -827,6 +830,7 @@ export namespace SessionPrompt { { tool: item.id, sessionID: ctx.sessionID, + messageID: input.processor.message.id, callID: ctx.callID, args, agent: ctx.agent, @@ -854,6 +858,7 @@ export namespace SessionPrompt { { tool: key, sessionID: ctx.sessionID, + messageID: input.processor.message.id, callID: opts.toolCallId, agent: ctx.agent, parentAgent: input.parentAgent, @@ -877,6 +882,7 @@ export namespace SessionPrompt { { tool: key, sessionID: ctx.sessionID, + messageID: input.processor.message.id, callID: opts.toolCallId, args, agent: ctx.agent, diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index d85eac068477..62312aaa5614 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -182,7 +182,7 @@ export interface Hooks { output: { parts: Part[] }, ) => Promise "tool.execute.before"?: ( - input: { tool: string; sessionID: string; callID: string; agent?: string; parentAgent?: string }, + input: { tool: string; sessionID: string; messageID: string; callID: string; agent?: string; parentAgent?: string }, output: { args: any }, ) => Promise "shell.env"?: ( @@ -190,7 +190,15 @@ export interface Hooks { output: { env: Record }, ) => Promise "tool.execute.after"?: ( - input: { tool: string; sessionID: string; callID: string; args: any; agent?: string; parentAgent?: string }, + input: { + tool: string + sessionID: string + messageID: string + callID: string + args: any + agent?: string + parentAgent?: string + }, output: { title: string output: string