Procházet zdrojové kódy

Normalize instance lifecycle wiring (#25501)

Kit Langton před 3 měsíci
rodič
revize
7d91d3b1ed
71 změnil soubory, kde provedl 852 přidání a 936 odebrání
  1. 2 1
      packages/opencode/src/cli/bootstrap.ts
  2. 3 2
      packages/opencode/src/cli/cmd/agent.ts
  3. 2 1
      packages/opencode/src/cli/cmd/github.ts
  4. 7 6
      packages/opencode/src/cli/cmd/mcp.ts
  5. 2 2
      packages/opencode/src/cli/cmd/providers.ts
  6. 3 3
      packages/opencode/src/cli/cmd/tui/plugin/runtime.ts
  7. 2 2
      packages/opencode/src/cli/cmd/tui/worker.ts
  8. 3 4
      packages/opencode/src/effect/app-runtime.ts
  9. 11 0
      packages/opencode/src/project/instance-layer.ts
  10. 10 21
      packages/opencode/src/project/instance-runtime.ts
  11. 9 18
      packages/opencode/src/project/instance-store.ts
  12. 0 7
      packages/opencode/src/project/instance.ts
  13. 10 0
      packages/opencode/src/project/with-instance.ts
  14. 27 10
      packages/opencode/src/server/routes/instance/config.ts
  15. 3 3
      packages/opencode/src/server/routes/instance/httpapi/server.ts
  16. 2 2
      packages/opencode/src/server/routes/instance/middleware.ts
  17. 2 2
      packages/opencode/src/server/workspace.ts
  18. 17 10
      packages/opencode/src/worktree/index.ts
  19. 11 10
      packages/opencode/test/acp/event-subscription.test.ts
  20. 39 38
      packages/opencode/test/agent/agent.test.ts
  21. 2 1
      packages/opencode/test/agent/plugin-agent-regression.test.ts
  22. 2 1
      packages/opencode/test/bus/bus-integration.test.ts
  23. 2 1
      packages/opencode/test/bus/bus.test.ts
  24. 53 52
      packages/opencode/test/config/config.test.ts
  25. 2 1
      packages/opencode/test/control-plane/workspace.test.ts
  26. 3 2
      packages/opencode/test/file/fsmonitor.test.ts
  27. 55 54
      packages/opencode/test/file/index.test.ts
  28. 12 11
      packages/opencode/test/file/path-traversal.test.ts
  29. 3 2
      packages/opencode/test/file/watcher.test.ts
  30. 2 1
      packages/opencode/test/fixture/fixture.ts
  31. 13 12
      packages/opencode/test/lsp/client.test.ts
  32. 4 3
      packages/opencode/test/mcp/headers.test.ts
  33. 2 1
      packages/opencode/test/mcp/lifecycle.test.ts
  34. 5 4
      packages/opencode/test/mcp/oauth-auto-connect.test.ts
  35. 4 3
      packages/opencode/test/mcp/oauth-browser.test.ts
  36. 7 6
      packages/opencode/test/permission-task.test.ts
  37. 2 1
      packages/opencode/test/permission/next.test.ts
  38. 2 1
      packages/opencode/test/project/instance-bootstrap-regression.test.ts
  39. 43 81
      packages/opencode/test/project/instance.test.ts
  40. 3 2
      packages/opencode/test/project/vcs.test.ts
  41. 3 2
      packages/opencode/test/project/worktree.test.ts
  42. 21 40
      packages/opencode/test/provider/amazon-bedrock.test.ts
  43. 14 13
      packages/opencode/test/provider/gitlab-duo.test.ts
  44. 125 222
      packages/opencode/test/provider/provider.test.ts
  45. 4 3
      packages/opencode/test/pty/pty-output-isolation.test.ts
  46. 3 2
      packages/opencode/test/pty/pty-session.test.ts
  47. 4 3
      packages/opencode/test/pty/pty-shell.test.ts
  48. 2 1
      packages/opencode/test/question/question.test.ts
  49. 7 6
      packages/opencode/test/server/global-session-list.test.ts
  50. 3 2
      packages/opencode/test/server/httpapi-experimental.test.ts
  51. 2 2
      packages/opencode/test/server/httpapi-instance-context.test.ts
  52. 2 1
      packages/opencode/test/server/httpapi-mcp.test.ts
  53. 2 1
      packages/opencode/test/server/httpapi-provider.test.ts
  54. 2 1
      packages/opencode/test/server/httpapi-sdk.test.ts
  55. 3 2
      packages/opencode/test/server/httpapi-session.test.ts
  56. 2 1
      packages/opencode/test/server/httpapi-sync.test.ts
  57. 2 1
      packages/opencode/test/server/session-actions.test.ts
  58. 21 20
      packages/opencode/test/server/session-list.test.ts
  59. 5 4
      packages/opencode/test/server/session-messages.test.ts
  60. 4 3
      packages/opencode/test/server/session-select.test.ts
  61. 20 19
      packages/opencode/test/session/compaction.test.ts
  62. 9 8
      packages/opencode/test/session/llm.test.ts
  63. 43 42
      packages/opencode/test/session/messages-pagination.test.ts
  64. 5 4
      packages/opencode/test/session/session.test.ts
  65. 2 1
      packages/opencode/test/session/structured-output-integration.test.ts
  66. 58 57
      packages/opencode/test/snapshot/snapshot.test.ts
  67. 25 24
      packages/opencode/test/tool/apply_patch.test.ts
  68. 19 18
      packages/opencode/test/tool/edit.test.ts
  69. 8 7
      packages/opencode/test/tool/external-directory.test.ts
  70. 42 41
      packages/opencode/test/tool/shell.test.ts
  71. 4 3
      packages/opencode/test/tool/webfetch.test.ts

+ 2 - 1
packages/opencode/src/cli/bootstrap.ts

@@ -1,8 +1,9 @@
 import { Instance } from "../project/instance"
 import { InstanceRuntime } from "../project/instance-runtime"
+import { WithInstance } from "../project/with-instance"
 
 export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
-  return Instance.provide({
+  return WithInstance.provide({
     directory,
     fn: async () => {
       try {

+ 3 - 2
packages/opencode/src/cli/cmd/agent.ts

@@ -10,6 +10,7 @@ import fs from "fs/promises"
 import { Filesystem } from "@/util/filesystem"
 import matter from "gray-matter"
 import { Instance } from "../../project/instance"
+import { WithInstance } from "../../project/with-instance"
 import { EOL } from "os"
 import type { Argv } from "yargs"
 
@@ -61,7 +62,7 @@ const AgentCreateCommand = cmd({
         describe: "model to use in the format of provider/model",
       }),
   async handler(args) {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         const cliPath = args.path
@@ -236,7 +237,7 @@ const AgentListCommand = cmd({
   command: "list",
   describe: "list all available agents",
   async handler() {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         const agents = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list()))

+ 2 - 1
packages/opencode/src/cli/cmd/github.ts

@@ -20,6 +20,7 @@ import { UI } from "../ui"
 import { cmd } from "./cmd"
 import { ModelsDev } from "@/provider/models"
 import { Instance } from "@/project/instance"
+import { WithInstance } from "@/project/with-instance"
 import { bootstrap } from "../bootstrap"
 import { SessionShare } from "@/share/session"
 import { Session } from "@/session/session"
@@ -203,7 +204,7 @@ export const GithubInstallCommand = cmd({
   command: "install",
   describe: "install the GitHub agent",
   async handler() {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         {

+ 7 - 6
packages/opencode/src/cli/cmd/mcp.ts

@@ -10,6 +10,7 @@ import { McpOAuthProvider } from "../../mcp/oauth-provider"
 import { Config } from "@/config/config"
 import { ConfigMCP } from "../../config/mcp"
 import { Instance } from "../../project/instance"
+import { WithInstance } from "../../project/with-instance"
 import { Installation } from "../../installation"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 import path from "path"
@@ -114,7 +115,7 @@ export const McpListCommand = cmd({
   aliases: ["ls"],
   describe: "list MCP servers and their status",
   async handler() {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         UI.empty()
@@ -186,7 +187,7 @@ export const McpAuthCommand = cmd({
       })
       .command(McpAuthListCommand),
   async handler(args) {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         UI.empty()
@@ -318,7 +319,7 @@ export const McpAuthListCommand = cmd({
   aliases: ["ls"],
   describe: "list OAuth-capable MCP servers and their auth status",
   async handler() {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         UI.empty()
@@ -357,7 +358,7 @@ export const McpLogoutCommand = cmd({
       type: "string",
     }),
   async handler(args) {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         UI.empty()
@@ -448,7 +449,7 @@ export const McpAddCommand = cmd({
   command: "add",
   describe: "add an MCP server",
   async handler() {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         UI.empty()
@@ -618,7 +619,7 @@ export const McpDebugCommand = cmd({
       demandOption: true,
     }),
   async handler(args) {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         UI.empty()

+ 2 - 2
packages/opencode/src/cli/cmd/providers.ts

@@ -13,7 +13,7 @@ import os from "os"
 import { Config } from "@/config/config"
 import { Global } from "@opencode-ai/core/global"
 import { Plugin } from "../../plugin"
-import { Instance } from "../../project/instance"
+import { WithInstance } from "../../project/with-instance"
 import type { Hooks } from "@opencode-ai/plugin"
 import { Process } from "@/util/process"
 import { text } from "node:stream/consumers"
@@ -303,7 +303,7 @@ export const ProvidersLoginCommand = cmd({
         type: "string",
       }),
   async handler(args) {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: process.cwd(),
       async fn() {
         UI.empty()

+ 3 - 3
packages/opencode/src/cli/cmd/tui/plugin/runtime.ts

@@ -16,7 +16,7 @@ import { TuiConfig } from "@/cli/cmd/tui/config/tui"
 import * as Log from "@opencode-ai/core/util/log"
 import { errorData, errorMessage } from "@/util/error"
 import { isRecord } from "@/util/record"
-import { Instance } from "@/project/instance"
+import { WithInstance } from "@/project/with-instance"
 import {
   readPackageThemes,
   readPluginId,
@@ -790,7 +790,7 @@ async function addPluginBySpec(state: RuntimeState | undefined, raw: string) {
     state.pending.delete(spec)
     return true
   }
-  const ready = await Instance.provide({
+  const ready = await WithInstance.provide({
     directory: state.directory,
     fn: () => resolveExternalPlugins([cfg], () => TuiConfig.waitForDependencies()),
   }).catch((error) => {
@@ -986,7 +986,7 @@ async function load(input: { api: Api; config: TuiConfig.Info }) {
   }
   runtime = next
   try {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: cwd,
       fn: async () => {
         const records = Flag.OPENCODE_PURE ? [] : (config.plugin_origins ?? [])

+ 2 - 2
packages/opencode/src/cli/cmd/tui/worker.ts

@@ -1,8 +1,8 @@
 import { Installation } from "@/installation"
 import { Server } from "@/server/server"
 import * as Log from "@opencode-ai/core/util/log"
-import { Instance } from "@/project/instance"
 import { InstanceRuntime } from "@/project/instance-runtime"
+import { WithInstance } from "@/project/with-instance"
 import { Rpc } from "@/util/rpc"
 import { upgrade } from "@/cli/upgrade"
 import { Config } from "@/config/config"
@@ -77,7 +77,7 @@ export const rpc = {
     return { url: server.url.toString() }
   },
   async checkUpgrade(input: { directory: string }) {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: input.directory,
       fn: async () => {
         await upgrade().catch(() => {})

+ 3 - 4
packages/opencode/src/effect/app-runtime.ts

@@ -40,7 +40,7 @@ import { Command } from "@/command"
 import { Truncate } from "@/tool/truncate"
 import { ToolRegistry } from "@/tool/registry"
 import { Format } from "@/format"
-import { InstanceRuntime } from "@/project/instance-runtime"
+import { InstanceLayer } from "@/project/instance-layer"
 import { Project } from "@/project/project"
 import { Vcs } from "@/project/vcs"
 import { Workspace } from "@/control-plane/workspace"
@@ -93,17 +93,16 @@ export const AppLayer = Layer.mergeAll(
   Truncate.defaultLayer,
   ToolRegistry.defaultLayer,
   Format.defaultLayer,
-  InstanceRuntime.layer,
   Project.defaultLayer,
   Vcs.defaultLayer,
   Workspace.defaultLayer,
-  Worktree.defaultLayer,
+  Worktree.appLayer,
   Pty.defaultLayer,
   Installation.defaultLayer,
   ShareNext.defaultLayer,
   SessionShare.defaultLayer,
   SyncEvent.defaultLayer,
-).pipe(Layer.provideMerge(Observability.layer))
+).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))
 
 const rt = ManagedRuntime.make(AppLayer, { memoMap })
 type Runtime = Pick<typeof rt, "runSync" | "runPromise" | "runPromiseExit" | "runFork" | "runCallback" | "dispose">

+ 11 - 0
packages/opencode/src/project/instance-layer.ts

@@ -0,0 +1,11 @@
+import { Effect, Layer } from "effect"
+import { InstanceStore } from "./instance-store"
+
+export const layer = Layer.unwrap(
+  Effect.promise(async () => {
+    const { InstanceBootstrap } = await import("./bootstrap")
+    return InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))
+  }),
+)
+
+export * as InstanceLayer from "./instance-layer"

+ 10 - 21
packages/opencode/src/project/instance-runtime.ts

@@ -1,27 +1,16 @@
-import { makeRuntime } from "@/effect/run-service"
+import { AppRuntime } from "@/effect/app-runtime"
 import { type InstanceContext } from "./instance-context"
 import { InstanceStore, type LoadInput } from "./instance-store"
-import { Effect, Layer } from "effect"
 
-// Production InstanceStore wiring plus a bridge for Promise/ALS callers that
-// cannot yet yield InstanceStore.Service. This keeps InstanceStore itself
-// low-level while still giving legacy Hono and CLI paths the production
-// bootstrap implementation. Delete the Promise helpers once those callers are
-// migrated to Effect boundaries that provide InstanceStore directly.
-// Keep the bootstrap implementation import lazy: Instance is imported broadly,
-// and importing the app bootstrap graph at module load can trigger ESM cycles.
-export const layer = Layer.unwrap(
-  Effect.promise(async () => {
-    const { InstanceBootstrap } = await import("./bootstrap")
-    return InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))
-  }),
-)
+// Bridge for Promise/ALS callers that cannot yet yield InstanceStore.Service.
+// Delete this module once those callers are migrated to Effect boundaries that
+// provide InstanceStore directly.
 
-const runtime = makeRuntime(InstanceStore.Service, layer)
-
-export const load = (input: LoadInput) => runtime.runPromise((store) => store.load(input))
-export const disposeInstance = (ctx: InstanceContext) => runtime.runPromise((store) => store.dispose(ctx))
-export const disposeAllInstances = () => runtime.runPromise((store) => store.disposeAll())
-export const reloadInstance = (input: LoadInput) => runtime.runPromise((store) => store.reload(input))
+export const load = (input: LoadInput) => AppRuntime.runPromise(InstanceStore.Service.use((store) => store.load(input)))
+export const disposeInstance = (ctx: InstanceContext) =>
+  AppRuntime.runPromise(InstanceStore.Service.use((store) => store.dispose(ctx)))
+export const disposeAllInstances = () => AppRuntime.runPromise(InstanceStore.Service.use((store) => store.disposeAll()))
+export const reloadInstance = (input: LoadInput) =>
+  AppRuntime.runPromise(InstanceStore.Service.use((store) => store.reload(input)))
 
 export * as InstanceRuntime from "./instance-runtime"

+ 9 - 18
packages/opencode/src/project/instance-store.ts

@@ -8,26 +8,18 @@ import { type InstanceContext } from "./instance-context"
 import { InstanceBootstrap } from "./bootstrap-service"
 import * as Project from "./project"
 
-export interface LoadInput<R = never> {
+export interface LoadInput {
   directory: string
-  /**
-   * Additional setup to run after the default InstanceBootstrap.
-   * Mainly used by tests for env-var setup or file writes that need the instance ALS context.
-   */
-  init?: Effect.Effect<void, never, R>
   worktree?: string
   project?: Project.Info
 }
 
 export interface Interface {
-  readonly load: <R = never>(input: LoadInput<R>) => Effect.Effect<InstanceContext, never, R>
-  readonly reload: <R = never>(input: LoadInput<R>) => Effect.Effect<InstanceContext, never, R>
+  readonly load: (input: LoadInput) => Effect.Effect<InstanceContext>
+  readonly reload: (input: LoadInput) => Effect.Effect<InstanceContext>
   readonly dispose: (ctx: InstanceContext) => Effect.Effect<void>
   readonly disposeAll: () => Effect.Effect<void>
-  readonly provide: <A, E, R, R2 = never>(
-    input: LoadInput<R2>,
-    effect: Effect.Effect<A, E, R>,
-  ) => Effect.Effect<A, E, R | R2>
+  readonly provide: <A, E, R>(input: LoadInput, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
 }
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/InstanceStore") {}
@@ -44,7 +36,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
     const scope = yield* Scope.Scope
     const cache = new Map<string, Entry>()
 
-    const boot = <R>(input: LoadInput<R> & { directory: string }) =>
+    const boot = (input: LoadInput & { directory: string }) =>
       Effect.gen(function* () {
         const ctx: InstanceContext =
           input.project && input.worktree
@@ -61,7 +53,6 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
                 })),
               )
         yield* bootstrap.run.pipe(Effect.provideService(InstanceRef, ctx))
-        if (input.init) yield* input.init.pipe(Effect.provideService(InstanceRef, ctx))
         return ctx
       }).pipe(Effect.withSpan("InstanceStore.boot"))
 
@@ -72,7 +63,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
         return true
       })
 
-    const completeLoad = <R>(directory: string, input: LoadInput<R>, entry: Entry) =>
+    const completeLoad = (directory: string, input: LoadInput, entry: Entry) =>
       Effect.gen(function* () {
         const exit = yield* Effect.exit(boot({ ...input, directory }))
         if (Exit.isFailure(exit)) yield* removeEntry(directory, entry)
@@ -108,7 +99,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
       return true
     })
 
-    const load = <R>(input: LoadInput<R>): Effect.Effect<InstanceContext, never, R> => {
+    const load = (input: LoadInput): Effect.Effect<InstanceContext> => {
       const directory = AppFileSystem.resolve(input.directory)
       return Effect.uninterruptibleMask((restore) =>
         Effect.gen(function* () {
@@ -126,7 +117,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
       ).pipe(Effect.withSpan("InstanceStore.load"))
     }
 
-    const reload = <R>(input: LoadInput<R>): Effect.Effect<InstanceContext, never, R> => {
+    const reload = (input: LoadInput): Effect.Effect<InstanceContext> => {
       const directory = AppFileSystem.resolve(input.directory)
       return Effect.uninterruptibleMask((restore) =>
         Effect.gen(function* () {
@@ -180,7 +171,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
       return yield* cachedDisposeAll
     })
 
-    const provide = <A, E, R, R2>(input: LoadInput<R2>, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R | R2> =>
+    const provide = <A, E, R>(input: LoadInput, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
       load(input).pipe(Effect.flatMap((ctx) => effect.pipe(Effect.provideService(InstanceRef, ctx))))
 
     yield* Effect.addFinalizer(() => disposeAll().pipe(Effect.ignore))

+ 0 - 7
packages/opencode/src/project/instance.ts

@@ -1,15 +1,8 @@
-import { Effect } from "effect"
 import { context, type InstanceContext } from "./instance-context"
-import { InstanceRuntime } from "./instance-runtime"
 
 export type { InstanceContext } from "./instance-context"
-export type { LoadInput } from "./instance-store"
 
 export const Instance = {
-  async provide<R>(input: { directory: string; init?: Effect.Effect<void>; fn: () => R }): Promise<R> {
-    const ctx = await InstanceRuntime.load({ directory: input.directory, init: input.init })
-    return context.provide(ctx, async () => input.fn())
-  },
   get current() {
     return context.use()
   },

+ 10 - 0
packages/opencode/src/project/with-instance.ts

@@ -0,0 +1,10 @@
+import { AppRuntime } from "@/effect/app-runtime"
+import { context } from "./instance-context"
+import { InstanceStore } from "./instance-store"
+
+export async function provide<R>(input: { directory: string; fn: () => R }): Promise<R> {
+  const ctx = await AppRuntime.runPromise(InstanceStore.Service.use((store) => store.load({ directory: input.directory })))
+  return context.provide(ctx, () => input.fn())
+}
+
+export * as WithInstance from "./with-instance"

+ 27 - 10
packages/opencode/src/server/routes/instance/config.ts

@@ -6,7 +6,11 @@ import { InstanceStore } from "@/project/instance-store"
 import { Provider } from "@/provider/provider"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
-import { jsonRequest } from "./trace"
+import { jsonRequest, runRequest } from "./trace"
+import { Effect } from "effect"
+import * as Log from "@opencode-ai/core/util/log"
+
+const log = Log.create({ service: "server.config" })
 
 export const ConfigRoutes = lazy(() =>
   new Hono()
@@ -52,15 +56,28 @@ export const ConfigRoutes = lazy(() =>
         },
       }),
       validator("json", Config.Info.zod),
-      async (c) =>
-        jsonRequest("ConfigRoutes.update", c, function* () {
-          const config = c.req.valid("json")
-          const cfg = yield* Config.Service
-          const store = yield* InstanceStore.Service
-          yield* cfg.update(config)
-          yield* store.dispose(yield* InstanceState.context)
-          return config
-        }),
+      async (c) => {
+        const result = await runRequest(
+          "ConfigRoutes.update",
+          c,
+          Effect.gen(function* () {
+            const config = c.req.valid("json")
+            const cfg = yield* Config.Service
+            yield* cfg.update(config)
+            return { config, ctx: yield* InstanceState.context }
+          }),
+        )
+        const response = c.json(result.config)
+        void runRequest(
+          "ConfigRoutes.update.dispose",
+          c,
+          InstanceStore.Service.use((store) => store.dispose(result.ctx)).pipe(
+            Effect.uninterruptible,
+            Effect.catchCause((cause) => Effect.sync(() => log.warn("instance disposal failed", { cause }))),
+          ),
+        )
+        return response
+      },
     )
     .get(
       "/providers",

+ 3 - 3
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -18,7 +18,7 @@ import { LSP } from "@/lsp/lsp"
 import { MCP } from "@/mcp"
 import { Permission } from "@/permission"
 import { Installation } from "@/installation"
-import { InstanceRuntime } from "@/project/instance-runtime"
+import { InstanceLayer } from "@/project/instance-layer"
 import { Plugin } from "@/plugin"
 import { Project } from "@/project/project"
 import { ProviderAuth } from "@/provider/auth"
@@ -152,7 +152,6 @@ export function createRoutes(corsOptions?: CorsOptions) {
       Format.defaultLayer,
       LSP.defaultLayer,
       Installation.defaultLayer,
-      InstanceRuntime.layer,
       MCP.defaultLayer,
       ModelsDev.defaultLayer,
       Permission.defaultLayer,
@@ -179,12 +178,13 @@ export function createRoutes(corsOptions?: CorsOptions) {
       ToolRegistry.defaultLayer,
       Vcs.defaultLayer,
       Workspace.defaultLayer,
-      Worktree.defaultLayer,
+      Worktree.appLayer,
       Bus.layer,
       AppFileSystem.defaultLayer,
       FetchHttpClient.layer,
       HttpServer.layerServices,
     ]),
+    Layer.provideMerge(InstanceLayer.layer),
     Layer.provideMerge(Observability.layer),
   )
 }

+ 2 - 2
packages/opencode/src/server/routes/instance/middleware.ts

@@ -1,5 +1,5 @@
 import type { MiddlewareHandler } from "hono"
-import { Instance } from "@/project/instance"
+import { WithInstance } from "@/project/with-instance"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { WorkspaceContext } from "@/control-plane/workspace-context"
 import { WorkspaceID } from "@/control-plane/schema"
@@ -20,7 +20,7 @@ export function InstanceMiddleware(workspaceID?: WorkspaceID): MiddlewareHandler
     return WorkspaceContext.provide({
       workspaceID,
       async fn() {
-        return Instance.provide({
+        return WithInstance.provide({
           directory,
           async fn() {
             return next()

+ 2 - 2
packages/opencode/src/server/workspace.ts

@@ -6,7 +6,7 @@ import { WorkspaceContext } from "@/control-plane/workspace-context"
 import { Workspace } from "@/control-plane/workspace"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { AppRuntime } from "@/effect/app-runtime"
-import { Instance } from "@/project/instance"
+import { WithInstance } from "@/project/with-instance"
 import { Session } from "@/session/session"
 import { SessionID } from "@/session/schema"
 import { Effect } from "effect"
@@ -97,7 +97,7 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware
       return WorkspaceContext.provide({
         workspaceID: WorkspaceID.make(workspaceID),
         fn: () =>
-          Instance.provide({
+          WithInstance.provide({
             directory: target.directory,
             async fn() {
               return next()

+ 17 - 10
packages/opencode/src/worktree/index.ts

@@ -1,7 +1,8 @@
 import z from "zod"
 import { NamedError } from "@opencode-ai/core/util/error"
 import { Global } from "@opencode-ai/core/global"
-import { Instance } from "../project/instance"
+import { InstanceLayer } from "@/project/instance-layer"
+import { InstanceStore } from "@/project/instance-store"
 import { Project } from "@/project/project"
 import { Database } from "@/storage/db"
 import { eq } from "drizzle-orm"
@@ -159,7 +160,12 @@ type GitResult = { code: number; text: string; stderr: string }
 export const layer: Layer.Layer<
   Service,
   never,
-  AppFileSystem.Service | Path.Path | ChildProcessSpawner.ChildProcessSpawner | Git.Service | Project.Service
+  | AppFileSystem.Service
+  | Path.Path
+  | ChildProcessSpawner.ChildProcessSpawner
+  | Git.Service
+  | Project.Service
+  | InstanceStore.Service
 > = Layer.effect(
   Service,
   Effect.gen(function* () {
@@ -169,6 +175,7 @@ export const layer: Layer.Layer<
     const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
     const gitSvc = yield* Git.Service
     const project = yield* Project.Service
+    const store = yield* InstanceStore.Service
 
     const git = Effect.fnUntraced(
       function* (args: string[], opts?: { cwd?: string }) {
@@ -251,13 +258,10 @@ export const layer: Layer.Layer<
         return
       }
 
-      const booted = yield* Effect.promise(() =>
-        Instance.provide({
-          directory: info.directory,
-          fn: () => undefined,
-        })
-          .then(() => true)
-          .catch((error) => {
+      const booted = yield* store.load({ directory: info.directory }).pipe(
+        Effect.as(true),
+        Effect.catch((error) =>
+          Effect.sync(() => {
             const message = errorMessage(error)
             log.error("worktree bootstrap failed", { directory: info.directory, message })
             GlobalBus.emit("event", {
@@ -268,6 +272,7 @@ export const layer: Layer.Layer<
             })
             return false
           }),
+        ),
       )
       if (!booted) return
 
@@ -579,7 +584,7 @@ export const layer: Layer.Layer<
   }),
 )
 
-export const defaultLayer = layer.pipe(
+export const appLayer = layer.pipe(
   Layer.provide(Git.defaultLayer),
   Layer.provide(CrossSpawnSpawner.defaultLayer),
   Layer.provide(Project.defaultLayer),
@@ -587,4 +592,6 @@ export const defaultLayer = layer.pipe(
   Layer.provide(NodePath.layer),
 )
 
+export const defaultLayer = appLayer.pipe(Layer.provide(InstanceLayer.layer))
+
 export * as Worktree from "."

+ 11 - 10
packages/opencode/test/acp/event-subscription.test.ts

@@ -3,6 +3,7 @@ import { ACP } from "../../src/acp/agent"
 import type { AgentSideConnection } from "@agentclientprotocol/sdk"
 import type { Event, EventMessagePartUpdated, ToolStatePending, ToolStateRunning } from "@opencode-ai/sdk/v2"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { tmpdir } from "../fixture/fixture"
 
 type SessionUpdateParams = Parameters<AgentSideConnection["sessionUpdate"]>[0]
@@ -262,7 +263,7 @@ function createFakeAgent() {
 describe("acp.agent event subscription", () => {
   test("routes message.part.delta by the event sessionID (no cross-session pollution)", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, updates, stop } = createFakeAgent()
@@ -297,7 +298,7 @@ describe("acp.agent event subscription", () => {
 
   test("does not emit user_message_chunk for live prompt parts", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()
@@ -337,7 +338,7 @@ describe("acp.agent event subscription", () => {
 
   test("keeps concurrent sessions isolated when message.part.delta events are interleaved", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, chunks, stop } = createFakeAgent()
@@ -389,7 +390,7 @@ describe("acp.agent event subscription", () => {
 
   test("does not create additional event subscriptions on repeated loadSession()", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, calls, stop } = createFakeAgent()
@@ -411,7 +412,7 @@ describe("acp.agent event subscription", () => {
 
   test("permission.asked events are handled and replied", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const permissionReplies: string[] = []
@@ -450,7 +451,7 @@ describe("acp.agent event subscription", () => {
 
   test("permission prompt on session A does not block message updates for session B", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const permissionReplies: string[] = []
@@ -537,7 +538,7 @@ describe("acp.agent event subscription", () => {
 
   test("streams running bash output snapshots and de-dupes identical snapshots", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()
@@ -571,7 +572,7 @@ describe("acp.agent event subscription", () => {
 
   test("emits synthetic pending before first running update for any tool", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()
@@ -616,7 +617,7 @@ describe("acp.agent event subscription", () => {
 
   test("does not emit duplicate synthetic pending after replayed running tool", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop, sdk } = createFakeAgent()
@@ -675,7 +676,7 @@ describe("acp.agent event subscription", () => {
 
   test("clears bash snapshot marker on pending state", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()

+ 39 - 38
packages/opencode/test/agent/agent.test.ts

@@ -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

+ 2 - 1
packages/opencode/test/agent/plugin-agent-regression.test.ts

@@ -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()))

+ 2 - 1
packages/opencode/test/bus/bus-integration.test.ts

@@ -3,12 +3,13 @@ import { Schema } from "effect"
 import { Bus } from "../../src/bus"
 import { BusEvent } from "../../src/bus/bus-event"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { disposeAllInstances, tmpdir } from "../fixture/fixture"
 
 const TestEvent = BusEvent.define("test.integration", Schema.Struct({ value: Schema.Number }))
 
 function withInstance(directory: string, fn: () => Promise<void>) {
-  return Instance.provide({ directory, fn })
+  return WithInstance.provide({ directory, fn })
 }
 
 describe("Bus integration: acquireRelease subscriber pattern", () => {

+ 2 - 1
packages/opencode/test/bus/bus.test.ts

@@ -3,6 +3,7 @@ import { Schema } from "effect"
 import { Bus } from "../../src/bus"
 import { BusEvent } from "../../src/bus/bus-event"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { disposeAllInstances, tmpdir } from "../fixture/fixture"
 
 const TestEvent = {
@@ -11,7 +12,7 @@ const TestEvent = {
 }
 
 function withInstance(directory: string, fn: () => Promise<void>) {
-  return Instance.provide({ directory, fn })
+  return WithInstance.provide({ directory, fn })
 }
 
 describe("Bus", () => {

+ 53 - 52
packages/opencode/test/config/config.test.ts

@@ -7,6 +7,7 @@ import { ConfigParse } from "../../src/config/parse"
 import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
 
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Auth } from "../../src/auth"
 import { Account } from "../../src/account/account"
 import { AccessToken, AccountID, OrgID } from "../../src/account/schema"
@@ -113,7 +114,7 @@ async function check(map: (dir: string) => string) {
       $schema: "https://opencode.ai/config.json",
       snapshot: false,
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: map(tmp.path),
       fn: async () => {
         const cfg = await load()
@@ -131,7 +132,7 @@ async function check(map: (dir: string) => string) {
 
 test("loads config with defaults when no files exist", async () => {
   await using tmp = await tmpdir()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -150,7 +151,7 @@ test("loads JSON config file", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -169,7 +170,7 @@ test("loads shell config field", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -191,7 +192,7 @@ test("updates config and preserves empty shell sentinel", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       await save({ shell: "" })
@@ -269,7 +270,7 @@ test("loads formatter boolean config", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -287,7 +288,7 @@ test("loads lsp boolean config", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -324,7 +325,7 @@ test("ignores legacy tui keys in opencode config", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -349,7 +350,7 @@ test("loads JSONC config file", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -377,7 +378,7 @@ test("jsonc overrides json in the same directory", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -400,7 +401,7 @@ test("handles environment variable substitution", async () => {
         })
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()
@@ -432,7 +433,7 @@ test("preserves env variables when adding $schema to config", async () => {
         )
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()
@@ -529,7 +530,7 @@ test("handles file inclusion substitution", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -548,7 +549,7 @@ test("handles file inclusion with replacement tokens", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -604,7 +605,7 @@ test("handles agent configuration", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -635,7 +636,7 @@ test("treats agent variant as model-scoped setting (not provider option)", async
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -665,7 +666,7 @@ test("handles command configuration", async () => {
       })
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -690,7 +691,7 @@ test("migrates autoshare to share field", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -717,7 +718,7 @@ test("migrates mode field to agent field", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -749,7 +750,7 @@ Test agent prompt`,
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -782,7 +783,7 @@ Ordered permissions`,
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -820,7 +821,7 @@ Nested agent prompt`,
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -869,7 +870,7 @@ Nested command template`,
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -914,7 +915,7 @@ Nested command template`,
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -934,7 +935,7 @@ Nested command template`,
 
 test("updates config and writes to file", async () => {
   await using tmp = await tmpdir()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const newConfig = { model: "updated/model" }
@@ -948,7 +949,7 @@ test("updates config and writes to file", async () => {
 
 test("gets config directories", async () => {
   await using tmp = await tmpdir()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const dirs = await listDirs()
@@ -978,7 +979,7 @@ test("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR", as
   process.env.OPENCODE_CONFIG_DIR = tmp.extra
 
   try {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await load()
@@ -1013,7 +1014,7 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => {
   )
 
   try {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(testLayer)))
@@ -1146,7 +1147,7 @@ Helper subagent prompt`,
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1185,7 +1186,7 @@ test("merges instructions arrays from global and local configs", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: path.join(tmp.path, "project"),
     fn: async () => {
       const config = await load()
@@ -1224,7 +1225,7 @@ test("deduplicates duplicate instructions from global and local configs", async
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: path.join(tmp.path, "project"),
     fn: async () => {
       const config = await load()
@@ -1359,7 +1360,7 @@ test("migrates legacy tools config to permissions - allow", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1390,7 +1391,7 @@ test("migrates legacy tools config to permissions - deny", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1420,7 +1421,7 @@ test("migrates legacy write tool to edit permission", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1452,7 +1453,7 @@ test("managed settings override user settings", async () => {
     share: "disabled",
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1480,7 +1481,7 @@ test("managed settings override project settings", async () => {
     disabled_providers: ["openai"],
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1500,7 +1501,7 @@ test("missing managed settings file is not an error", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1527,7 +1528,7 @@ test("migrates legacy edit tool to edit permission", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1556,7 +1557,7 @@ test("migrates legacy patch tool to edit permission", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1588,7 +1589,7 @@ test("migrates mixed legacy tools config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1623,7 +1624,7 @@ test("merges legacy tools with existing permission config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1660,7 +1661,7 @@ test("permission config preserves user key order", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1743,7 +1744,7 @@ test("project config can override MCP server enabled status", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1799,7 +1800,7 @@ test("MCP config deep merges preserving base config properties", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -1850,7 +1851,7 @@ test("local .opencode config can override MCP from project config", async () =>
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const config = await load()
@@ -2139,7 +2140,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
           )
         },
       })
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const config = await load()
@@ -2170,7 +2171,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
           await Filesystem.write(path.join(opencodeDir, "test-cmd.md"), "# Test Command\nThis is a test command.")
         },
       })
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const directories = await listDirs()
@@ -2194,7 +2195,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
 
     try {
       await using tmp = await tmpdir()
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           // Should still get default config (from global or defaults)
@@ -2236,7 +2237,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
         },
       })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           // The relative instruction should be skipped without error
@@ -2296,7 +2297,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
       process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = "true"
       process.env["OPENCODE_CONFIG_DIR"] = configDirTmp.path
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: projectTmp.path,
         fn: async () => {
           const config = await load()
@@ -2331,7 +2332,7 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => {
 
     try {
       await using tmp = await tmpdir()
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const config = await load()
@@ -2365,7 +2366,7 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => {
           })
         },
       })
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const config = await load()

+ 2 - 1
packages/opencode/test/control-plane/workspace.test.ts

@@ -14,6 +14,7 @@ import { Database } from "@/storage/db"
 import { ProjectID } from "@/project/schema"
 import { ProjectTable } from "@/project/project.sql"
 import { Instance } from "@/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Session as SessionNs } from "@/session/session"
 import { SessionID, MessageID, PartID } from "@/session/schema"
 import { SessionTable } from "@/session/session.sql"
@@ -101,7 +102,7 @@ afterEach(async () => {
 
 async function withInstance<T>(fn: (dir: string) => T | Promise<T>) {
   await using tmp = await tmpdir({ git: true })
-  return Instance.provide({
+  return WithInstance.provide({
     directory: tmp.path,
     fn: () => fn(tmp.path),
   })

+ 3 - 2
packages/opencode/test/file/fsmonitor.test.ts

@@ -5,6 +5,7 @@ import fs from "fs/promises"
 import path from "path"
 import { File } from "../../src/file"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { provideInstance, tmpdir } from "../fixture/fixture"
 
 const run = <A, E>(eff: Effect.Effect<A, E, File.Service>) =>
@@ -30,7 +31,7 @@ describe("file fsmonitor", () => {
     const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow()
     expect(before.exitCode).not.toBe(0)
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await status()
@@ -55,7 +56,7 @@ describe("file fsmonitor", () => {
     const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow()
     expect(before.exitCode).not.toBe(0)
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await read("tracked.txt")

+ 55 - 54
packages/opencode/test/file/index.test.ts

@@ -5,6 +5,7 @@ import path from "path"
 import fs from "fs/promises"
 import { File } from "../../src/file"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Filesystem } from "@/util/filesystem"
 import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture"
 
@@ -28,7 +29,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.txt")
       await fs.writeFile(filepath, "Hello World", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("test.txt")
@@ -41,7 +42,7 @@ describe("file/index Filesystem patterns", () => {
     test("reads with Filesystem.exists() check", async () => {
       await using tmp = await tmpdir()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           // Non-existent file should return empty content
@@ -57,7 +58,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.txt")
       await fs.writeFile(filepath, "  content with spaces  \n\n", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("test.txt")
@@ -71,7 +72,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "empty.txt")
       await fs.writeFile(filepath, "", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("empty.txt")
@@ -86,7 +87,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "multiline.txt")
       await fs.writeFile(filepath, "line1\nline2\nline3", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("multiline.txt")
@@ -103,7 +104,7 @@ describe("file/index Filesystem patterns", () => {
       const binaryContent = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
       await fs.writeFile(filepath, binaryContent)
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("image.png")
@@ -120,7 +121,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "binary.so")
       await fs.writeFile(filepath, Buffer.from([0x7f, 0x45, 0x4c, 0x46]), "binary")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("binary.so")
@@ -137,7 +138,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.json")
       await fs.writeFile(filepath, '{"key": "value"}', "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           expect(await Filesystem.mimeType(filepath)).toContain("application/json")
@@ -161,7 +162,7 @@ describe("file/index Filesystem patterns", () => {
         const filepath = path.join(tmp.path, `test.${ext}`)
         await fs.writeFile(filepath, Buffer.from([0x00, 0x00, 0x00, 0x00]), "binary")
 
-        await Instance.provide({
+        await WithInstance.provide({
           directory: tmp.path,
           fn: async () => {
             expect(await Filesystem.mimeType(filepath)).toContain(mime)
@@ -175,7 +176,7 @@ describe("file/index Filesystem patterns", () => {
     test("reads .gitignore via Filesystem.exists() and readText()", async () => {
       await using tmp = await tmpdir({ git: true })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const gitignorePath = path.join(tmp.path, ".gitignore")
@@ -193,7 +194,7 @@ describe("file/index Filesystem patterns", () => {
     test("reads .ignore file similarly", async () => {
       await using tmp = await tmpdir({ git: true })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const ignorePath = path.join(tmp.path, ".ignore")
@@ -208,7 +209,7 @@ describe("file/index Filesystem patterns", () => {
     test("handles missing .gitignore gracefully", async () => {
       await using tmp = await tmpdir({ git: true })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const gitignorePath = path.join(tmp.path, ".gitignore")
@@ -226,7 +227,7 @@ describe("file/index Filesystem patterns", () => {
     test("reads untracked files via Filesystem.readText()", async () => {
       await using tmp = await tmpdir({ git: true })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const untrackedPath = path.join(tmp.path, "untracked.txt")
@@ -247,7 +248,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "readonly.txt")
       await fs.writeFile(filepath, "content", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nonExistentPath = path.join(tmp.path, "does-not-exist.txt")
@@ -264,7 +265,7 @@ describe("file/index Filesystem patterns", () => {
     test("handles errors in Filesystem.readArrayBuffer()", async () => {
       await using tmp = await tmpdir()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nonExistentPath = path.join(tmp.path, "does-not-exist.bin")
@@ -279,7 +280,7 @@ describe("file/index Filesystem patterns", () => {
       const _filepath = path.join(tmp.path, "broken.png")
       // Don't create the file
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           // read() handles missing images gracefully
@@ -297,7 +298,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.ts")
       await fs.writeFile(filepath, "export const value = 1", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("test.ts")
@@ -312,7 +313,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.mts")
       await fs.writeFile(filepath, "export const value = 1", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("test.mts")
@@ -327,7 +328,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.sh")
       await fs.writeFile(filepath, "#!/usr/bin/env bash\necho hello", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("test.sh")
@@ -342,7 +343,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "Dockerfile")
       await fs.writeFile(filepath, "FROM alpine:3.20", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("Dockerfile")
@@ -357,7 +358,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.txt")
       await fs.writeFile(filepath, "simple text", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("test.txt")
@@ -372,7 +373,7 @@ describe("file/index Filesystem patterns", () => {
       const filepath = path.join(tmp.path, "test.jpg")
       await fs.writeFile(filepath, Buffer.from([0xff, 0xd8, 0xff, 0xe0]), "binary")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("test.jpg")
@@ -387,7 +388,7 @@ describe("file/index Filesystem patterns", () => {
     test("throws for paths outside project directory", async () => {
       await using tmp = await tmpdir()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await expect(read("../outside.txt")).rejects.toThrow("Access denied")
@@ -398,7 +399,7 @@ describe("file/index Filesystem patterns", () => {
     test("throws for paths outside project directory", async () => {
       await using tmp = await tmpdir()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await expect(read("../outside.txt")).rejects.toThrow("Access denied")
@@ -416,7 +417,7 @@ describe("file/index Filesystem patterns", () => {
       await $`git commit -m "add file"`.cwd(tmp.path).quiet()
       await fs.writeFile(filepath, "modified\nextra line\n", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await status()
@@ -433,7 +434,7 @@ describe("file/index Filesystem patterns", () => {
       await using tmp = await tmpdir({ git: true })
       await fs.writeFile(path.join(tmp.path, "new.txt"), "line1\nline2\nline3\n", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await status()
@@ -454,7 +455,7 @@ describe("file/index Filesystem patterns", () => {
       await $`git commit -m "add file"`.cwd(tmp.path).quiet()
       await fs.rm(filepath)
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await status()
@@ -477,7 +478,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.rm(path.join(tmp.path, "remove.txt"))
       await fs.writeFile(path.join(tmp.path, "brand-new.txt"), "hello\n", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await status()
@@ -491,7 +492,7 @@ describe("file/index Filesystem patterns", () => {
     test("returns empty for non-git project", async () => {
       await using tmp = await tmpdir()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await status()
@@ -503,7 +504,7 @@ describe("file/index Filesystem patterns", () => {
     test("returns empty for clean repo", async () => {
       await using tmp = await tmpdir({ git: true })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await status()
@@ -526,7 +527,7 @@ describe("file/index Filesystem patterns", () => {
       for (let i = 0; i < 512; i++) modified[i] = i % 256
       await fs.writeFile(filepath, modified)
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await status()
@@ -547,7 +548,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(path.join(tmp.path, "file.txt"), "content", "utf-8")
       await fs.writeFile(path.join(tmp.path, "subdir", "nested.txt"), "nested", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nodes = await list()
@@ -571,7 +572,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(path.join(tmp.path, "zz.txt"), "", "utf-8")
       await fs.writeFile(path.join(tmp.path, "aa.txt"), "", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nodes = await list()
@@ -596,7 +597,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(path.join(tmp.path, ".DS_Store"), "", "utf-8")
       await fs.writeFile(path.join(tmp.path, "visible.txt"), "", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nodes = await list()
@@ -615,7 +616,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(path.join(tmp.path, "main.ts"), "code", "utf-8")
       await fs.mkdir(path.join(tmp.path, "build"))
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nodes = await list()
@@ -635,7 +636,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(path.join(tmp.path, "sub", "a.txt"), "", "utf-8")
       await fs.writeFile(path.join(tmp.path, "sub", "b.txt"), "", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nodes = await list("sub")
@@ -650,7 +651,7 @@ describe("file/index Filesystem patterns", () => {
     test("throws for paths outside project directory", async () => {
       await using tmp = await tmpdir({ git: true })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await expect(list("../outside")).rejects.toThrow("Access denied")
@@ -662,7 +663,7 @@ describe("file/index Filesystem patterns", () => {
       await using tmp = await tmpdir()
       await fs.writeFile(path.join(tmp.path, "file.txt"), "hi", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const nodes = await list()
@@ -692,7 +693,7 @@ describe("file/index Filesystem patterns", () => {
     test("empty query returns files", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -706,7 +707,7 @@ describe("file/index Filesystem patterns", () => {
     test("search works before explicit init", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await search({ query: "main", type: "file" })
@@ -718,7 +719,7 @@ describe("file/index Filesystem patterns", () => {
     test("empty query returns dirs sorted with hidden last", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -738,7 +739,7 @@ describe("file/index Filesystem patterns", () => {
     test("fuzzy matches file names", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -752,7 +753,7 @@ describe("file/index Filesystem patterns", () => {
     test("type filter returns only files", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -769,7 +770,7 @@ describe("file/index Filesystem patterns", () => {
     test("type filter returns only directories", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -786,7 +787,7 @@ describe("file/index Filesystem patterns", () => {
     test("respects limit", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -800,7 +801,7 @@ describe("file/index Filesystem patterns", () => {
     test("query starting with dot prefers hidden files", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -815,7 +816,7 @@ describe("file/index Filesystem patterns", () => {
     test("search refreshes after init when files change", async () => {
       await using tmp = await setupSearchableRepo()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -839,7 +840,7 @@ describe("file/index Filesystem patterns", () => {
       await $`git commit -m "add file"`.cwd(tmp.path).quiet()
       await fs.writeFile(filepath, "modified content\n", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("file.txt")
@@ -863,7 +864,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(filepath, "after\n", "utf-8")
       await $`git add .`.cwd(tmp.path).quiet()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("staged.txt")
@@ -880,7 +881,7 @@ describe("file/index Filesystem patterns", () => {
       await $`git add .`.cwd(tmp.path).quiet()
       await $`git commit -m "add file"`.cwd(tmp.path).quiet()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const result = await read("clean.txt")
@@ -900,7 +901,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(path.join(one.path, "a.ts"), "one", "utf-8")
       await fs.writeFile(path.join(two.path, "b.ts"), "two", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: one.path,
         fn: async () => {
           await init()
@@ -911,7 +912,7 @@ describe("file/index Filesystem patterns", () => {
         },
       })
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: two.path,
         fn: async () => {
           await init()
@@ -927,7 +928,7 @@ describe("file/index Filesystem patterns", () => {
       await using tmp = await tmpdir({ git: true })
       await fs.writeFile(path.join(tmp.path, "before.ts"), "before", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()
@@ -941,7 +942,7 @@ describe("file/index Filesystem patterns", () => {
       await fs.writeFile(path.join(tmp.path, "after.ts"), "after", "utf-8")
       await fs.rm(path.join(tmp.path, "before.ts"))
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await init()

+ 12 - 11
packages/opencode/test/file/path-traversal.test.ts

@@ -5,6 +5,7 @@ import fs from "fs/promises"
 import { Filesystem } from "@/util/filesystem"
 import { File } from "../../src/file"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { containsPath } from "../../src/project/instance-context"
 import { provideInstance, tmpdir } from "../fixture/fixture"
 
@@ -55,7 +56,7 @@ describe("File.read path traversal protection", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await expect(read("../../../etc/passwd")).rejects.toThrow("Access denied: path escapes project directory")
@@ -66,7 +67,7 @@ describe("File.read path traversal protection", () => {
   test("rejects deeply nested traversal", async () => {
     await using tmp = await tmpdir()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await expect(read("src/nested/../../../../../../../etc/passwd")).rejects.toThrow(
@@ -83,7 +84,7 @@ describe("File.read path traversal protection", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const result = await read("valid.txt")
@@ -97,7 +98,7 @@ describe("File.list path traversal protection", () => {
   test("rejects ../ traversal attempting to list /etc", async () => {
     await using tmp = await tmpdir()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await expect(list("../../../etc")).rejects.toThrow("Access denied: path escapes project directory")
@@ -112,7 +113,7 @@ describe("File.list path traversal protection", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const result = await list("subdir")
@@ -126,7 +127,7 @@ describe("containsPath", () => {
   test("returns true for path inside directory", async () => {
     await using tmp = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: () => {
         expect(containsPath(path.join(tmp.path, "foo.txt"), Instance.current)).toBe(true)
@@ -140,7 +141,7 @@ describe("containsPath", () => {
     const subdir = path.join(tmp.path, "packages", "lib")
     await fs.mkdir(subdir, { recursive: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: subdir,
       fn: () => {
         // .opencode at worktree root, but we're running from packages/lib
@@ -156,7 +157,7 @@ describe("containsPath", () => {
   test("returns false for path outside both directory and worktree", async () => {
     await using tmp = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: () => {
         expect(containsPath("/etc/passwd", Instance.current)).toBe(false)
@@ -168,7 +169,7 @@ describe("containsPath", () => {
   test("returns false for path with .. escaping worktree", async () => {
     await using tmp = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: () => {
         expect(containsPath(path.join(tmp.path, "..", "escape.txt"), Instance.current)).toBe(false)
@@ -179,7 +180,7 @@ describe("containsPath", () => {
   test("handles directory === worktree (running from repo root)", async () => {
     await using tmp = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: () => {
         expect(Instance.directory).toBe(Instance.worktree)
@@ -192,7 +193,7 @@ describe("containsPath", () => {
   test("non-git project does not allow arbitrary paths via worktree='/'", async () => {
     await using tmp = await tmpdir() // no git: true
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: () => {
         // worktree is "/" for non-git projects, but containsPath should NOT allow all paths

+ 3 - 2
packages/opencode/test/file/watcher.test.ts

@@ -9,6 +9,7 @@ import { Config } from "@/config/config"
 import { FileWatcher } from "../../src/file/watcher"
 import { Git } from "../../src/git"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 
 // Native @parcel/watcher bindings aren't reliably available in CI (missing on Linux, flaky on Windows)
 const describeWatcher = FileWatcher.hasNativeBinding() && !process.env.CI ? describe : describe.skip
@@ -28,7 +29,7 @@ type WatcherEvent = { file: string; event: "add" | "change" | "unlink" }
 
 /** Run `body` with a live FileWatcher service. */
 function withWatcher<E>(directory: string, body: Effect.Effect<void, E>) {
-  return Instance.provide({
+  return WithInstance.provide({
     directory,
     fn: async () => {
       const layer: Layer.Layer<FileWatcher.Service, never, never> = FileWatcher.layer.pipe(
@@ -193,7 +194,7 @@ describeWatcher("FileWatcher", () => {
     await withWatcher(tmp.path, Effect.void)
 
     // Now write a file — no watcher should be listening
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: () =>
         Effect.runPromise(

+ 2 - 1
packages/opencode/test/fixture/fixture.ts

@@ -25,8 +25,9 @@ const runTestInstanceStore = <A>(fn: (store: InstanceStore.Interface) => Effect.
   testInstanceRuntime.runPromise(InstanceStore.Service.use(fn))
 
 export async function provideTestInstance<R>(input: { directory: string; init?: Effect.Effect<void>; fn: () => R }) {
-  const ctx = await runTestInstanceStore((store) => store.load({ directory: input.directory, init: input.init }))
+  const ctx = await runTestInstanceStore((store) => store.load({ directory: input.directory }))
   try {
+    if (input.init) await testInstanceRuntime.runPromise(input.init.pipe(Effect.provideService(InstanceRef, ctx)))
     return await Instance.restore(ctx, () => input.fn())
   } finally {
     await runTestInstanceStore((store) => store.dispose(ctx))

+ 13 - 12
packages/opencode/test/lsp/client.test.ts

@@ -5,6 +5,7 @@ import { tmpdir } from "../fixture/fixture"
 import { LSPClient } from "@/lsp/client"
 import * as LSPServer from "@/lsp/server"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import * as Log from "@opencode-ai/core/util/log"
 
 function spawnFakeServer() {
@@ -25,7 +26,7 @@ describe("LSPClient interop", () => {
   test("handles workspace/workspaceFolders request", async () => {
     const handle = spawnFakeServer() as any
 
-    const client = await Instance.provide({
+    const client = await WithInstance.provide({
       directory: process.cwd(),
       fn: () =>
         LSPClient.create({
@@ -48,7 +49,7 @@ describe("LSPClient interop", () => {
   test("handles client/registerCapability request", async () => {
     const handle = spawnFakeServer() as any
 
-    const client = await Instance.provide({
+    const client = await WithInstance.provide({
       directory: process.cwd(),
       fn: () =>
         LSPClient.create({
@@ -71,7 +72,7 @@ describe("LSPClient interop", () => {
   test("handles client/unregisterCapability request", async () => {
     const handle = spawnFakeServer() as any
 
-    const client = await Instance.provide({
+    const client = await WithInstance.provide({
       directory: process.cwd(),
       fn: () =>
         LSPClient.create({
@@ -94,7 +95,7 @@ describe("LSPClient interop", () => {
   test("initialize does not overclaim unsupported diagnostics capabilities", async () => {
     const handle = spawnFakeServer() as any
 
-    const client = await Instance.provide({
+    const client = await WithInstance.provide({
       directory: process.cwd(),
       fn: () =>
         LSPClient.create({
@@ -121,7 +122,7 @@ describe("LSPClient interop", () => {
       gamma: true,
     }
 
-    const client = await Instance.provide({
+    const client = await WithInstance.provide({
       directory: process.cwd(),
       fn: () =>
         LSPClient.create({
@@ -150,7 +151,7 @@ describe("LSPClient interop", () => {
     const file = path.join(tmp.path, "client.ts")
     await Bun.write(file, "first\n")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const client = await LSPClient.create({
@@ -193,7 +194,7 @@ describe("LSPClient interop", () => {
     const file = path.join(tmp.path, "client.ts")
     await Bun.write(file, "const x = 1\n")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const client = await LSPClient.create({
@@ -239,7 +240,7 @@ describe("LSPClient interop", () => {
     const file = path.join(tmp.path, "client.ts")
     await Bun.write(file, "const x = 1\n")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const client = await LSPClient.create({
@@ -286,7 +287,7 @@ describe("LSPClient interop", () => {
     const file = path.join(tmp.path, "client.cs")
     await Bun.write(file, "class C {}\n")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const client = await LSPClient.create({
@@ -334,7 +335,7 @@ describe("LSPClient interop", () => {
     const file = path.join(tmp.path, "client.cs")
     await Bun.write(file, "class C {}\n")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const client = await LSPClient.create({
@@ -387,7 +388,7 @@ describe("LSPClient interop", () => {
     await Bun.write(file, "class C {}\n")
     await Bun.write(related, "class D {}\n")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const client = await LSPClient.create({
@@ -451,7 +452,7 @@ describe("LSPClient interop", () => {
     const file = path.join(tmp.path, "client.cs")
     await Bun.write(file, "class C {}\n")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const client = await LSPClient.create({

+ 4 - 3
packages/opencode/test/mcp/headers.test.ts

@@ -48,6 +48,7 @@ beforeEach(() => {
 const { MCP } = await import("../../src/mcp/index")
 const { AppRuntime } = await import("../../src/effect/app-runtime")
 const { Instance } = await import("../../src/project/instance")
+const { WithInstance } = await import("../../src/project/with-instance")
 const { tmpdir } = await import("../fixture/fixture")
 const service = MCP.Service as unknown as Effect.Effect<MCPNS.Interface, never, never>
 
@@ -73,7 +74,7 @@ test("headers are passed to transports when oauth is enabled (default)", async (
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       // Trigger MCP initialization - it will fail to connect but we can check the transport options
@@ -112,7 +113,7 @@ test("headers are passed to transports when oauth is enabled (default)", async (
 test("headers are passed to transports when oauth is explicitly disabled", async () => {
   await using tmp = await tmpdir()
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       transportCalls.length = 0
@@ -150,7 +151,7 @@ test("headers are passed to transports when oauth is explicitly disabled", async
 test("no requestInit when headers are not provided", async () => {
   await using tmp = await tmpdir()
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       transportCalls.length = 0

+ 2 - 1
packages/opencode/test/mcp/lifecycle.test.ts

@@ -172,6 +172,7 @@ beforeEach(() => {
 // Import after mocks
 const { MCP } = await import("../../src/mcp/index")
 const { Instance } = await import("../../src/project/instance")
+const { WithInstance } = await import("../../src/project/with-instance")
 const { tmpdir } = await import("../fixture/fixture")
 
 // --- Helper ---
@@ -193,7 +194,7 @@ function withInstance(
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await Effect.runPromise(MCP.Service.use(fn).pipe(Effect.provide(MCP.defaultLayer)))

+ 5 - 4
packages/opencode/test/mcp/oauth-auto-connect.test.ts

@@ -112,6 +112,7 @@ beforeEach(() => {
 // Import modules after mocking
 const { MCP } = await import("../../src/mcp/index")
 const { Instance } = await import("../../src/project/instance")
+const { WithInstance } = await import("../../src/project/with-instance")
 const { tmpdir } = await import("../fixture/fixture")
 
 test("first connect to OAuth server shows needs_auth instead of failed", async () => {
@@ -132,7 +133,7 @@ test("first connect to OAuth server shows needs_auth instead of failed", async (
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const result = await Effect.runPromise(
@@ -162,7 +163,7 @@ test("state() generates a new state when none is saved", async () => {
 
   await using tmp = await tmpdir()
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const auth = await Effect.runPromise(
@@ -203,7 +204,7 @@ test("state() returns existing state when one is saved", async () => {
 
   await using tmp = await tmpdir()
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const auth = await Effect.runPromise(
@@ -252,7 +253,7 @@ test("authenticate() stores a connected client when auth completes without redir
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       await Effect.runPromise(

+ 4 - 3
packages/opencode/test/mcp/oauth-browser.test.ts

@@ -106,6 +106,7 @@ const { AppRuntime } = await import("../../src/effect/app-runtime")
 const { Bus } = await import("../../src/bus")
 const { McpOAuthCallback } = await import("../../src/mcp/oauth-callback")
 const { Instance } = await import("../../src/project/instance")
+const { WithInstance } = await import("../../src/project/with-instance")
 const { tmpdir } = await import("../fixture/fixture")
 const service = MCP.Service as unknown as Effect.Effect<MCPNS.Interface, never, never>
 
@@ -127,7 +128,7 @@ test("BrowserOpenFailed event is published when open() throws", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       openShouldFail = true
@@ -183,7 +184,7 @@ test("BrowserOpenFailed event is NOT published when open() succeeds", async () =
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       openShouldFail = false
@@ -237,7 +238,7 @@ test("open() is called with the authorization URL", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       openShouldFail = false

+ 7 - 6
packages/opencode/test/permission-task.test.ts

@@ -2,6 +2,7 @@ import { afterEach, describe, test, expect } from "bun:test"
 import { Permission } from "../src/permission"
 import { Config } from "@/config/config"
 import { Instance } from "../src/project/instance"
+import { WithInstance } from "../src/project/with-instance"
 import { disposeAllInstances, tmpdir } from "./fixture/fixture"
 import { AppRuntime } from "../src/effect/app-runtime"
 
@@ -158,7 +159,7 @@ describe("permission.task with real config files", () => {
         },
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()
@@ -183,7 +184,7 @@ describe("permission.task with real config files", () => {
         },
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()
@@ -208,7 +209,7 @@ describe("permission.task with real config files", () => {
         },
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()
@@ -235,7 +236,7 @@ describe("permission.task with real config files", () => {
         },
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()
@@ -273,7 +274,7 @@ describe("permission.task with real config files", () => {
         },
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()
@@ -304,7 +305,7 @@ describe("permission.task with real config files", () => {
         },
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const config = await load()

+ 2 - 1
packages/opencode/test/permission/next.test.ts

@@ -6,6 +6,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { Permission } from "../../src/permission"
 import { PermissionID } from "../../src/permission/schema"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { InstanceRuntime } from "../../src/project/instance-runtime"
 import {
   disposeAllInstances,
@@ -1006,7 +1007,7 @@ it.live("pending permission rejects on instance dispose", () =>
 
     expect(yield* waitForPending(1).pipe(run)).toHaveLength(1)
     yield* Effect.promise(() =>
-      Instance.provide({ directory: dir, fn: () => void InstanceRuntime.disposeInstance(Instance.current) }),
+      WithInstance.provide({ directory: dir, fn: () => void InstanceRuntime.disposeInstance(Instance.current) }),
     )
 
     const exit = yield* Fiber.await(fiber)

+ 2 - 1
packages/opencode/test/project/instance-bootstrap-regression.test.ts

@@ -5,6 +5,7 @@ import path from "node:path"
 import { pathToFileURL } from "node:url"
 import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { InstanceRuntime } from "../../src/project/instance-runtime"
 import { InstanceMiddleware } from "../../src/server/routes/instance/middleware"
 import { disposeAllInstances, tmpdir } from "../fixture/fixture"
@@ -50,7 +51,7 @@ async function bootstrapFixture() {
 test("Instance.provide runs InstanceBootstrap before fn (boundary invariant)", async () => {
   await using tmp = await bootstrapFixture()
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => "ok",
   })

+ 43 - 81
packages/opencode/test/project/instance.test.ts

@@ -5,17 +5,23 @@ import { InstanceRef } from "../../src/effect/instance-ref"
 import { registerDisposer } from "../../src/effect/instance-registry"
 import { InstanceBootstrap } from "../../src/project/bootstrap-service"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { InstanceStore } from "../../src/project/instance-store"
 import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 
-const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
+let bootstrapRun: Effect.Effect<void> = Effect.void
+const noopBootstrap = Layer.succeed(
+  InstanceBootstrap.Service,
+  InstanceBootstrap.Service.of({ run: Effect.suspend(() => bootstrapRun) }),
+)
 
 const it = testEffect(
   Layer.mergeAll(InstanceStore.defaultLayer, CrossSpawnSpawner.defaultLayer).pipe(Layer.provide(noopBootstrap)),
 )
 
 afterEach(async () => {
+  bootstrapRun = Effect.void
   await disposeAllInstances()
 })
 
@@ -32,18 +38,16 @@ describe("InstanceStore", () => {
     }),
   )
 
-  it.live("runs load init with InstanceRef provided", () =>
+  it.live("runs bootstrap with InstanceRef provided", () =>
     Effect.gen(function* () {
       const dir = yield* tmpdirScoped({ git: true })
       const store = yield* InstanceStore.Service
       let initializedDirectory: string | undefined
 
-      yield* store.load({
-        directory: dir,
-        init: Effect.gen(function* () {
-          initializedDirectory = (yield* InstanceRef)?.directory
-        }),
+      bootstrapRun = Effect.gen(function* () {
+        initializedDirectory = (yield* InstanceRef)?.directory
       })
+      yield* store.load({ directory: dir })
 
       expect(initializedDirectory).toBe(dir)
       expect(() => Instance.current).toThrow()
@@ -56,18 +60,11 @@ describe("InstanceStore", () => {
       const store = yield* InstanceStore.Service
       let initialized = 0
 
-      const first = yield* store.load({
-        directory: dir,
-        init: Effect.sync(() => {
-          initialized++
-        }),
-      })
-      const second = yield* store.load({
-        directory: dir,
-        init: Effect.sync(() => {
-          initialized++
-        }),
+      bootstrapRun = Effect.sync(() => {
+        initialized++
       })
+      const first = yield* store.load({ directory: dir })
+      const second = yield* store.load({ directory: dir })
 
       expect(second).toBe(first)
       expect(initialized).toBe(1)
@@ -82,27 +79,19 @@ describe("InstanceStore", () => {
       const release = Promise.withResolvers<void>()
       let initialized = 0
 
-      const first = yield* store
-        .load({
-          directory: dir,
-          init: Effect.promise(async () => {
-            initialized++
-            started.resolve()
-            await release.promise
-          }),
-        })
-        .pipe(Effect.forkScoped)
+      bootstrapRun = Effect.promise(async () => {
+        initialized++
+        started.resolve()
+        await release.promise
+      })
+      const first = yield* store.load({ directory: dir }).pipe(Effect.forkScoped)
 
       yield* Effect.promise(() => started.promise)
 
-      const second = yield* store
-        .load({
-          directory: dir,
-          init: Effect.sync(() => {
-            initialized++
-          }),
-        })
-        .pipe(Effect.forkScoped)
+      bootstrapRun = Effect.sync(() => {
+        initialized++
+      })
+      const second = yield* store.load({ directory: dir }).pipe(Effect.forkScoped)
 
       expect(initialized).toBe(1)
       release.resolve()
@@ -119,27 +108,21 @@ describe("InstanceStore", () => {
       const store = yield* InstanceStore.Service
       let attempts = 0
 
-      const failed = yield* store
-        .load({
-          directory: dir,
-          init: Effect.sync(() => {
-            attempts++
-            throw new Error("init failed")
-          }),
-        })
-        .pipe(
-          Effect.as(false),
-          Effect.catchCause(() => Effect.succeed(true)),
-        )
+      bootstrapRun = Effect.sync(() => {
+        attempts++
+        throw new Error("init failed")
+      })
+      const failed = yield* store.load({ directory: dir }).pipe(
+        Effect.as(false),
+        Effect.catchCause(() => Effect.succeed(true)),
+      )
 
       expect(failed).toBe(true)
 
-      const ctx = yield* store.load({
-        directory: dir,
-        init: Effect.sync(() => {
-          attempts++
-        }),
+      bootstrapRun = Effect.sync(() => {
+        attempts++
       })
+      const ctx = yield* store.load({ directory: dir })
 
       expect(ctx.directory).toBe(dir)
       expect(attempts).toBe(2)
@@ -173,15 +156,11 @@ describe("InstanceStore", () => {
       yield* Effect.addFinalizer(() => Effect.sync(off))
 
       const first = yield* store.load({ directory: dir })
-      const reload = yield* store
-        .reload({
-          directory: dir,
-          init: Effect.promise(async () => {
-            reloading.resolve()
-            await releaseReload.promise
-          }),
-        })
-        .pipe(Effect.forkScoped)
+      bootstrapRun = Effect.promise(async () => {
+        reloading.resolve()
+        await releaseReload.promise
+      })
+      const reload = yield* store.reload({ directory: dir }).pipe(Effect.forkScoped)
 
       yield* Effect.promise(() => reloading.promise)
       const staleDispose = yield* store.dispose(first).pipe(Effect.forkScoped)
@@ -242,12 +221,12 @@ describe("InstanceStore", () => {
     }),
   )
 
-  it.live("keeps Instance.provide as the legacy ALS wrapper", () =>
+  it.live("provides legacy Promise callers with instance ALS", () =>
     Effect.gen(function* () {
       const dir = yield* tmpdirScoped({ git: true })
 
       const directory = yield* Effect.promise(() =>
-        Instance.provide({
+        WithInstance.provide({
           directory: dir,
           fn: () => Instance.directory,
         }),
@@ -258,21 +237,4 @@ describe("InstanceStore", () => {
     }),
   )
 
-  it.live("does not install legacy ALS around Effect init", () =>
-    Effect.gen(function* () {
-      const dir = yield* tmpdirScoped()
-
-      const directory = yield* Effect.promise(() =>
-        Instance.provide({
-          directory: dir,
-          init: Effect.sync(() => {
-            expect(() => Instance.current).toThrow()
-          }),
-          fn: () => Instance.directory,
-        }),
-      )
-
-      expect(directory).toBe(dir)
-    }),
-  )
 })

+ 3 - 2
packages/opencode/test/project/vcs.test.ts

@@ -7,6 +7,7 @@ import { disposeAllInstances, tmpdir } from "../fixture/fixture"
 import { AppRuntime } from "../../src/effect/app-runtime"
 import { FileWatcher } from "../../src/file/watcher"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { GlobalBus } from "../../src/bus/global"
 import { Vcs } from "@/project/vcs"
 
@@ -18,7 +19,7 @@ const describeVcs = FileWatcher.hasNativeBinding() && !process.env.CI ? describe
 // ---------------------------------------------------------------------------
 
 async function withVcs(directory: string, body: () => Promise<void>) {
-  return Instance.provide({
+  return WithInstance.provide({
     directory,
     fn: async () => {
       await AppRuntime.runPromise(
@@ -36,7 +37,7 @@ async function withVcs(directory: string, body: () => Promise<void>) {
 }
 
 function withVcsOnly(directory: string, body: () => Promise<void>) {
-  return Instance.provide({
+  return WithInstance.provide({
     directory,
     fn: async () => {
       await AppRuntime.runPromise(

+ 3 - 2
packages/opencode/test/project/worktree.test.ts

@@ -5,6 +5,7 @@ import path from "path"
 import { Cause, Effect, Exit, Layer } from "effect"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { InstanceRuntime } from "../../src/project/instance-runtime"
 import { Worktree } from "../../src/worktree"
 import { disposeAllInstances, provideInstance, provideTmpdirInstance } from "../fixture/fixture"
@@ -138,7 +139,7 @@ describe("Worktree", () => {
             expect(props.branch).toBe(info.branch)
 
             yield* Effect.promise(() =>
-              Instance.provide({
+              WithInstance.provide({
                 directory: info.directory,
                 fn: () => InstanceRuntime.disposeInstance(Instance.current),
               }),
@@ -163,7 +164,7 @@ describe("Worktree", () => {
 
             yield* Effect.promise(() => ready)
             yield* Effect.promise(() =>
-              Instance.provide({
+              WithInstance.provide({
                 directory: info.directory,
                 fn: () => InstanceRuntime.disposeInstance(Instance.current),
               }),

+ 21 - 40
packages/opencode/test/provider/amazon-bedrock.test.ts

@@ -5,6 +5,7 @@ import { unlink } from "fs/promises"
 import { ProviderID } from "../../src/provider/schema"
 import { tmpdir } from "../fixture/fixture"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Provider } from "@/provider/provider"
 import { Env } from "../../src/env"
 import { Global } from "@opencode-ai/core/global"
@@ -43,13 +44,11 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async ()
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("AWS_REGION", "us-east-1")
       set("AWS_PROFILE", "default")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
@@ -68,13 +67,11 @@ test("Bedrock: falls back to AWS_REGION env var when no config region", async ()
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("AWS_REGION", "eu-west-1")
       set("AWS_PROFILE", "default")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
@@ -123,14 +120,12 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
       }),
     )
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
-      init: Effect.promise(async () => {
+      fn: async () => {
         set("AWS_PROFILE", "")
         set("AWS_ACCESS_KEY_ID", "")
         set("AWS_BEARER_TOKEN_BEDROCK", "")
-      }).pipe(Effect.asVoid),
-      fn: async () => {
         const providers = await list()
         expect(providers[ProviderID.amazonBedrock]).toBeDefined()
         expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
@@ -169,13 +164,11 @@ test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("AWS_PROFILE", "default")
       set("AWS_ACCESS_KEY_ID", "test-key-id")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1")
@@ -201,12 +194,10 @@ test("Bedrock: includes custom endpoint in options when specified", async () =>
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("AWS_PROFILE", "default")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("AWS_PROFILE", "default")
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.endpoint).toBe(
@@ -234,15 +225,13 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async ()
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token")
       set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role")
       set("AWS_PROFILE", "")
       set("AWS_ACCESS_KEY_ID", "")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1")
@@ -277,12 +266,10 @@ test("Bedrock: model with us. prefix should not be double-prefixed", async () =>
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("AWS_PROFILE", "default")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("AWS_PROFILE", "default")
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       // The model should exist with the us. prefix
@@ -314,12 +301,10 @@ test("Bedrock: model with global. prefix should not be prefixed", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("AWS_PROFILE", "default")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("AWS_PROFILE", "default")
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].models["global.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
@@ -350,12 +335,10 @@ test("Bedrock: model with eu. prefix should not be double-prefixed", async () =>
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("AWS_PROFILE", "default")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("AWS_PROFILE", "default")
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].models["eu.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
@@ -386,12 +369,10 @@ test("Bedrock: model without prefix in US region should get us. prefix added", a
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("AWS_PROFILE", "default")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("AWS_PROFILE", "default")
       const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       // Non-prefixed model should still be registered

+ 14 - 13
packages/opencode/test/provider/gitlab-duo.test.ts

@@ -9,6 +9,7 @@ export {}
 // import { ProviderID, ModelID } from "../../src/provider/schema"
 // import { tmpdir } from "../fixture/fixture"
 // import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 // import { Provider } from "@/provider/provider"
 // import { Env } from "../../src/env"
 // import { Global } from "@opencode-ai/core/global"
@@ -25,7 +26,7 @@ export {}
 //       )
 //     },
 //   })
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "test-gitlab-token")
@@ -56,7 +57,7 @@ export {}
 //       )
 //     },
 //   })
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "test-token")
@@ -95,7 +96,7 @@ export {}
 //     }),
 //   )
 
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "")
@@ -130,7 +131,7 @@ export {}
 //     }),
 //   )
 
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "")
@@ -162,7 +163,7 @@ export {}
 //       )
 //     },
 //   })
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_INSTANCE_URL", "https://gitlab.company.internal")
@@ -193,7 +194,7 @@ export {}
 //       )
 //     },
 //   })
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "env-token")
@@ -216,7 +217,7 @@ export {}
 //       )
 //     },
 //   })
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "test-token")
@@ -252,7 +253,7 @@ export {}
 //       )
 //     },
 //   })
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "test-token")
@@ -277,7 +278,7 @@ export {}
 //       )
 //     },
 //   })
-//   await Instance.provide({
+//   await WithInstance.provide({
 //     directory: tmp.path,
 //     init: async () => {
 //       Env.set("GITLAB_TOKEN", "test-token")
@@ -301,7 +302,7 @@ export {}
 //         await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
 //       },
 //     })
-//     await Instance.provide({
+//     await WithInstance.provide({
 //       directory: tmp.path,
 //       init: async () => {
 //         Env.set("GITLAB_TOKEN", "test-token")
@@ -349,7 +350,7 @@ export {}
 //         await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
 //       },
 //     })
-//     await Instance.provide({
+//     await WithInstance.provide({
 //       directory: tmp.path,
 //       init: async () => {
 //         Env.set("GITLAB_TOKEN", "test-token")
@@ -372,7 +373,7 @@ export {}
 //         await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
 //       },
 //     })
-//     await Instance.provide({
+//     await WithInstance.provide({
 //       directory: tmp.path,
 //       init: async () => {
 //         Env.set("GITLAB_TOKEN", "test-token")
@@ -396,7 +397,7 @@ export {}
 //         await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
 //       },
 //     })
-//     await Instance.provide({
+//     await WithInstance.provide({
 //       directory: tmp.path,
 //       init: async () => {
 //         Env.set("GITLAB_TOKEN", "test-token")

+ 125 - 222
packages/opencode/test/provider/provider.test.ts

@@ -5,6 +5,7 @@ import path from "path"
 import { disposeAllInstances, tmpdir } from "../fixture/fixture"
 import { Global } from "@opencode-ai/core/global"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Plugin } from "../../src/plugin/index"
 import { ModelsDev } from "@/provider/models"
 import { Provider } from "@/provider/provider"
@@ -80,12 +81,10 @@ test("provider loaded from env variable", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       // Provider should retain its connection source even if custom loaders
@@ -114,7 +113,7 @@ test("provider loaded from config with apiKey option", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -135,12 +134,10 @@ test("disabled_providers excludes provider", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeUndefined()
     },
@@ -159,13 +156,11 @@ test("enabled_providers restricts to only listed providers", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("ANTHROPIC_API_KEY", "test-api-key")
       set("OPENAI_API_KEY", "test-openai-key")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.openai]).toBeUndefined()
@@ -189,12 +184,10 @@ test("model whitelist filters models for provider", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       const models = Object.keys(providers[ProviderID.anthropic].models)
@@ -220,12 +213,10 @@ test("model blacklist excludes specific models", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       const models = Object.keys(providers[ProviderID.anthropic].models)
@@ -255,12 +246,10 @@ test("custom model alias via config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.anthropic].models["my-alias"]).toBeDefined()
@@ -301,7 +290,7 @@ test("custom provider with npm package", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -358,7 +347,7 @@ test("custom DeepSeek openai-compatible model defaults interleaved reasoning fie
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -392,12 +381,10 @@ test("env variable takes precedence, config merges options", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "env-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "env-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       // Config options should be merged
@@ -418,12 +405,10 @@ test("getModel returns model for valid provider/model", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
       expect(model).toBeDefined()
       expect(String(model.providerID)).toBe("anthropic")
@@ -445,12 +430,10 @@ test("getModel throws ModelNotFoundError for invalid model", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow()
     },
   })
@@ -467,7 +450,7 @@ test("getModel throws ModelNotFoundError for invalid provider", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       expect(getModel(ProviderID.make("nonexistent-provider"), ModelID.make("some-model"))).rejects.toThrow()
@@ -498,12 +481,10 @@ test("defaultModel returns first available model when no config set", async () =
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const model = await defaultModel()
       expect(model.providerID).toBeDefined()
       expect(model.modelID).toBeDefined()
@@ -523,12 +504,10 @@ test("defaultModel respects config model setting", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const model = await defaultModel()
       expect(String(model.providerID)).toBe("anthropic")
       expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
@@ -565,7 +544,7 @@ test("provider with baseURL from config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -603,7 +582,7 @@ test("model cost defaults to zero when not specified", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -638,12 +617,10 @@ test("model options are merged from existing model", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.options.customOption).toBe("custom-value")
@@ -667,12 +644,10 @@ test("provider removed when all models filtered out", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeUndefined()
     },
@@ -690,12 +665,10 @@ test("closest finds model by partial match", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const result = await closest(ProviderID.anthropic, ["sonnet-4"])
       expect(result).toBeDefined()
       expect(String(result?.providerID)).toBe("anthropic")
@@ -715,7 +688,7 @@ test("closest returns undefined for nonexistent provider", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const result = await closest(ProviderID.make("nonexistent"), ["model"])
@@ -745,12 +718,10 @@ test("getModel uses realIdByKey for aliased models", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic].models["my-sonnet"]).toBeDefined()
 
@@ -791,7 +762,7 @@ test("provider api field sets model api.url", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -831,7 +802,7 @@ test("explicit baseURL overrides api field", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -860,12 +831,10 @@ test("model inherits properties from existing database model", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.name).toBe("Custom Name for Sonnet")
@@ -888,12 +857,10 @@ test("disabled_providers prevents loading even with env var", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("OPENAI_API_KEY", "test-openai-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("OPENAI_API_KEY", "test-openai-key")
       const providers = await list()
       expect(providers[ProviderID.openai]).toBeUndefined()
     },
@@ -912,13 +879,11 @@ test("enabled_providers with empty array allows no providers", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("ANTHROPIC_API_KEY", "test-api-key")
       set("OPENAI_API_KEY", "test-openai-key")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(Object.keys(providers).length).toBe(0)
     },
@@ -942,12 +907,10 @@ test("whitelist and blacklist can be combined", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       const models = Object.keys(providers[ProviderID.anthropic].models)
@@ -984,7 +947,7 @@ test("model modalities default correctly", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1027,7 +990,7 @@ test("model with custom cost values", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1051,12 +1014,10 @@ test("getSmallModel returns appropriate small model", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const model = await getSmallModel(ProviderID.anthropic)
       expect(model).toBeDefined()
       expect(model?.id).toContain("haiku")
@@ -1076,12 +1037,10 @@ test("getSmallModel respects config small_model override", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const model = await getSmallModel(ProviderID.anthropic)
       expect(model).toBeDefined()
       expect(String(model?.providerID)).toBe("anthropic")
@@ -1124,13 +1083,11 @@ test("multiple providers can be configured simultaneously", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("ANTHROPIC_API_KEY", "test-anthropic-key")
       set("OPENAI_API_KEY", "test-openai-key")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.openai]).toBeDefined()
@@ -1169,7 +1126,7 @@ test("provider with custom npm package", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1203,12 +1160,10 @@ test("model alias name defaults to alias key when id differs", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.anthropic].models["sonnet"].name).toBe("sonnet")
     },
@@ -1243,12 +1198,10 @@ test("provider with multiple env var options only includes apiKey when single en
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("MULTI_ENV_KEY_1", "test-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("MULTI_ENV_KEY_1", "test-key")
       const providers = await list()
       expect(providers[ProviderID.make("multi-env")]).toBeDefined()
       // When multiple env options exist, key should NOT be auto-set
@@ -1285,12 +1238,10 @@ test("provider with single env var includes apiKey automatically", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("SINGLE_ENV_KEY", "my-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("SINGLE_ENV_KEY", "my-api-key")
       const providers = await list()
       expect(providers[ProviderID.make("single-env")]).toBeDefined()
       // Single env option should auto-set key
@@ -1322,12 +1273,10 @@ test("model cost overrides existing cost values", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.cost.input).toBe(999)
@@ -1372,7 +1321,7 @@ test("completely new provider not in database can be configured", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1401,14 +1350,12 @@ test("disabled_providers and enabled_providers interaction", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("ANTHROPIC_API_KEY", "test-anthropic")
       set("OPENAI_API_KEY", "test-openai")
       set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       // anthropic: in enabled, not in disabled = allowed
       expect(providers[ProviderID.anthropic]).toBeDefined()
@@ -1446,7 +1393,7 @@ test("model with tool_call false", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1481,7 +1428,7 @@ test("model defaults tool_call to true when not specified", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1520,7 +1467,7 @@ test("model headers are preserved", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1559,13 +1506,11 @@ test("provider env fallback - second env var used if first missing", async () =>
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       // Only set fallback, not primary
       set("FALLBACK_KEY", "fallback-api-key")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       // Provider should load because fallback env var is set
       expect(providers[ProviderID.make("fallback-env")]).toBeDefined()
@@ -1584,12 +1529,10 @@ test("getModel returns consistent results", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
       const model2 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
       expect(model1.providerID).toEqual(model2.providerID)
@@ -1625,7 +1568,7 @@ test("provider name defaults to id when not in database", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1645,12 +1588,10 @@ test("ModelNotFoundError includes suggestions for typos", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       try {
         await getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4")) // typo: sonet instead of sonnet
         expect(true).toBe(false) // Should not reach here
@@ -1673,12 +1614,10 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       try {
         await getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4")) // typo: antropic
         expect(true).toBe(false) // Should not reach here
@@ -1701,7 +1640,7 @@ test("getProvider returns undefined for nonexistent provider", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const provider = await getProvider(ProviderID.make("nonexistent"))
@@ -1721,12 +1660,10 @@ test("getProvider returns provider info", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const provider = await getProvider(ProviderID.anthropic)
       expect(provider).toBeDefined()
       expect(String(provider?.id)).toBe("anthropic")
@@ -1745,12 +1682,10 @@ test("closest returns undefined when no partial match found", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"])
       expect(result).toBeUndefined()
     },
@@ -1768,12 +1703,10 @@ test("closest checks multiple query terms in order", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       // First term won't match, second will
       const result = await closest(ProviderID.anthropic, ["nonexistent", "haiku"])
       expect(result).toBeDefined()
@@ -1808,7 +1741,7 @@ test("model limit defaults to zero when not specified", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -1840,12 +1773,10 @@ test("provider options are deeply merged", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       // Custom options should be merged
       expect(providers[ProviderID.anthropic].options.timeout).toBe(30000)
@@ -1878,12 +1809,10 @@ test("custom model inherits npm package from models.dev provider config", async
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("OPENAI_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("OPENAI_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.openai].models["my-custom-model"]
       expect(model).toBeDefined()
@@ -1913,12 +1842,10 @@ test("custom model inherits api.url from models.dev provider", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("OPENROUTER_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("OPENROUTER_API_KEY", "test-api-key")
       const providers = await list()
       expect(providers[ProviderID.openrouter]).toBeDefined()
 
@@ -2046,12 +1973,10 @@ test("model variants are generated for reasoning models", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       // Claude sonnet 4 has reasoning capability
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
@@ -2084,12 +2009,10 @@ test("model variants can be disabled via config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants).toBeDefined()
@@ -2127,12 +2050,10 @@ test("model variants can be customized via config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants!["high"]).toBeDefined()
@@ -2166,12 +2087,10 @@ test("disabled key is stripped from variant config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants!["max"]).toBeDefined()
@@ -2204,12 +2123,10 @@ test("all variants can be disabled via config", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants).toBeDefined()
@@ -2242,12 +2159,10 @@ test("variant config merges with generated variants", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("ANTHROPIC_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants!["high"]).toBeDefined()
@@ -2280,12 +2195,10 @@ test("variants filtered in second pass for database models", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("OPENAI_API_KEY", "test-api-key")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("OPENAI_API_KEY", "test-api-key")
       const providers = await list()
       const model = providers[ProviderID.openai].models["gpt-5"]
       expect(model.variants).toBeDefined()
@@ -2329,7 +2242,7 @@ test("custom model with variants enabled and disabled", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const providers = await list()
@@ -2384,12 +2297,10 @@ test("Google Vertex: retains baseURL for custom proxy", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
       const providers = await list()
       expect(providers[ProviderID.make("vertex-proxy")]).toBeDefined()
       expect(providers[ProviderID.make("vertex-proxy")].options.baseURL).toBe("https://my-proxy.com/v1")
@@ -2429,12 +2340,10 @@ test("Google Vertex: supports OpenAI compatible models", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
-      set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
-    }).pipe(Effect.asVoid),
     fn: async () => {
+      set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
       const providers = await list()
       const model = providers[ProviderID.make("vertex-openai")].models["gpt-4"]
 
@@ -2455,14 +2364,12 @@ test("cloudflare-ai-gateway loads with env variables", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("CLOUDFLARE_ACCOUNT_ID", "test-account")
       set("CLOUDFLARE_GATEWAY_ID", "test-gateway")
       set("CLOUDFLARE_API_TOKEN", "test-token")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined()
     },
@@ -2487,14 +2394,12 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => {
       )
     },
   })
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("CLOUDFLARE_ACCOUNT_ID", "test-account")
       set("CLOUDFLARE_GATEWAY_ID", "test-gateway")
       set("CLOUDFLARE_API_TOKEN", "test-token")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined()
       expect(providers[ProviderID.make("cloudflare-ai-gateway")].options.metadata).toEqual({
@@ -2542,7 +2447,7 @@ test("plugin config providers persist after instance dispose", async () => {
     },
   })
 
-  const first = await Instance.provide({
+  const first = await WithInstance.provide({
     directory: tmp.path,
     fn: async () =>
       AppRuntime.runPromise(
@@ -2559,7 +2464,7 @@ test("plugin config providers persist after instance dispose", async () => {
 
   await disposeAllInstances()
 
-  const second = await Instance.provide({
+  const second = await WithInstance.provide({
     directory: tmp.path,
     fn: async () => list(),
   })
@@ -2590,13 +2495,11 @@ test("plugin config enabled and disabled providers are honored", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
-    init: Effect.promise(async () => {
+    fn: async () => {
       set("ANTHROPIC_API_KEY", "test-anthropic-key")
       set("OPENAI_API_KEY", "test-openai-key")
-    }).pipe(Effect.asVoid),
-    fn: async () => {
       const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.openai]).toBeUndefined()
@@ -2616,7 +2519,7 @@ test("opencode loader keeps paid models when config apiKey is present", async ()
     },
   })
 
-  const none = await Instance.provide({
+  const none = await WithInstance.provide({
     directory: base.path,
     fn: async () => paid(await list()),
   })
@@ -2639,7 +2542,7 @@ test("opencode loader keeps paid models when config apiKey is present", async ()
     },
   })
 
-  const keyedCount = await Instance.provide({
+  const keyedCount = await WithInstance.provide({
     directory: keyed.path,
     fn: async () => paid(await list()),
   })
@@ -2660,7 +2563,7 @@ test("opencode loader keeps paid models when auth exists", async () => {
     },
   })
 
-  const none = await Instance.provide({
+  const none = await WithInstance.provide({
     directory: base.path,
     fn: async () => paid(await list()),
   })
@@ -2694,7 +2597,7 @@ test("opencode loader keeps paid models when auth exists", async () => {
       }),
     )
 
-    const keyedCount = await Instance.provide({
+    const keyedCount = await WithInstance.provide({
       directory: keyed.path,
       fn: async () => paid(await list()),
     })

+ 4 - 3
packages/opencode/test/pty/pty-output-isolation.test.ts

@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
 import { AppRuntime } from "../../src/effect/app-runtime"
 import { Effect } from "effect"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Pty } from "../../src/pty"
 import { tmpdir } from "../fixture/fixture"
 import { setTimeout as sleep } from "node:timers/promises"
@@ -10,7 +11,7 @@ describe("pty", () => {
   test("does not leak output when websocket objects are reused", async () => {
     await using dir = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: dir.path,
       fn: () =>
         AppRuntime.runPromise(
@@ -60,7 +61,7 @@ describe("pty", () => {
   test("does not leak output when Bun recycles websocket objects before re-connect", async () => {
     await using dir = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: dir.path,
       fn: () =>
         AppRuntime.runPromise(
@@ -105,7 +106,7 @@ describe("pty", () => {
   test("treats in-place socket data mutation as the same connection", async () => {
     await using dir = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: dir.path,
       fn: () =>
         AppRuntime.runPromise(

+ 3 - 2
packages/opencode/test/pty/pty-session.test.ts

@@ -3,6 +3,7 @@ import { AppRuntime } from "../../src/effect/app-runtime"
 import { Bus } from "../../src/bus"
 import { Effect } from "effect"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Pty } from "../../src/pty"
 import type { PtyID } from "../../src/pty/schema"
 import { tmpdir } from "../fixture/fixture"
@@ -27,7 +28,7 @@ describe("pty", () => {
 
     await using dir = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: dir.path,
       fn: () =>
         AppRuntime.runPromise(
@@ -68,7 +69,7 @@ describe("pty", () => {
 
     await using dir = await tmpdir({ git: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: dir.path,
       fn: () =>
         AppRuntime.runPromise(

+ 4 - 3
packages/opencode/test/pty/pty-shell.test.ts

@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
 import { AppRuntime } from "../../src/effect/app-runtime"
 import { Effect } from "effect"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Pty } from "../../src/pty"
 import { Shell } from "../../src/shell/shell"
 import { tmpdir } from "../fixture/fixture"
@@ -17,7 +18,7 @@ describe("pty shell args", () => {
       "does not add login args to pwsh",
       async () => {
         await using dir = await tmpdir()
-        await Instance.provide({
+        await WithInstance.provide({
           directory: dir.path,
           fn: () =>
             AppRuntime.runPromise(
@@ -47,7 +48,7 @@ describe("pty shell args", () => {
       "adds login args to bash",
       async () => {
         await using dir = await tmpdir()
-        await Instance.provide({
+        await WithInstance.provide({
           directory: dir.path,
           fn: () =>
             AppRuntime.runPromise(
@@ -78,7 +79,7 @@ describe("pty configured shell", () => {
       await using dir = await tmpdir({
         config: { shell: Shell.name(configured) },
       })
-      await Instance.provide({
+      await WithInstance.provide({
         directory: dir.path,
         fn: () =>
           AppRuntime.runPromise(

+ 2 - 1
packages/opencode/test/question/question.test.ts

@@ -2,6 +2,7 @@ import { afterEach, expect } from "bun:test"
 import { Cause, Effect, Exit, Fiber, Layer } from "effect"
 import { Question } from "../../src/question"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { InstanceRuntime } from "../../src/project/instance-runtime"
 import { QuestionID } from "../../src/question/schema"
 import { disposeAllInstances, provideInstance, reloadTestInstance, tmpdirScoped } from "../fixture/fixture"
@@ -398,7 +399,7 @@ it.live("pending question rejects on instance dispose", () =>
 
     expect(yield* waitForPending(1).pipe(provideInstance(dir))).toHaveLength(1)
     yield* Effect.promise(() =>
-      Instance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }),
+      WithInstance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }),
     )
 
     const exit = yield* Fiber.await(fiber)

+ 7 - 6
packages/opencode/test/server/global-session-list.test.ts

@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
 import { Effect } from "effect"
 import z from "zod"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Project } from "@/project/project"
 import { Session as SessionNs } from "@/session/session"
 import * as Log from "@opencode-ai/core/util/log"
@@ -28,11 +29,11 @@ describe("session.listGlobal", () => {
     await using first = await tmpdir({ git: true })
     await using second = await tmpdir({ git: true })
 
-    const firstSession = await Instance.provide({
+    const firstSession = await WithInstance.provide({
       directory: first.path,
       fn: async () => svc.create({ title: "first-session" }),
     })
-    const secondSession = await Instance.provide({
+    const secondSession = await WithInstance.provide({
       directory: second.path,
       fn: async () => svc.create({ title: "second-session" }),
     })
@@ -58,12 +59,12 @@ describe("session.listGlobal", () => {
   test("excludes archived sessions by default", async () => {
     await using tmp = await tmpdir({ git: true })
 
-    const archived = await Instance.provide({
+    const archived = await WithInstance.provide({
       directory: tmp.path,
       fn: async () => svc.create({ title: "archived-session" }),
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => svc.setArchived({ sessionID: archived.id, time: Date.now() }),
     })
@@ -82,12 +83,12 @@ describe("session.listGlobal", () => {
   test("supports cursor pagination", async () => {
     await using tmp = await tmpdir({ git: true })
 
-    const first = await Instance.provide({
+    const first = await WithInstance.provide({
       directory: tmp.path,
       fn: async () => svc.create({ title: "page-one" }),
     })
     await new Promise((resolve) => setTimeout(resolve, 5))
-    const second = await Instance.provide({
+    const second = await WithInstance.provide({
       directory: tmp.path,
       fn: async () => svc.create({ title: "page-two" }),
     })

+ 3 - 2
packages/opencode/test/server/httpapi-experimental.test.ts

@@ -2,6 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"
 import { Effect } from "effect"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Server } from "../../src/server/server"
 import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/groups/experimental"
 import { Session } from "@/session/session"
@@ -126,12 +127,12 @@ describe("experimental HttpApi", () => {
   test("serves global session list through Hono bridge", async () => {
     await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
 
-    const first = await Instance.provide({
+    const first = await WithInstance.provide({
       directory: tmp.path,
       fn: async () => createSession({ title: "page-one" }),
     })
     await new Promise((resolve) => setTimeout(resolve, 5))
-    const second = await Instance.provide({
+    const second = await WithInstance.provide({
       directory: tmp.path,
       fn: async () => createSession({ title: "page-two" }),
     })

+ 2 - 2
packages/opencode/test/server/httpapi-instance-context.test.ts

@@ -10,8 +10,8 @@ import { registerAdapter } from "../../src/control-plane/adapters"
 import type { WorkspaceAdapter } from "../../src/control-plane/types"
 import { Workspace } from "../../src/control-plane/workspace"
 import { InstanceRef, WorkspaceRef } from "../../src/effect/instance-ref"
-import { InstanceRuntime } from "../../src/project/instance-runtime"
 import { Instance } from "../../src/project/instance"
+import { InstanceLayer } from "../../src/project/instance-layer"
 import { Project } from "../../src/project/project"
 import { disposeMiddleware, markInstanceForDisposal } from "../../src/server/routes/instance/httpapi/lifecycle"
 import { instanceRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/instance-context"
@@ -41,7 +41,7 @@ const it = testEffect(
     testStateLayer,
     NodeHttpServer.layerTest,
     NodeServices.layer,
-    InstanceRuntime.layer,
+    InstanceLayer.layer,
     Project.defaultLayer,
     Workspace.defaultLayer,
   ),

+ 2 - 1
packages/opencode/test/server/httpapi-mcp.test.ts

@@ -5,6 +5,7 @@ import { Flag } from "@opencode-ai/core/flag/flag"
 import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
 import { McpPaths } from "../../src/server/routes/instance/httpapi/groups/mcp"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { InstanceRuntime } from "../../src/project/instance-runtime"
 import { Server } from "../../src/server/server"
 import * as Log from "@opencode-ai/core/util/log"
@@ -59,7 +60,7 @@ function withMcpProject<A, E, R>(self: (dir: string) => Effect.Effect<A, E, R>)
     )
     yield* Effect.addFinalizer(() =>
       Effect.promise(() =>
-        Instance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }),
+        WithInstance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }),
       ).pipe(Effect.ignore),
     )
 

+ 2 - 1
packages/opencode/test/server/httpapi-provider.test.ts

@@ -3,6 +3,7 @@ import { Effect, FileSystem, Layer, Path } from "effect"
 import { NodeFileSystem, NodePath } from "@effect/platform-node"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { InstanceRuntime } from "../../src/project/instance-runtime"
 import { Server } from "../../src/server/server"
 import * as Log from "@opencode-ai/core/util/log"
@@ -91,7 +92,7 @@ function withProviderProject<A, E, R>(self: (dir: string) => Effect.Effect<A, E,
     yield* writeProviderAuthPlugin(dir)
     yield* Effect.addFinalizer(() =>
       Effect.promise(() =>
-        Instance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }),
+        WithInstance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }),
       ).pipe(Effect.ignore),
     )
 

+ 2 - 1
packages/opencode/test/server/httpapi-sdk.test.ts

@@ -5,6 +5,7 @@ import { HttpRouter } from "effect/unstable/http"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { createOpencodeClient } from "@opencode-ai/sdk/v2"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
 import { Server } from "../../src/server/server"
 import { MessageID, PartID, SessionID } from "../../src/session/schema"
@@ -226,7 +227,7 @@ function seedMessage(directory: string, sessionID: string) {
   const id = SessionID.make(sessionID)
   return call(
     async () =>
-      await Instance.provide({
+      await WithInstance.provide({
         directory,
         fn: () =>
           Effect.runPromise(

+ 3 - 2
packages/opencode/test/server/httpapi-session.test.ts

@@ -9,6 +9,7 @@ import { Workspace } from "../../src/control-plane/workspace"
 import { PermissionID } from "../../src/permission/schema"
 import { ModelID, ProviderID } from "../../src/provider/schema"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Project } from "../../src/project/project"
 import { Server } from "../../src/server/server"
 import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
@@ -44,7 +45,7 @@ function pathFor(path: string, params: Record<string, string>) {
 function createSession(directory: string, input?: Session.CreateInput) {
   return Effect.promise(
     async () =>
-      await Instance.provide({
+      await WithInstance.provide({
         directory,
         fn: () => runSession(Session.Service.use((svc) => svc.create(input))),
       }),
@@ -54,7 +55,7 @@ function createSession(directory: string, input?: Session.CreateInput) {
 function createTextMessage(directory: string, sessionID: SessionID, text: string) {
   return Effect.promise(
     async () =>
-      await Instance.provide({
+      await WithInstance.provide({
         directory,
         fn: () =>
           runSession(

+ 2 - 1
packages/opencode/test/server/httpapi-sync.test.ts

@@ -2,6 +2,7 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
 import { Effect } from "effect"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Server } from "../../src/server/server"
 import { SyncPaths } from "../../src/server/routes/instance/httpapi/groups/sync"
 import { Session } from "@/session/session"
@@ -38,7 +39,7 @@ describe("sync HttpApi", () => {
     const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" }
     const info = spyOn(Log.create({ service: "server.sync" }), "info")
 
-    const session = await Instance.provide({
+    const session = await WithInstance.provide({
       directory: tmp.path,
       fn: async () => runSession(Session.Service.use((svc) => svc.create({ title: "sync" }))),
     })

+ 2 - 1
packages/opencode/test/server/session-actions.test.ts

@@ -1,6 +1,7 @@
 import { afterEach, describe, expect, mock, test } from "bun:test"
 import { Effect } from "effect"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Server } from "../../src/server/server"
 import { Session as SessionNs } from "@/session/session"
 import type { SessionID } from "../../src/session/schema"
@@ -31,7 +32,7 @@ afterEach(async () => {
 describe("session action routes", () => {
   test("abort route returns success", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})

+ 21 - 20
packages/opencode/test/server/session-list.test.ts

@@ -1,6 +1,7 @@
 import { afterEach, describe, expect, test } from "bun:test"
 import { Effect } from "effect"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Session as SessionNs } from "@/session/session"
 import * as Log from "@opencode-ai/core/util/log"
 import { disposeAllInstances, tmpdir } from "../fixture/fixture"
@@ -40,20 +41,20 @@ describe("session.list", () => {
     await mkdir(path.join(tmp.path, "packages", "opencode"), { recursive: true })
     await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const root = await svc.create({ title: "root" })
 
-        const parent = await Instance.provide({
+        const parent = await WithInstance.provide({
           directory: path.join(tmp.path, "packages"),
           fn: async () => svc.create({ title: "parent" }),
         })
-        const current = await Instance.provide({
+        const current = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "opencode"),
           fn: async () => svc.create({ title: "current" }),
         })
-        const sibling = await Instance.provide({
+        const sibling = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "app"),
           fn: async () => svc.create({ title: "sibling" }),
         })
@@ -73,20 +74,20 @@ describe("session.list", () => {
     await mkdir(path.join(tmp.path, "packages", "opencode"), { recursive: true })
     await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const root = await svc.create({ title: "root" })
 
-        const parent = await Instance.provide({
+        const parent = await WithInstance.provide({
           directory: path.join(tmp.path, "packages"),
           fn: async () => svc.create({ title: "parent" }),
         })
-        const current = await Instance.provide({
+        const current = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "opencode"),
           fn: async () => svc.create({ title: "current" }),
         })
-        const sibling = await Instance.provide({
+        const sibling = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "app"),
           fn: async () => svc.create({ title: "sibling" }),
         })
@@ -106,22 +107,22 @@ describe("session.list", () => {
     await mkdir(path.join(tmp.path, "packages", "opencode", "src", "deep"), { recursive: true })
     await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
-        const parent = await Instance.provide({
+        const parent = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "opencode"),
           fn: async () => svc.create({ title: "parent" }),
         })
-        const current = await Instance.provide({
+        const current = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "opencode", "src"),
           fn: async () => svc.create({ title: "current" }),
         })
-        const deeper = await Instance.provide({
+        const deeper = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "opencode", "src", "deep"),
           fn: async () => svc.create({ title: "deeper" }),
         })
-        const sibling = await Instance.provide({
+        const sibling = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "app"),
           fn: async () => svc.create({ title: "sibling" }),
         })
@@ -146,14 +147,14 @@ describe("session.list", () => {
     await mkdir(path.join(tmp.path, "packages", "opencode", "src"), { recursive: true })
     await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
-        const current = await Instance.provide({
+        const current = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "opencode", "src"),
           fn: async () => svc.create({ title: "legacy-current" }),
         })
-        const sibling = await Instance.provide({
+        const sibling = await WithInstance.provide({
           directory: path.join(tmp.path, "packages", "app"),
           fn: async () => svc.create({ title: "legacy-sibling" }),
         })
@@ -175,7 +176,7 @@ describe("session.list", () => {
 
   test("filters root sessions", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const root = await svc.create({ title: "root-session" })
@@ -192,7 +193,7 @@ describe("session.list", () => {
 
   test("filters by start time", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await svc.create({ title: "new-session" })
@@ -206,7 +207,7 @@ describe("session.list", () => {
 
   test("filters by search term", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await svc.create({ title: "unique-search-term-abc" })
@@ -223,7 +224,7 @@ describe("session.list", () => {
 
   test("respects limit parameter", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         await svc.create({ title: "session-1" })

+ 5 - 4
packages/opencode/test/server/session-messages.test.ts

@@ -1,6 +1,7 @@
 import { afterEach, describe, expect, test } from "bun:test"
 import { Effect } from "effect"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Server } from "../../src/server/server"
 import { Session as SessionNs } from "@/session/session"
 import { MessageV2 } from "../../src/session/message-v2"
@@ -76,7 +77,7 @@ describe("session messages endpoint", () => {
   test("returns cursor headers for older pages", async () => {
     await using tmp = await tmpdir({ git: true })
     await withoutWatcher(() =>
-      Instance.provide({
+      WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const session = await svc.create({})
@@ -105,7 +106,7 @@ describe("session messages endpoint", () => {
   test("keeps full-history responses when limit is omitted", async () => {
     await using tmp = await tmpdir({ git: true })
     await withoutWatcher(() =>
-      Instance.provide({
+      WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const session = await svc.create({})
@@ -126,7 +127,7 @@ describe("session messages endpoint", () => {
   test("rejects invalid cursors and missing sessions", async () => {
     await using tmp = await tmpdir({ git: true })
     await withoutWatcher(() =>
-      Instance.provide({
+      WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const session = await svc.create({})
@@ -147,7 +148,7 @@ describe("session messages endpoint", () => {
   test("does not truncate large legacy limit requests", async () => {
     await using tmp = await tmpdir({ git: true })
     await withoutWatcher(() =>
-      Instance.provide({
+      WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const session = await svc.create({})

+ 4 - 3
packages/opencode/test/server/session-select.test.ts

@@ -4,6 +4,7 @@ import { Session as SessionNs } from "@/session/session"
 import type { SessionID } from "../../src/session/schema"
 import * as Log from "@opencode-ai/core/util/log"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Server } from "../../src/server/server"
 import { disposeAllInstances, tmpdir } from "../fixture/fixture"
 
@@ -30,7 +31,7 @@ afterEach(async () => {
 describe("tui.selectSession endpoint", () => {
   test("should return 200 when called with valid session", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         // #given
@@ -56,7 +57,7 @@ describe("tui.selectSession endpoint", () => {
 
   test("should return 404 when session does not exist", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         // #given
@@ -78,7 +79,7 @@ describe("tui.selectSession endpoint", () => {
 
   test("should return 400 when session ID format is invalid", async () => {
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         // #given

+ 20 - 19
packages/opencode/test/session/compaction.test.ts

@@ -10,6 +10,7 @@ import { LLM } from "../../src/session/llm"
 import { SessionCompaction } from "../../src/session/compaction"
 import { Token } from "@/util/token"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import * as Log from "@opencode-ai/core/util/log"
 import { Permission } from "../../src/permission"
 import { Plugin } from "../../src/plugin"
@@ -792,7 +793,7 @@ describe("session.compaction.prune", () => {
 describe("session.compaction.process", () => {
   test("throws when parent is not a user message", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -822,7 +823,7 @@ describe("session.compaction.process", () => {
 
   test("publishes compacted event on continue", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -872,7 +873,7 @@ describe("session.compaction.process", () => {
 
   test("marks summary message as errored on compact result", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -910,7 +911,7 @@ describe("session.compaction.process", () => {
 
   test("adds synthetic continue prompt when auto is enabled", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -951,7 +952,7 @@ describe("session.compaction.process", () => {
 
   test("persists tail_start_id for retained recent turns", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -998,7 +999,7 @@ describe("session.compaction.process", () => {
 
   test("shrinks retained tail to fit preserve token budget", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1047,7 +1048,7 @@ describe("session.compaction.process", () => {
         captured = JSON.stringify(input.messages)
       }),
     )
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1096,7 +1097,7 @@ describe("session.compaction.process", () => {
         captured = JSON.stringify(input.messages)
       }),
     )
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1155,7 +1156,7 @@ describe("session.compaction.process", () => {
         captured = JSON.stringify(input.messages)
       }),
     )
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1218,7 +1219,7 @@ describe("session.compaction.process", () => {
 
   test("allows plugins to disable synthetic continue prompt", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1261,7 +1262,7 @@ describe("session.compaction.process", () => {
 
   test("replays the prior user turn on overflow when earlier context exists", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1309,7 +1310,7 @@ describe("session.compaction.process", () => {
 
   test("falls back to overflow guidance when no replayable turn exists", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1369,7 +1370,7 @@ describe("session.compaction.process", () => {
     )
 
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1443,7 +1444,7 @@ describe("session.compaction.process", () => {
     const ready = defer()
 
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1545,7 +1546,7 @@ describe("session.compaction.process", () => {
     )
 
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1587,7 +1588,7 @@ describe("session.compaction.process", () => {
     )
 
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1639,7 +1640,7 @@ describe("session.compaction.process", () => {
     )
 
     await using tmp = await tmpdir({ git: true })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1707,7 +1708,7 @@ describe("session.compaction.process", () => {
     stub.push(reply("summary one"))
     stub.push(reply("summary two"))
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})
@@ -1779,7 +1780,7 @@ describe("session.compaction.process", () => {
 
   test("ignores previous summaries when sizing the retained tail", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const session = await svc.create({})

+ 9 - 8
packages/opencode/test/session/llm.test.ts

@@ -6,6 +6,7 @@ import z from "zod"
 import { makeRuntime } from "../../src/effect/run-service"
 import { LLM } from "../../src/session/llm"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Provider } from "@/provider/provider"
 import { ProviderTransform } from "@/provider/transform"
 import { ModelsDev } from "@/provider/models"
@@ -338,7 +339,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
@@ -425,7 +426,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
@@ -515,7 +516,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
@@ -629,7 +630,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.openai, ModelID.make(model.id))
@@ -745,7 +746,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.openai, ModelID.make(model.id))
@@ -864,7 +865,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
@@ -982,7 +983,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.make("anthropic"), ModelID.make(model.id))
@@ -1223,7 +1224,7 @@ describe("session.llm.stream", () => {
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))

+ 43 - 42
packages/opencode/test/session/messages-pagination.test.ts

@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
 import { Effect } from "effect"
 import path from "path"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Session as SessionNs } from "@/session/session"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageID, PartID, type SessionID } from "../../src/session/schema"
@@ -123,7 +124,7 @@ async function addCompactionPart(sessionID: SessionID, messageID: MessageID, tai
 
 describe("MessageV2.page", () => {
   test("returns sync result", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -139,7 +140,7 @@ describe("MessageV2.page", () => {
   })
 
   test("pages backward with opaque cursors", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -167,7 +168,7 @@ describe("MessageV2.page", () => {
   })
 
   test("returns items in chronological order within a page", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -182,7 +183,7 @@ describe("MessageV2.page", () => {
   })
 
   test("returns empty items for session with no messages", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -198,7 +199,7 @@ describe("MessageV2.page", () => {
   })
 
   test("throws NotFoundError for non-existent session", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const fake = "non-existent-session" as SessionID
@@ -208,7 +209,7 @@ describe("MessageV2.page", () => {
   })
 
   test("handles exact limit boundary", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -225,7 +226,7 @@ describe("MessageV2.page", () => {
   })
 
   test("limit of 1 returns single newest message", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -242,7 +243,7 @@ describe("MessageV2.page", () => {
   })
 
   test("hydrates multiple parts per message", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -266,7 +267,7 @@ describe("MessageV2.page", () => {
   })
 
   test("accepts cursors from fractional timestamps", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -284,7 +285,7 @@ describe("MessageV2.page", () => {
   })
 
   test("messages with same timestamp are ordered by id", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -304,7 +305,7 @@ describe("MessageV2.page", () => {
   })
 
   test("does not return messages from other sessions", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const a = await svc.create({})
@@ -326,7 +327,7 @@ describe("MessageV2.page", () => {
   })
 
   test("large limit returns all messages without cursor", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -346,7 +347,7 @@ describe("MessageV2.page", () => {
 
 describe("MessageV2.stream", () => {
   test("yields items newest first", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -361,7 +362,7 @@ describe("MessageV2.stream", () => {
   })
 
   test("yields nothing for empty session", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -375,7 +376,7 @@ describe("MessageV2.stream", () => {
   })
 
   test("yields single message", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -391,7 +392,7 @@ describe("MessageV2.stream", () => {
   })
 
   test("hydrates parts for each yielded message", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -409,7 +410,7 @@ describe("MessageV2.stream", () => {
   })
 
   test("handles sets exceeding internal page size", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -426,7 +427,7 @@ describe("MessageV2.stream", () => {
   })
 
   test("is a sync generator", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -447,7 +448,7 @@ describe("MessageV2.stream", () => {
 
 describe("MessageV2.parts", () => {
   test("returns parts for a message", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -464,7 +465,7 @@ describe("MessageV2.parts", () => {
   })
 
   test("returns empty array for message with no parts", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -479,7 +480,7 @@ describe("MessageV2.parts", () => {
   })
 
   test("returns multiple parts in order", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -512,7 +513,7 @@ describe("MessageV2.parts", () => {
   })
 
   test("returns empty for non-existent message id", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         await svc.create({})
@@ -523,7 +524,7 @@ describe("MessageV2.parts", () => {
   })
 
   test("parts contain sessionID and messageID", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -541,7 +542,7 @@ describe("MessageV2.parts", () => {
 
 describe("MessageV2.get", () => {
   test("returns message with hydrated parts", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -560,7 +561,7 @@ describe("MessageV2.get", () => {
   })
 
   test("throws NotFoundError for non-existent message", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -575,7 +576,7 @@ describe("MessageV2.get", () => {
   })
 
   test("scopes by session id", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const a = await svc.create({})
@@ -593,7 +594,7 @@ describe("MessageV2.get", () => {
   })
 
   test("returns message with multiple parts", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -616,7 +617,7 @@ describe("MessageV2.get", () => {
   })
 
   test("returns assistant message with correct role", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -642,7 +643,7 @@ describe("MessageV2.get", () => {
   })
 
   test("returns message with zero parts", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -660,7 +661,7 @@ describe("MessageV2.get", () => {
 
 describe("MessageV2.filterCompacted", () => {
   test("returns all messages when no compaction", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -677,7 +678,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("stops at compaction boundary and returns chronological order", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -721,7 +722,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("does not break on compaction part without matching summary", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -739,7 +740,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("skips assistant with error even if marked as summary", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -764,7 +765,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("skips assistant without finish even if marked as summary", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -785,7 +786,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("retains original tail when compaction stores tail_start_id", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -841,7 +842,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("fork remaps compaction tail_start_id for filterCompacted", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -907,7 +908,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("retains an assistant tail when compaction starts inside a turn", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -971,7 +972,7 @@ describe("MessageV2.filterCompacted", () => {
   })
 
   test("prefers latest compaction boundary when repeated compactions exist", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -1093,7 +1094,7 @@ describe("MessageV2.cursor", () => {
 
 describe("MessageV2 consistency", () => {
   test("page hydration matches get for each message", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -1112,7 +1113,7 @@ describe("MessageV2 consistency", () => {
   })
 
   test("parts from get match standalone parts call", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -1128,7 +1129,7 @@ describe("MessageV2 consistency", () => {
   })
 
   test("stream collects same messages as exhaustive page iteration", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})
@@ -1155,7 +1156,7 @@ describe("MessageV2 consistency", () => {
   })
 
   test("filterCompacted of full stream returns same as Array.from when no compaction", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: root,
       fn: async () => {
         const session = await svc.create({})

+ 5 - 4
packages/opencode/test/session/session.test.ts

@@ -4,6 +4,7 @@ import { Session as SessionNs } from "@/session/session"
 import { Bus } from "../../src/bus"
 import * as Log from "@opencode-ai/core/util/log"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageID, PartID, type SessionID } from "../../src/session/schema"
 import { AppRuntime } from "../../src/effect/app-runtime"
@@ -34,7 +35,7 @@ function updatePart<T extends MessageV2.Part>(part: T) {
 
 describe("session.created event", () => {
   test("should emit session.created event when session is created", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         let eventReceived = false
@@ -63,7 +64,7 @@ describe("session.created event", () => {
   })
 
   test("session.created event should be emitted before session.updated", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const events: string[] = []
@@ -95,7 +96,7 @@ describe("step-finish token propagation via Bus event", () => {
   test(
     "non-zero tokens propagate through PartUpdated event",
     async () => {
-      await Instance.provide({
+      await WithInstance.provide({
         directory: projectRoot,
         fn: async () => {
           const info = await create({})
@@ -166,7 +167,7 @@ describe("Session", () => {
   test("remove works without an instance", async () => {
     await using tmp = await tmpdir({ git: true })
 
-    const info = await Instance.provide({
+    const info = await WithInstance.provide({
       directory: tmp.path,
       fn: () => create({ title: "remove-without-instance" }),
     })

+ 2 - 1
packages/opencode/test/session/structured-output-integration.test.ts

@@ -5,6 +5,7 @@ import { Session } from "@/session/session"
 import { SessionPrompt } from "../../src/session/prompt"
 import * as Log from "@opencode-ai/core/util/log"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { MessageV2 } from "../../src/session/message-v2"
 
 const projectRoot = path.join(__dirname, "../..")
@@ -15,7 +16,7 @@ const hasApiKey = !!process.env.ANTHROPIC_API_KEY
 
 // Helper to run test within Instance context
 async function withInstance<T>(fn: () => Promise<T>): Promise<T> {
-  return Instance.provide({
+  return WithInstance.provide({
     directory: projectRoot,
     fn,
   })

+ 58 - 57
packages/opencode/test/snapshot/snapshot.test.ts

@@ -5,6 +5,7 @@ import path from "path"
 import { Effect } from "effect"
 import { Snapshot } from "../../src/snapshot"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Filesystem } from "@/util/filesystem"
 import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture"
 
@@ -47,7 +48,7 @@ function run<A>(dir: string, body: (snapshot: Snapshot.Interface) => Effect.Effe
 
 test("tracks deleted files correctly", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -62,7 +63,7 @@ test("tracks deleted files correctly", async () => {
 
 test("revert should remove new files", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -86,7 +87,7 @@ test("revert should remove new files", async () => {
 
 test("revert in subdirectory", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -113,7 +114,7 @@ test("revert in subdirectory", async () => {
 
 test("multiple file operations", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -145,7 +146,7 @@ test("multiple file operations", async () => {
 
 test("empty directory handling", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -160,7 +161,7 @@ test("empty directory handling", async () => {
 
 test("binary file handling", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -184,7 +185,7 @@ test("binary file handling", async () => {
 
 test("symlink handling", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -199,7 +200,7 @@ test("symlink handling", async () => {
 
 test("file under size limit handling", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -214,7 +215,7 @@ test("file under size limit handling", async () => {
 
 test("large added files are skipped", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -231,7 +232,7 @@ test("large added files are skipped", async () => {
 
 test("nested directory revert", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -256,7 +257,7 @@ test("nested directory revert", async () => {
 
 test("special characters in filenames", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -276,7 +277,7 @@ test("special characters in filenames", async () => {
 
 test("revert with empty patches", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       // Should not crash with empty patches
@@ -290,7 +291,7 @@ test("revert with empty patches", async () => {
 
 test("patch with invalid hash", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -309,7 +310,7 @@ test("patch with invalid hash", async () => {
 
 test("revert non-existent file", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -333,7 +334,7 @@ test("revert non-existent file", async () => {
 
 test("unicode filenames", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -373,7 +374,7 @@ test("unicode filenames", async () => {
 
 test.skip("unicode filenames modification and restore", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const chineseFile = fwd(tmp.path, "文件.txt")
@@ -402,7 +403,7 @@ test.skip("unicode filenames modification and restore", async () => {
 
 test("unicode filenames in subdirectories", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -428,7 +429,7 @@ test("unicode filenames in subdirectories", async () => {
 
 test("very long filenames", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -455,7 +456,7 @@ test("very long filenames", async () => {
 
 test("hidden files", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -475,7 +476,7 @@ test("hidden files", async () => {
 
 test("nested symlinks", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -495,7 +496,7 @@ test("nested symlinks", async () => {
 
 test("file permissions and ownership changes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -516,7 +517,7 @@ test("file permissions and ownership changes", async () => {
 
 test("circular symlinks", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -547,7 +548,7 @@ test("source project gitignore is respected - ignored files are not snapshotted"
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -576,7 +577,7 @@ test("source project gitignore is respected - ignored files are not snapshotted"
 
 test("gitignore changes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -600,7 +601,7 @@ test("gitignore changes", async () => {
 
 test("files tracked in snapshot but now gitignored are filtered out", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       // First, create a file and snapshot it
@@ -634,7 +635,7 @@ test("files tracked in snapshot but now gitignored are filtered out", async () =
 
 test("gitignore updated between track calls filters from diff", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       // a.txt is already committed from bootstrap - track it in snapshot
@@ -669,7 +670,7 @@ test("gitignore updated between track calls filters from diff", async () => {
 
 test("git info exclude changes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -695,7 +696,7 @@ test("git info exclude changes", async () => {
 
 test("git info exclude keeps global excludes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const global = `${tmp.path}/global.ignore`
@@ -731,7 +732,7 @@ test("git info exclude keeps global excludes", async () => {
 
 test("concurrent file operations during patch", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -763,7 +764,7 @@ test("snapshot state isolation between projects", async () => {
   await using tmp1 = await bootstrap()
   await using tmp2 = await bootstrap()
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp1.path,
     fn: async () => {
       const before1 = await run(tmp1.path, (snapshot) => snapshot.track())
@@ -773,7 +774,7 @@ test("snapshot state isolation between projects", async () => {
     },
   })
 
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp2.path,
     fn: async () => {
       const before2 = await run(tmp2.path, (snapshot) => snapshot.track())
@@ -793,14 +794,14 @@ test("patch detects changes in secondary worktree", async () => {
   await $`git worktree add ${worktreePath} HEAD`.cwd(tmp.path).quiet()
 
   try {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         expect(await run(tmp.path, (snapshot) => snapshot.track())).toBeTruthy()
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: worktreePath,
       fn: async () => {
         const before = await run(worktreePath, (snapshot) => snapshot.track())
@@ -825,7 +826,7 @@ test("revert only removes files in invoking worktree", async () => {
   await $`git worktree add ${worktreePath} HEAD`.cwd(tmp.path).quiet()
 
   try {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         expect(await run(tmp.path, (snapshot) => snapshot.track())).toBeTruthy()
@@ -834,7 +835,7 @@ test("revert only removes files in invoking worktree", async () => {
     const primaryFile = `${tmp.path}/worktree.txt`
     await Filesystem.write(primaryFile, "primary content")
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: worktreePath,
       fn: async () => {
         const before = await run(worktreePath, (snapshot) => snapshot.track())
@@ -869,14 +870,14 @@ test("diff reports worktree-only/shared edits and ignores primary-only", async (
   await $`git worktree add ${worktreePath} HEAD`.cwd(tmp.path).quiet()
 
   try {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         expect(await run(tmp.path, (snapshot) => snapshot.track())).toBeTruthy()
       },
     })
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: worktreePath,
       fn: async () => {
         const before = await run(worktreePath, (snapshot) => snapshot.track())
@@ -903,7 +904,7 @@ test("diff reports worktree-only/shared edits and ignores primary-only", async (
 
 test("track with no changes returns same hash", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const hash1 = await run(tmp.path, (snapshot) => snapshot.track())
@@ -922,7 +923,7 @@ test("track with no changes returns same hash", async () => {
 
 test("diff function with various changes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -943,7 +944,7 @@ test("diff function with various changes", async () => {
 
 test("restore function", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -977,7 +978,7 @@ test("restore function", async () => {
 
 test("revert should not delete files that existed but were deleted in snapshot", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const snapshot1 = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1007,7 +1008,7 @@ test("revert should not delete files that existed but were deleted in snapshot",
 
 test("revert preserves file that existed in snapshot when deleted then recreated", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       await Filesystem.write(`${tmp.path}/existing.txt`, "original content")
@@ -1044,7 +1045,7 @@ test("revert preserves file that existed in snapshot when deleted then recreated
 
 test("diffFull sets status based on git change type", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       await Filesystem.write(`${tmp.path}/grow.txt`, "one\n")
@@ -1090,7 +1091,7 @@ test("diffFull sets status based on git change type", async () => {
 
 test("diffFull with new file additions", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1115,7 +1116,7 @@ test("diffFull with new file additions", async () => {
 
 test("diffFull with a large interleaved mixed diff", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const ids = Array.from({ length: 60 }, (_, i) => i.toString().padStart(3, "0"))
@@ -1178,7 +1179,7 @@ test("diffFull with a large interleaved mixed diff", async () => {
 
 test("diffFull preserves git diff order across batch boundaries", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const ids = Array.from({ length: 140 }, (_, i) => i.toString().padStart(3, "0"))
@@ -1204,7 +1205,7 @@ test("diffFull preserves git diff order across batch boundaries", async () => {
 
 test("diffFull with file modifications", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1230,7 +1231,7 @@ test("diffFull with file modifications", async () => {
 
 test("diffFull with file deletions", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1255,7 +1256,7 @@ test("diffFull with file deletions", async () => {
 
 test("diffFull with multiple line additions", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1281,7 +1282,7 @@ test("diffFull with multiple line additions", async () => {
 
 test("diffFull with addition and deletion", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1313,7 +1314,7 @@ test("diffFull with addition and deletion", async () => {
 
 test("diffFull with multiple additions and deletions", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1355,7 +1356,7 @@ test("diffFull with multiple additions and deletions", async () => {
 
 test("diffFull with no changes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1372,7 +1373,7 @@ test("diffFull with no changes", async () => {
 
 test("diffFull with binary file changes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const before = await run(tmp.path, (snapshot) => snapshot.track())
@@ -1395,7 +1396,7 @@ test("diffFull with binary file changes", async () => {
 
 test("diffFull with whitespace changes", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       await Filesystem.write(`${tmp.path}/whitespace.txt`, "line1\nline2")
@@ -1419,7 +1420,7 @@ test("diffFull with whitespace changes", async () => {
 
 test("revert with overlapping files across patches uses first patch hash", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       // Write initial content and snapshot
@@ -1453,7 +1454,7 @@ test("revert with overlapping files across patches uses first patch hash", async
 
 test("revert preserves patch order when the same hash appears again", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       await $`mkdir -p ${tmp.path}/foo`.quiet()
@@ -1490,7 +1491,7 @@ test("revert preserves patch order when the same hash appears again", async () =
 
 test("revert handles large mixed batches across chunk boundaries", async () => {
   await using tmp = await bootstrap()
-  await Instance.provide({
+  await WithInstance.provide({
     directory: tmp.path,
     fn: async () => {
       const base = Array.from({ length: 140 }, (_, i) => fwd(tmp.path, "batch", `${i}.txt`))

+ 25 - 24
packages/opencode/test/tool/apply_patch.test.ts

@@ -4,6 +4,7 @@ import * as fs from "fs/promises"
 import { Effect, ManagedRuntime, Layer } from "effect"
 import { ApplyPatchTool } from "../../src/tool/apply_patch"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { LSP } from "@/lsp/lsp"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Format } from "../../src/format"
@@ -97,7 +98,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir({ git: true })
     const { ctx, calls } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const modifyPath = path.join(fixture.path, "modify.txt")
@@ -149,7 +150,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir({ git: true })
     const { ctx, calls } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const original = path.join(fixture.path, "old", "name.txt")
@@ -179,7 +180,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "multi.txt")
@@ -199,7 +200,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx, calls } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const bom = String.fromCharCode(0xfeff)
@@ -228,7 +229,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "insert_only.txt")
@@ -247,7 +248,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "no_newline.txt")
@@ -269,7 +270,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const original = path.join(fixture.path, "old", "name.txt")
@@ -292,7 +293,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const original = path.join(fixture.path, "old", "name.txt")
@@ -317,7 +318,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "duplicate.txt")
@@ -335,7 +336,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const patchText = "*** Begin Patch\n*** Update File: missing.txt\n@@\n-nope\n+better\n*** End Patch"
@@ -351,7 +352,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const patchText = "*** Begin Patch\n*** Delete File: missing.txt\n*** End Patch"
@@ -365,7 +366,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const dirPath = path.join(fixture.path, "dir")
@@ -382,7 +383,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const patchText = "*** Begin Patch\n*** Frobnicate File: foo\n*** End Patch"
@@ -396,7 +397,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "modify.txt")
@@ -414,7 +415,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const patchText =
@@ -432,7 +433,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "tail.txt")
@@ -450,7 +451,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "two_chunks.txt")
@@ -468,7 +469,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "multi_ctx.txt")
@@ -486,7 +487,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "eof_anchor.txt")
@@ -508,7 +509,7 @@ describe("tool.apply_patch freeform", () => {
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const patchText = `cat <<'EOF'
@@ -529,7 +530,7 @@ EOF`
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const patchText = `<<EOF
@@ -550,7 +551,7 @@ EOF`
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "trailing_ws.txt")
@@ -570,7 +571,7 @@ EOF`
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "leading_ws.txt")
@@ -590,7 +591,7 @@ EOF`
     await using fixture = await tmpdir()
     const { ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: fixture.path,
       fn: async () => {
         const target = path.join(fixture.path, "unicode.txt")

+ 19 - 18
packages/opencode/test/tool/edit.test.ts

@@ -4,6 +4,7 @@ import fs from "fs/promises"
 import { Effect, Layer, ManagedRuntime } from "effect"
 import { EditTool } from "../../src/tool/edit"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { disposeAllInstances, tmpdir } from "../fixture/fixture"
 import { LSP } from "@/lsp/lsp"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
@@ -73,7 +74,7 @@ describe("tool.edit", () => {
       await using tmp = await tmpdir()
       const filepath = path.join(tmp.path, "newfile.txt")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -102,7 +103,7 @@ describe("tool.edit", () => {
       const bom = String.fromCharCode(0xfeff)
       await fs.writeFile(filepath, `${bom}using System;\n`, "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -131,7 +132,7 @@ describe("tool.edit", () => {
       await using tmp = await tmpdir()
       const filepath = path.join(tmp.path, "nested", "dir", "file.txt")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -156,7 +157,7 @@ describe("tool.edit", () => {
       await using tmp = await tmpdir()
       const filepath = path.join(tmp.path, "new.txt")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const { FileWatcher } = await import("../../src/file/watcher")
@@ -191,7 +192,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "existing.txt")
       await fs.writeFile(filepath, "old content here", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -220,7 +221,7 @@ describe("tool.edit", () => {
       const bom = String.fromCharCode(0xfeff)
       await fs.writeFile(filepath, `${bom}using System;\nclass Test {}\n`, "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -250,7 +251,7 @@ describe("tool.edit", () => {
       await using tmp = await tmpdir()
       const filepath = path.join(tmp.path, "nonexistent.txt")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -275,7 +276,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "content", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -300,7 +301,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "actual content", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -325,7 +326,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "foo bar foo baz foo", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -352,7 +353,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "original", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const { FileWatcher } = await import("../../src/file/watcher")
@@ -387,7 +388,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "line1\nline2\nline3", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -413,7 +414,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "line1\r\nold\r\nline3", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -439,7 +440,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "content", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -464,7 +465,7 @@ describe("tool.edit", () => {
       const dirpath = path.join(tmp.path, "adir")
       await fs.mkdir(dirpath)
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -489,7 +490,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "line1\nline2\nline3", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -558,7 +559,7 @@ describe("tool.edit", () => {
         },
       })
 
-      return await Instance.provide({
+      return await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()
@@ -702,7 +703,7 @@ describe("tool.edit", () => {
       const filepath = path.join(tmp.path, "file.txt")
       await fs.writeFile(filepath, "top = 0\nmiddle = keep\nbottom = 0\n", "utf-8")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const edit = await resolve()

+ 8 - 7
packages/opencode/test/tool/external-directory.test.ts

@@ -3,6 +3,7 @@ import path from "path"
 import { Effect } from "effect"
 import type { Tool } from "@/tool/tool"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { assertExternalDirectory } from "../../src/tool/external-directory"
 import { Filesystem } from "@/util/filesystem"
 import { tmpdir } from "../fixture/fixture"
@@ -38,7 +39,7 @@ describe("tool.assertExternalDirectory", () => {
   test("no-ops for empty target", async () => {
     const { requests, ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: "/tmp",
       fn: async () => {
         await assertExternalDirectory(ctx)
@@ -51,7 +52,7 @@ describe("tool.assertExternalDirectory", () => {
   test("no-ops for paths inside Instance.directory", async () => {
     const { requests, ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: "/tmp/project",
       fn: async () => {
         await assertExternalDirectory(ctx, path.join("/tmp/project", "file.txt"))
@@ -68,7 +69,7 @@ describe("tool.assertExternalDirectory", () => {
     const target = "/tmp/outside/file.txt"
     const expected = glob(path.join(path.dirname(target), "*"))
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory,
       fn: async () => {
         await assertExternalDirectory(ctx, target)
@@ -88,7 +89,7 @@ describe("tool.assertExternalDirectory", () => {
     const target = "/tmp/outside"
     const expected = glob(path.join(target, "*"))
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory,
       fn: async () => {
         await assertExternalDirectory(ctx, target, { kind: "directory" })
@@ -104,7 +105,7 @@ describe("tool.assertExternalDirectory", () => {
   test("skips prompting when bypass=true", async () => {
     const { requests, ctx } = makeCtx()
 
-    await Instance.provide({
+    await WithInstance.provide({
       directory: "/tmp/project",
       fn: async () => {
         await assertExternalDirectory(ctx, "/tmp/outside/file.txt", { bypass: true })
@@ -131,7 +132,7 @@ describe("tool.assertExternalDirectory", () => {
         .replaceAll("\\", "/")
         .toLowerCase()
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await assertExternalDirectory(ctx, alt)
@@ -152,7 +153,7 @@ describe("tool.assertExternalDirectory", () => {
       const root = path.parse(tmp.path).root
       const target = path.join(root, "boot.ini")
 
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           await assertExternalDirectory(ctx, target)

+ 42 - 41
packages/opencode/test/tool/shell.test.ts

@@ -6,6 +6,7 @@ import { Config } from "@/config/config"
 import { Shell } from "../../src/shell/shell"
 import { ShellTool } from "../../src/tool/shell"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { Filesystem } from "@/util/filesystem"
 import { tmpdir } from "../fixture/fixture"
 import type { Permission } from "../../src/permission"
@@ -140,7 +141,7 @@ const mustTruncate = (result: {
 
 describe("tool.shell", () => {
   each("basic", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -163,7 +164,7 @@ describe("tool.shell", () => {
     await using tmp = await tmpdir({
       config: { shell: "fish" },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initBash()
@@ -190,7 +191,7 @@ describe("tool.shell", () => {
 describe("tool.shell permissions", () => {
   each("asks for bash permission with correct pattern", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initShell()
@@ -213,7 +214,7 @@ describe("tool.shell permissions", () => {
 
   each("asks for bash permission with multiple commands", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initShell()
@@ -239,7 +240,7 @@ describe("tool.shell permissions", () => {
     test(
       `parses PowerShell conditionals for permission prompts [${item.label}]`,
       withShell(item, async () => {
-        await Instance.provide({
+        await WithInstance.provide({
           directory: projectRoot,
           fn: async () => {
             const bash = await initShell()
@@ -269,7 +270,7 @@ describe("tool.shell permissions", () => {
       `uses PowerShell cmdlet prefixes for always-allow prompts [${item.label}]`,
       withShell(item, async () => {
         await using tmp = await tmpdir()
-        await Instance.provide({
+        await WithInstance.provide({
           directory: tmp.path,
           fn: async () => {
             const bash = await initShell()
@@ -297,7 +298,7 @@ describe("tool.shell permissions", () => {
   }
 
   each("asks for external_directory permission for wildcard external paths", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -333,7 +334,7 @@ describe("tool.shell permissions", () => {
               await Bun.write(path.join(dir, "outside.txt"), "x")
             },
           })
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initShell()
@@ -366,7 +367,7 @@ describe("tool.shell permissions", () => {
       test(
         `asks for external_directory permission for PowerShell paths after switches [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initShell()
@@ -396,7 +397,7 @@ describe("tool.shell permissions", () => {
       test(
         `asks for nested PowerShell command permissions [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initShell()
@@ -428,7 +429,7 @@ describe("tool.shell permissions", () => {
         `asks for external_directory permission for drive-relative PowerShell paths [${item.label}]`,
         withShell(item, async () => {
           await using tmp = await tmpdir()
-          await Instance.provide({
+          await WithInstance.provide({
             directory: tmp.path,
             fn: async () => {
               const bash = await initShell()
@@ -458,7 +459,7 @@ describe("tool.shell permissions", () => {
       test(
         `asks for external_directory permission for $HOME PowerShell paths [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initShell()
@@ -489,7 +490,7 @@ describe("tool.shell permissions", () => {
         `asks for external_directory permission for $PWD PowerShell paths [${item.label}]`,
         withShell(item, async () => {
           await using tmp = await tmpdir()
-          await Instance.provide({
+          await WithInstance.provide({
             directory: tmp.path,
             fn: async () => {
               const bash = await initBash()
@@ -519,7 +520,7 @@ describe("tool.shell permissions", () => {
       test(
         `asks for external_directory permission for $PSHOME PowerShell paths [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initBash()
@@ -553,7 +554,7 @@ describe("tool.shell permissions", () => {
           const prev = process.env[key]
           delete process.env[key]
           try {
-            await Instance.provide({
+            await WithInstance.provide({
               directory: projectRoot,
               fn: async () => {
                 const bash = await initShell()
@@ -588,7 +589,7 @@ describe("tool.shell permissions", () => {
       test(
         `asks for external_directory permission for PowerShell env paths [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initBash()
@@ -617,7 +618,7 @@ describe("tool.shell permissions", () => {
       test(
         `asks for external_directory permission for PowerShell FileSystem paths [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initBash()
@@ -649,7 +650,7 @@ describe("tool.shell permissions", () => {
       test(
         `asks for external_directory permission for braced PowerShell env paths [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initBash()
@@ -681,7 +682,7 @@ describe("tool.shell permissions", () => {
       test(
         `treats Set-Location like cd for permissions [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initBash()
@@ -712,7 +713,7 @@ describe("tool.shell permissions", () => {
       test(
         `does not add nested PowerShell expressions to permission prompts [${item.label}]`,
         withShell(item, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initShell()
@@ -741,7 +742,7 @@ describe("tool.shell permissions", () => {
     test(
       "asks for external_directory permission for cmd file commands [cmd]",
       withShell(cmdShell, async () => {
-        await Instance.provide({
+        await WithInstance.provide({
           directory: projectRoot,
           fn: async () => {
             const bash = await initShell()
@@ -766,7 +767,7 @@ describe("tool.shell permissions", () => {
 
   each("asks for external_directory permission when cd to parent", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initBash()
@@ -791,7 +792,7 @@ describe("tool.shell permissions", () => {
 
   each("asks for external_directory permission when workdir is outside project", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initBash()
@@ -821,7 +822,7 @@ describe("tool.shell permissions", () => {
       const err = new Error("stop after permission")
       await using outerTmp = await tmpdir()
       await using tmp = await tmpdir()
-      await Instance.provide({
+      await WithInstance.provide({
         directory: tmp.path,
         fn: async () => {
           const bash = await initBash()
@@ -857,7 +858,7 @@ describe("tool.shell permissions", () => {
       test(
         "uses Git Bash /tmp semantics for external workdir",
         withShell({ label: "bash", shell: bash }, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initBash()
@@ -889,7 +890,7 @@ describe("tool.shell permissions", () => {
       test(
         "uses Git Bash /tmp semantics for external file paths",
         withShell({ label: "bash", shell: bash }, async () => {
-          await Instance.provide({
+          await WithInstance.provide({
             directory: projectRoot,
             fn: async () => {
               const bash = await initBash()
@@ -926,7 +927,7 @@ describe("tool.shell permissions", () => {
       },
     })
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initBash()
@@ -959,7 +960,7 @@ describe("tool.shell permissions", () => {
         await Bun.write(path.join(dir, "tmpfile"), "x")
       },
     })
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initBash()
@@ -981,7 +982,7 @@ describe("tool.shell permissions", () => {
 
   each("includes always patterns for auto-approval", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initBash()
@@ -1004,7 +1005,7 @@ describe("tool.shell permissions", () => {
 
   each("does not ask for bash permission when command is cd only", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initShell()
@@ -1026,7 +1027,7 @@ describe("tool.shell permissions", () => {
 
   each("matches redirects in permission pattern", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initShell()
@@ -1049,7 +1050,7 @@ describe("tool.shell permissions", () => {
 
   each("always pattern has space before wildcard to not include different commands", async () => {
     await using tmp = await tmpdir()
-    await Instance.provide({
+    await WithInstance.provide({
       directory: tmp.path,
       fn: async () => {
         const bash = await initBash()
@@ -1065,7 +1066,7 @@ describe("tool.shell permissions", () => {
 
 describe("tool.shell abort", () => {
   test("preserves output when aborted", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -1099,7 +1100,7 @@ describe("tool.shell abort", () => {
   }, 15_000)
 
   test("terminates command on timeout", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -1121,7 +1122,7 @@ describe("tool.shell abort", () => {
   }, 15_000)
 
   test.skipIf(process.platform === "win32")("captures stderr in output", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -1142,7 +1143,7 @@ describe("tool.shell abort", () => {
   })
 
   test("returns non-zero exit code", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -1161,7 +1162,7 @@ describe("tool.shell abort", () => {
   })
 
   test("streams metadata updates progressively", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initBash()
@@ -1192,7 +1193,7 @@ describe("tool.shell abort", () => {
 
 describe("tool.shell truncation", () => {
   test("truncates output exceeding line limit", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -1214,7 +1215,7 @@ describe("tool.shell truncation", () => {
   })
 
   test("truncates output exceeding byte limit", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -1236,7 +1237,7 @@ describe("tool.shell truncation", () => {
   })
 
   test("does not truncate small output", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()
@@ -1256,7 +1257,7 @@ describe("tool.shell truncation", () => {
   })
 
   test("full output is saved to file when truncated", async () => {
-    await Instance.provide({
+    await WithInstance.provide({
       directory: projectRoot,
       fn: async () => {
         const bash = await initShell()

+ 4 - 3
packages/opencode/test/tool/webfetch.test.ts

@@ -5,6 +5,7 @@ import { FetchHttpClient } from "effect/unstable/http"
 import { Agent } from "../../src/agent/agent"
 import { Truncate } from "@/tool/truncate"
 import { Instance } from "../../src/project/instance"
+import { WithInstance } from "../../src/project/with-instance"
 import { WebFetchTool } from "../../src/tool/webfetch"
 import { SessionID, MessageID } from "../../src/session/schema"
 
@@ -41,7 +42,7 @@ describe("tool.webfetch", () => {
     await withFetch(
       () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }),
       async (url) => {
-        await Instance.provide({
+        await WithInstance.provide({
           directory: projectRoot,
           fn: async () => {
             const result = await exec({ url: new URL("/image.png", url).toString(), format: "markdown" })
@@ -69,7 +70,7 @@ describe("tool.webfetch", () => {
           headers: { "content-type": "image/svg+xml; charset=UTF-8" },
         }),
       async (url) => {
-        await Instance.provide({
+        await WithInstance.provide({
           directory: projectRoot,
           fn: async () => {
             const result = await exec({ url: new URL("/image.svg", url).toString(), format: "html" })
@@ -89,7 +90,7 @@ describe("tool.webfetch", () => {
           headers: { "content-type": "text/plain; charset=utf-8" },
         }),
       async (url) => {
-        await Instance.provide({
+        await WithInstance.provide({
           directory: projectRoot,
           fn: async () => {
             const result = await exec({ url: new URL("/file.txt", url).toString(), format: "text" })