Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/opencode/src/session/message-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ export namespace MessageV2 {
})
.optional(),
agent: z.string(),
parentAgent: z.string().optional(),
model: z.object({
providerID: z.string(),
modelID: z.string(),
Expand Down
19 changes: 18 additions & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export namespace SessionPrompt {
})
.optional(),
agent: z.string().optional(),
parentAgent: z.string().optional(),
noReply: z.boolean().optional(),
tools: z
.record(z.string(), z.boolean())
Expand Down Expand Up @@ -409,6 +410,8 @@ export namespace SessionPrompt {
tool: "task",
sessionID,
callID: part.id,
agent: lastUser.agent,
parentAgent: lastUser.parentAgent,
},
{ args: taskArgs },
)
Expand Down Expand Up @@ -458,6 +461,8 @@ export namespace SessionPrompt {
sessionID,
callID: part.id,
args: taskArgs,
agent: lastUser.agent,
parentAgent: lastUser.parentAgent,
},
result,
)
Expand Down Expand Up @@ -607,6 +612,7 @@ export namespace SessionPrompt {
processor,
bypassAgentCheck,
messages: msgs,
parentAgent: lastUser.parentAgent,
})

// Inject StructuredOutput tool if JSON schema mode enabled
Expand Down Expand Up @@ -739,6 +745,7 @@ export namespace SessionPrompt {
processor: SessionProcessor.Info
bypassAgentCheck: boolean
messages: MessageV2.WithParts[]
parentAgent?: string
}) {
using _ = log.time("resolveTools")
const tools: Record<string, AITool> = {}
Expand Down Expand Up @@ -795,6 +802,8 @@ export namespace SessionPrompt {
tool: item.id,
sessionID: ctx.sessionID,
callID: ctx.callID,
agent: ctx.agent,
parentAgent: input.parentAgent,
},
{
args,
Expand All @@ -817,6 +826,8 @@ export namespace SessionPrompt {
sessionID: ctx.sessionID,
callID: ctx.callID,
args,
agent: ctx.agent,
parentAgent: input.parentAgent,
},
output,
)
Expand All @@ -841,6 +852,8 @@ export namespace SessionPrompt {
tool: key,
sessionID: ctx.sessionID,
callID: opts.toolCallId,
agent: ctx.agent,
parentAgent: input.parentAgent,
},
{
args,
Expand All @@ -863,6 +876,8 @@ export namespace SessionPrompt {
sessionID: ctx.sessionID,
callID: opts.toolCallId,
args,
agent: ctx.agent,
parentAgent: input.parentAgent,
},
result,
)
Expand Down Expand Up @@ -970,6 +985,7 @@ export namespace SessionPrompt {
},
tools: input.tools,
agent: agent.name,
parentAgent: input.parentAgent,
model,
system: input.system,
format: input.format,
Expand Down Expand Up @@ -1461,6 +1477,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(),
Expand Down Expand Up @@ -1620,7 +1637,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, {
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
59 changes: 59 additions & 0 deletions packages/opencode/test/plugin/parent-agent.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
})
6 changes: 3 additions & 3 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,15 @@ export interface Hooks {
output: { parts: Part[] },
) => Promise<void>
"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<void>
"shell.env"?: (
input: { cwd: string; sessionID?: string; callID?: string },
input: { cwd: string; sessionID?: string; callID?: string; agent?: string; parentAgent?: string },
output: { env: Record<string, string> },
) => Promise<void>
"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
Expand Down
Loading