test(agent): migrate plan bypass tests to Effect runner (#27119)
This commit is contained in:
@@ -18,33 +18,23 @@
|
||||
* permissions are passed through, and Plan Mode's restrictions live on the
|
||||
* agent, not the session.
|
||||
*/
|
||||
import { test, expect, afterEach } from "bun:test"
|
||||
import { expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture"
|
||||
import { WithInstance } from "../../src/project/with-instance"
|
||||
import { Agent } from "../../src/agent/agent"
|
||||
import { deriveSubagentSessionPermission } from "../../src/agent/subagent-permissions"
|
||||
import { Permission } from "../../src/permission"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
afterEach(async () => {
|
||||
await disposeAllInstances()
|
||||
})
|
||||
|
||||
function load<A>(dir: string, fn: (svc: Agent.Interface) => Effect.Effect<A>) {
|
||||
return Effect.runPromise(provideInstance(dir)(Agent.Service.use(fn)).pipe(Effect.provide(Agent.defaultLayer)))
|
||||
}
|
||||
const it = testEffect(Agent.defaultLayer)
|
||||
|
||||
// `deriveSubagentSessionPermission` is imported from production. The test
|
||||
// exercises the actual helper that task.ts uses to build the subagent's
|
||||
// session permission, so any regression in that helper trips this test.
|
||||
|
||||
test("[#26514] subagent spawned from plan mode inherits read-only restriction (edit denied)", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await WithInstance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const planAgent = await load(tmp.path, (svc) => svc.get("plan"))
|
||||
const generalAgent = await load(tmp.path, (svc) => svc.get("general"))
|
||||
it.instance("[#26514] subagent spawned from plan mode inherits read-only restriction (edit denied)", () =>
|
||||
Effect.gen(function* () {
|
||||
const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
|
||||
const generalAgent = yield* Agent.Service.use((svc) => svc.get("general"))
|
||||
|
||||
expect(planAgent).toBeDefined()
|
||||
expect(generalAgent).toBeDefined()
|
||||
@@ -71,21 +61,17 @@ test("[#26514] subagent spawned from plan mode inherits read-only restriction (e
|
||||
|
||||
expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("deny")
|
||||
expect(Permission.evaluate("edit", "/another/path/index.tsx", effective).action).toBe("deny")
|
||||
},
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
test("[#26514] explore subagent launched from plan mode also stays read-only", async () => {
|
||||
it.instance("[#26514] explore subagent launched from plan mode also stays read-only", () =>
|
||||
// Sibling check: even though `explore` is intrinsically read-only, the
|
||||
// bug surface is the same. Including this case to document that the fix
|
||||
// should propagate the parent **agent** permissions, not just deny edit
|
||||
// when the subagent happens to already deny it.
|
||||
await using tmp = await tmpdir()
|
||||
await WithInstance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const planAgent = await load(tmp.path, (svc) => svc.get("plan"))
|
||||
const explore = await load(tmp.path, (svc) => svc.get("explore"))
|
||||
Effect.gen(function* () {
|
||||
const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
|
||||
const explore = yield* Agent.Service.use((svc) => svc.get("explore"))
|
||||
expect(planAgent).toBeDefined()
|
||||
expect(explore).toBeDefined()
|
||||
|
||||
@@ -99,29 +85,18 @@ test("[#26514] explore subagent launched from plan mode also stays read-only", a
|
||||
|
||||
// Already deny — sanity check.
|
||||
expect(Permission.evaluate("edit", "/x.ts", effective).action).toBe("deny")
|
||||
},
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
test("[#26514] custom user subagent launched from plan mode bypasses Plan Mode read-only", async () => {
|
||||
it.instance(
|
||||
"[#26514] custom user subagent launched from plan mode bypasses Plan Mode read-only",
|
||||
// The most damaging case: a user-defined subagent with default
|
||||
// permissions (allow-by-default, like `general`). The subagent must NOT
|
||||
// be able to edit when the parent agent is `plan`.
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
agent: {
|
||||
my_subagent: {
|
||||
description: "A user-defined subagent",
|
||||
mode: "subagent",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await WithInstance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const planAgent = await load(tmp.path, (svc) => svc.get("plan"))
|
||||
const my = await load(tmp.path, (svc) => svc.get("my_subagent"))
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
|
||||
const my = yield* Agent.Service.use((svc) => svc.get("my_subagent"))
|
||||
expect(planAgent).toBeDefined()
|
||||
expect(my).toBeDefined()
|
||||
|
||||
@@ -136,6 +111,15 @@ test("[#26514] custom user subagent launched from plan mode bypasses Plan Mode r
|
||||
// BUG: on origin/dev edit resolves to "allow" because the plan
|
||||
// agent's `edit: deny *` rule never reaches the subagent.
|
||||
expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("deny")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
agent: {
|
||||
my_subagent: {
|
||||
description: "A user-defined subagent",
|
||||
mode: "subagent",
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user