fix(core): limit v2 subagent nesting depth (#37291)
This commit is contained in:
parent
198ca749fd
commit
d01dfa57b7
7 changed files with 116 additions and 0 deletions
|
|
@ -283,6 +283,12 @@ describe("Config", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("migrates the v1 experimental subagent depth", () =>
|
||||
Effect.sync(() => {
|
||||
expect(ConfigMigrateV1.migrate({ experimental: { subagent_depth: 2 } }).experimental?.subagent_depth).toBe(2)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("migrates v1 provider setup options into AISDK settings", () =>
|
||||
Effect.sync(() => {
|
||||
const migrated = ConfigMigrateV1.migrate({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { DateTime, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
||||
import path from "path"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
|
@ -164,6 +165,77 @@ describe("SubagentTool", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.live("prevents subagents from launching subagents by default", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
||||
).pipe(
|
||||
Effect.flatMap((dir) =>
|
||||
Effect.gen(function* () {
|
||||
const location = Location.Ref.make({ directory: AbsolutePath.make(dir.path) })
|
||||
const sessions = yield* SessionV2.Service
|
||||
const root = yield* sessions.create({ location })
|
||||
const parent = yield* sessions.create({ parentID: root.id, title: "parent" })
|
||||
yield* withSubagent(parent.location)
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const registry = yield* ToolRegistry.Service.pipe(Effect.provide(locations.get(parent.location)))
|
||||
yield* waitForTool(registry, SubagentTool.name)
|
||||
|
||||
expect(
|
||||
yield* executeTool(registry, {
|
||||
sessionID: parent.id,
|
||||
...toolIdentity,
|
||||
call: {
|
||||
type: "tool-call",
|
||||
id: "call-nested-subagent",
|
||||
name: SubagentTool.name,
|
||||
input: { agent: "reviewer", description: "nested", prompt: "should fail" },
|
||||
},
|
||||
}),
|
||||
).toEqual({ type: "error", value: expect.stringContaining("Subagent depth limit reached (1)") })
|
||||
expect((yield* sessions.list({ parentID: parent.id })).data).toHaveLength(0)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("allows nested subagents up to the configured depth", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
||||
).pipe(
|
||||
Effect.flatMap((dir) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(path.join(dir.path, "opencode.json"), JSON.stringify({ experimental: { subagent_depth: 2 } })),
|
||||
)
|
||||
const location = Location.Ref.make({ directory: AbsolutePath.make(dir.path) })
|
||||
const sessions = yield* SessionV2.Service
|
||||
const root = yield* sessions.create({ location })
|
||||
const parent = yield* sessions.create({ parentID: root.id, title: "parent", model: parentModel })
|
||||
yield* withSubagent(parent.location)
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const registry = yield* ToolRegistry.Service.pipe(Effect.provide(locations.get(parent.location)))
|
||||
yield* waitForTool(registry, SubagentTool.name)
|
||||
|
||||
const settled = yield* settleTool(registry, {
|
||||
sessionID: parent.id,
|
||||
...toolIdentity,
|
||||
call: {
|
||||
type: "tool-call",
|
||||
id: "call-configured-nested-subagent",
|
||||
name: SubagentTool.name,
|
||||
input: { agent: "reviewer", description: "nested", prompt: "should run" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(settled.output?.structured).toMatchObject({ status: "completed", output: childText })
|
||||
expect((yield* sessions.get(outputSessionID(settled.output?.structured))).parentID).toBe(parent.id)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("runs a foreground child session and returns the final assistant text", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue