diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 3da0664f3d5a..d1742940e08d 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -64,12 +64,16 @@ export const TaskTool = Tool.define( const session = taskID ? yield* sessions.get(SessionID.make(taskID)).pipe(Effect.catchCause(() => Effect.succeed(undefined))) : undefined + const parent = yield* sessions.get(ctx.sessionID) const nextSession = session ?? (yield* sessions.create({ parentID: ctx.sessionID, title: params.description + ` (@${next.name} subagent)`, permission: [ + ...(parent.permission ?? []).filter( + (rule) => rule.permission === "external_directory" || rule.action === "deny", + ), ...(canTodo ? [] : [ diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index b94dd5208655..ccadad12dda3 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -35,9 +35,9 @@ const it = testEffect( ), ) -const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") { +const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned", permission?: Session.Info["permission"]) { const session = yield* Session.Service - const chat = yield* session.create({ title }) + const chat = yield* session.create({ title, permission }) const user = yield* session.updateMessage({ id: MessageID.ascending(), role: "user", @@ -384,4 +384,79 @@ describe("tool.task", () => { }, ), ) + + it.live("execute inherits parent external_directory and deny rules into child sessions", () => + provideTmpdirInstance( + () => + Effect.gen(function* () { + const sessions = yield* Session.Service + const parentPermission = [ + { + permission: "external_directory", + pattern: "/tmp/shared/*", + action: "allow" as const, + }, + { + permission: "read", + pattern: "*.env", + action: "deny" as const, + }, + { + permission: "edit", + pattern: "*", + action: "allow" as const, + }, + ] + const { chat, assistant } = yield* seed("Pinned", parentPermission) + const tool = yield* TaskTool + const def = yield* tool.init() + + const result = yield* def.execute( + { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "reviewer", + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: new AbortController().signal, + extra: { promptOps: stubOps() }, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + }, + ) + + const child = yield* sessions.get(result.metadata.sessionId) + expect(child.permission?.some((rule) => rule.permission === "edit" && rule.action === "allow")).toBe(false) + expect(child.permission).toEqual([ + { + permission: "external_directory", + pattern: "/tmp/shared/*", + action: "allow", + }, + { + permission: "read", + pattern: "*.env", + action: "deny", + }, + ]) + }), + { + config: { + agent: { + reviewer: { + mode: "subagent", + permission: { + task: "allow", + todowrite: "allow", + }, + }, + }, + }, + }, + ), + ) })