Normalize instance lifecycle wiring (#25501)

This commit is contained in:
Kit Langton
2026-05-02 20:39:20 -04:00
committed by GitHub
parent a6464062b7
commit 7d91d3b1ed
71 changed files with 852 additions and 936 deletions

View File

@@ -3,6 +3,7 @@ import { Effect } from "effect"
import path from "path"
import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { WithInstance } from "../../src/project/with-instance"
import { Agent } from "../../src/agent/agent"
import { Permission } from "../../src/permission"
import { Global } from "@opencode-ai/core/global"
@@ -23,7 +24,7 @@ afterEach(async () => {
test("returns default native agents when no config", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const agents = await load(tmp.path, (svc) => svc.list())
@@ -41,7 +42,7 @@ test("returns default native agents when no config", async () => {
test("build agent has correct default properties", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -56,7 +57,7 @@ test("build agent has correct default properties", async () => {
test("plan agent denies edits except .opencode/plans/*", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const plan = await load(tmp.path, (svc) => svc.get("plan"))
@@ -71,7 +72,7 @@ test("plan agent denies edits except .opencode/plans/*", async () => {
test("explore agent denies edit and write", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const explore = await load(tmp.path, (svc) => svc.get("explore"))
@@ -87,7 +88,7 @@ test("explore agent denies edit and write", async () => {
test("explore agent asks for external directories and allows whitelisted external paths", async () => {
const { Truncate } = await import("../../src/tool/truncate")
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const explore = await load(tmp.path, (svc) => svc.get("explore"))
@@ -103,7 +104,7 @@ test("explore agent asks for external directories and allows whitelisted externa
test("general agent denies todo tools", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const general = await load(tmp.path, (svc) => svc.get("general"))
@@ -117,7 +118,7 @@ test("general agent denies todo tools", async () => {
test("compaction agent denies all permissions", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const compaction = await load(tmp.path, (svc) => svc.get("compaction"))
@@ -143,7 +144,7 @@ test("custom agent from config creates new agent", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const custom = await load(tmp.path, (svc) => svc.get("my_custom_agent"))
@@ -172,7 +173,7 @@ test("custom agent config overrides native agent properties", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -195,7 +196,7 @@ test("agent disable removes agent from list", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const explore = await load(tmp.path, (svc) => svc.get("explore"))
@@ -221,7 +222,7 @@ test("agent permission config merges with defaults", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -242,7 +243,7 @@ test("global permission config applies to all agents", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -261,7 +262,7 @@ test("agent steps/maxSteps config sets steps property", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -280,7 +281,7 @@ test("agent mode can be overridden", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const explore = await load(tmp.path, (svc) => svc.get("explore"))
@@ -297,7 +298,7 @@ test("agent name can be overridden", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -314,7 +315,7 @@ test("agent prompt can be set from config", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -334,7 +335,7 @@ test("unknown agent properties are placed into options", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -357,7 +358,7 @@ test("agent options merge correctly", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -382,7 +383,7 @@ test("multiple custom agents can be defined", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const agentA = await load(tmp.path, (svc) => svc.get("agent_a"))
@@ -411,7 +412,7 @@ test("Agent.list keeps the default agent first and sorts the rest by name", asyn
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const names = (await load(tmp.path, (svc) => svc.list())).map((a) => a.name)
@@ -423,7 +424,7 @@ test("Agent.list keeps the default agent first and sorts the rest by name", asyn
test("Agent.get returns undefined for non-existent agent", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const nonExistent = await load(tmp.path, (svc) => svc.get("does_not_exist"))
@@ -434,7 +435,7 @@ test("Agent.get returns undefined for non-existent agent", async () => {
test("default permission includes doom_loop and external_directory as ask", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -446,7 +447,7 @@ test("default permission includes doom_loop and external_directory as ask", asyn
test("webfetch is allowed by default", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -468,7 +469,7 @@ test("legacy tools config converts to permissions", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -490,7 +491,7 @@ test("legacy tools config maps write/edit/patch to edit permission", async () =>
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -508,7 +509,7 @@ test("Truncate.GLOB is allowed even when user denies external_directory globally
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -521,7 +522,7 @@ test("Truncate.GLOB is allowed even when user denies external_directory globally
test("global tmp directory children are allowed for external_directory", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -546,7 +547,7 @@ test("Truncate.GLOB is allowed even when user denies external_directory per-agen
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -569,7 +570,7 @@ test("explicit Truncate.GLOB deny is respected", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -601,7 +602,7 @@ description: Permission skill.
process.env.OPENCODE_TEST_HOME = tmp.path
try {
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const build = await load(tmp.path, (svc) => svc.get("build"))
@@ -617,7 +618,7 @@ description: Permission skill.
test("defaultAgent returns build when no default_agent config", async () => {
await using tmp = await tmpdir()
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const agent = await load(tmp.path, (svc) => svc.defaultAgent())
@@ -632,7 +633,7 @@ test("defaultAgent respects default_agent config set to plan", async () => {
default_agent: "plan",
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const agent = await load(tmp.path, (svc) => svc.defaultAgent())
@@ -652,7 +653,7 @@ test("defaultAgent respects default_agent config set to custom agent with mode a
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const agent = await load(tmp.path, (svc) => svc.defaultAgent())
@@ -667,7 +668,7 @@ test("defaultAgent throws when default_agent points to subagent", async () => {
default_agent: "explore",
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "explore" is a subagent')
@@ -681,7 +682,7 @@ test("defaultAgent throws when default_agent points to hidden agent", async () =
default_agent: "compaction",
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "compaction" is hidden')
@@ -695,7 +696,7 @@ test("defaultAgent throws when default_agent points to non-existent agent", asyn
default_agent: "does_not_exist",
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow(
@@ -713,7 +714,7 @@ test("defaultAgent returns plan when build is disabled and default_agent not set
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const agent = await load(tmp.path, (svc) => svc.defaultAgent())
@@ -732,7 +733,7 @@ test("defaultAgent throws when all primary agents are disabled", async () => {
},
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
// build and plan are disabled, no primary-capable agents remain

View File

@@ -4,6 +4,7 @@ import { pathToFileURL } from "url"
import { AppRuntime } from "../../src/effect/app-runtime"
import { Agent } from "../../src/agent/agent"
import { Instance } from "../../src/project/instance"
import { WithInstance } from "../../src/project/with-instance"
import { disposeAllInstances, tmpdir } from "../fixture/fixture"
afterEach(async () => {
@@ -39,7 +40,7 @@ test("plugin-registered agents appear in Agent.list", async () => {
},
})
await Instance.provide({
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const agents = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list()))