Explorar o código

refactor(opencode): roll out serviceUse proxy across 14 services + tests (#28576)

Kit Langton hai 3 meses
pai
achega
39ea816a72
Modificáronse 48 ficheiros con 646 adicións e 628 borrados
  1. 3 0
      packages/opencode/src/account/account.ts
  2. 3 0
      packages/opencode/src/account/repo.ts
  3. 3 0
      packages/opencode/src/agent/agent.ts
  4. 3 0
      packages/opencode/src/config/config.ts
  5. 3 0
      packages/opencode/src/env/index.ts
  6. 3 0
      packages/opencode/src/file/index.ts
  7. 3 0
      packages/opencode/src/file/ripgrep.ts
  8. 3 0
      packages/opencode/src/format/index.ts
  9. 3 0
      packages/opencode/src/installation/index.ts
  10. 3 0
      packages/opencode/src/mcp/auth.ts
  11. 3 0
      packages/opencode/src/mcp/index.ts
  12. 3 0
      packages/opencode/src/project/instance-store.ts
  13. 3 0
      packages/opencode/src/provider/auth.ts
  14. 3 0
      packages/opencode/src/share/share-next.ts
  15. 20 20
      packages/opencode/test/account/repo.test.ts
  16. 9 9
      packages/opencode/test/account/service.test.ts
  17. 6 6
      packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts
  18. 1 1
      packages/opencode/test/agent/plugin-agent-regression.test.ts
  19. 3 3
      packages/opencode/test/config/agent-color.test.ts
  20. 43 43
      packages/opencode/test/config/config.test.ts
  21. 1 1
      packages/opencode/test/config/tui.test.ts
  22. 451 475
      packages/opencode/test/control-plane/workspace.test.ts
  23. 2 2
      packages/opencode/test/file/fsmonitor.test.ts
  24. 2 2
      packages/opencode/test/file/path-traversal.test.ts
  25. 6 6
      packages/opencode/test/file/ripgrep.test.ts
  26. 3 3
      packages/opencode/test/format/format.test.ts
  27. 9 9
      packages/opencode/test/installation/installation.test.ts
  28. 3 3
      packages/opencode/test/mcp/oauth-auto-connect.test.ts
  29. 1 1
      packages/opencode/test/permission-task.test.ts
  30. 2 2
      packages/opencode/test/plugin/auth-override.test.ts
  31. 2 2
      packages/opencode/test/provider/amazon-bedrock.test.ts
  32. 7 7
      packages/opencode/test/provider/provider.test.ts
  33. 3 3
      packages/opencode/test/server/global-session-list.test.ts
  34. 1 1
      packages/opencode/test/server/httpapi-experimental.test.ts
  35. 1 1
      packages/opencode/test/server/httpapi-instance-context.test.ts
  36. 2 2
      packages/opencode/test/server/httpapi-session.test.ts
  37. 1 1
      packages/opencode/test/server/httpapi-sync.test.ts
  38. 1 1
      packages/opencode/test/server/httpapi-workspace-routing.test.ts
  39. 2 2
      packages/opencode/test/server/httpapi-workspace.test.ts
  40. 1 1
      packages/opencode/test/server/project-init-git.test.ts
  41. 2 2
      packages/opencode/test/server/session-actions.test.ts
  42. 2 2
      packages/opencode/test/server/session-diff-missing-patch.test.ts
  43. 5 5
      packages/opencode/test/server/session-list.test.ts
  44. 2 2
      packages/opencode/test/server/session-messages.test.ts
  45. 1 1
      packages/opencode/test/server/session-select.test.ts
  46. 1 1
      packages/opencode/test/session/compaction.test.ts
  47. 1 1
      packages/opencode/test/session/session.test.ts
  48. 7 7
      packages/opencode/test/share/share-next.test.ts

+ 3 - 0
packages/opencode/src/account/account.ts

@@ -1,4 +1,5 @@
 import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
+import { serviceUse } from "@/effect/service-use"
 import {
   FetchHttpClient,
   HttpClient,
@@ -181,6 +182,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Account") {}
 
+export const use = serviceUse(Service)
+
 export const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/account/repo.ts

@@ -1,4 +1,5 @@
 import { eq } from "drizzle-orm"
+import { serviceUse } from "@/effect/service-use"
 import { Effect, Layer, Option, Schema, Context } from "effect"
 
 import { Database } from "@/storage/db"
@@ -38,6 +39,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/AccountRepo") {}
 
+export const use = serviceUse(Service)
+
 export const layer: Layer.Layer<Service> = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/agent/agent.ts

@@ -1,4 +1,5 @@
 import { Config } from "@/config/config"
+import { serviceUse } from "@/effect/service-use"
 import { Provider } from "@/provider/provider"
 import { ModelID, ProviderID } from "../provider/schema"
 import { generateObject, streamObject, type ModelMessage } from "ai"
@@ -76,6 +77,8 @@ type State = Omit<Interface, "generate">
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Agent") {}
 
+export const use = serviceUse(Service)
+
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/config/config.ts

@@ -1,4 +1,5 @@
 import * as Log from "@opencode-ai/core/util/log"
+import { serviceUse } from "@/effect/service-use"
 import path from "path"
 import { pathToFileURL } from "url"
 import os from "os"
@@ -319,6 +320,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Config") {}
 
+export const use = serviceUse(Service)
+
 function globalConfigFile() {
   const candidates = ["opencode.jsonc", "opencode.json", "config.json"].map((file) =>
     path.join(Global.Path.config, file),

+ 3 - 0
packages/opencode/src/env/index.ts

@@ -1,4 +1,5 @@
 import { Context, Effect, Layer } from "effect"
+import { serviceUse } from "@/effect/service-use"
 import { InstanceState } from "@/effect/instance-state"
 
 type State = Record<string, string | undefined>
@@ -12,6 +13,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Env") {}
 
+export const use = serviceUse(Service)
+
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/file/index.ts

@@ -1,4 +1,5 @@
 import { BusEvent } from "@/bus/bus-event"
+import { serviceUse } from "@/effect/service-use"
 import { InstanceState } from "@/effect/instance-state"
 
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
@@ -326,6 +327,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/File") {}
 
+export const use = serviceUse(Service)
+
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/file/ripgrep.ts

@@ -1,4 +1,5 @@
 import path from "path"
+import { serviceUse } from "@/effect/service-use"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect"
 import type { PlatformError } from "effect/PlatformError"
@@ -141,6 +142,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Ripgrep") {}
 
+export const use = serviceUse(Service)
+
 function env() {
   const env = sanitizedProcessEnv()
   delete env.RIPGREP_CONFIG_PATH

+ 3 - 0
packages/opencode/src/format/index.ts

@@ -1,4 +1,5 @@
 import { Effect, Layer, Context, Schema } from "effect"
+import { serviceUse } from "@/effect/service-use"
 import { ChildProcess } from "effect/unstable/process"
 import { AppProcess } from "@opencode-ai/core/process"
 import { InstanceState } from "@/effect/instance-state"
@@ -27,6 +28,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Format") {}
 
+export const use = serviceUse(Service)
+
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/installation/index.ts

@@ -1,4 +1,5 @@
 import { Effect, Layer, Schema, Context, Stream } from "effect"
+import { serviceUse } from "@/effect/service-use"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
 import { withTransientReadRetry } from "@/util/effect-http-client"
 import { errorMessage } from "@/util/error"
@@ -89,6 +90,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Installation") {}
 
+export const use = serviceUse(Service)
+
 export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProcess.Service> = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/mcp/auth.ts

@@ -1,4 +1,5 @@
 import path from "path"
+import { serviceUse } from "@/effect/service-use"
 import { Global } from "@opencode-ai/core/global"
 import { Effect, Layer, Context, Option, Schema } from "effect"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
@@ -51,6 +52,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/McpAuth") {}
 
+export const use = serviceUse(Service)
+
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/mcp/index.ts

@@ -1,4 +1,5 @@
 import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai"
+import { serviceUse } from "@/effect/service-use"
 import { Client } from "@modelcontextprotocol/sdk/client/index.js"
 import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
 import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
@@ -264,6 +265,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/MCP") {}
 
+export const use = serviceUse(Service)
+
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/project/instance-store.ts

@@ -1,4 +1,5 @@
 import { GlobalBus } from "@/bus/global"
+import { serviceUse } from "@/effect/service-use"
 import { WorkspaceContext } from "@/control-plane/workspace-context"
 import { InstanceRef } from "@/effect/instance-ref"
 import { disposeInstance as runDisposers } from "@/effect/instance-registry"
@@ -24,6 +25,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/InstanceStore") {}
 
+export const use = serviceUse(Service)
+
 interface Entry {
   readonly deferred: Deferred.Deferred<InstanceContext>
 }

+ 3 - 0
packages/opencode/src/provider/auth.ts

@@ -1,4 +1,5 @@
 import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
+import { serviceUse } from "@/effect/service-use"
 import { Auth } from "@/auth"
 import { InstanceState } from "@/effect/instance-state"
 import { optionalOmitUndefined } from "@opencode-ai/core/schema"
@@ -102,6 +103,8 @@ interface State {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/ProviderAuth") {}
 
+export const use = serviceUse(Service)
+
 export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> = Layer.effect(
   Service,
   Effect.gen(function* () {

+ 3 - 0
packages/opencode/src/share/share-next.ts

@@ -1,4 +1,5 @@
 import type * as SDK from "@opencode-ai/sdk/v2"
+import { serviceUse } from "@/effect/service-use"
 import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
 import { Account } from "@/account/account"
@@ -76,6 +77,8 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/ShareNext") {}
 
+export const use = serviceUse(Service)
+
 const db = <T>(fn: (d: Parameters<typeof Database.use>[0] extends (trx: infer D) => any ? D : never) => T) =>
   Effect.sync(() => Database.use(fn))
 

+ 20 - 20
packages/opencode/test/account/repo.test.ts

@@ -18,14 +18,14 @@ const it = testEffect(Layer.merge(AccountRepo.layer, truncate))
 
 it.live("list returns empty when no accounts exist", () =>
   Effect.gen(function* () {
-    const accounts = yield* AccountRepo.Service.use((r) => r.list())
+    const accounts = yield* AccountRepo.use.list()
     expect(accounts).toEqual([])
   }),
 )
 
 it.live("active returns none when no accounts exist", () =>
   Effect.gen(function* () {
-    const active = yield* AccountRepo.Service.use((r) => r.active())
+    const active = yield* AccountRepo.use.active()
     expect(Option.isNone(active)).toBe(true)
   }),
 )
@@ -45,13 +45,13 @@ it.live("persistAccount inserts and getRow retrieves", () =>
       }),
     )
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     expect(Option.isSome(row)).toBe(true)
     const value = Option.getOrThrow(row)
     expect(value.id).toBe(AccountID.make("user-1"))
     expect(value.email).toBe("test@example.com")
 
-    const active = yield* AccountRepo.Service.use((r) => r.active())
+    const active = yield* AccountRepo.use.active()
     expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-1"))
   }),
 )
@@ -72,9 +72,9 @@ it.live("persistAccount normalizes trailing slashes in stored server URLs", () =
       }),
     )
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
-    const active = yield* AccountRepo.Service.use((r) => r.active())
-    const list = yield* AccountRepo.Service.use((r) => r.list())
+    const row = yield* AccountRepo.use.getRow(id)
+    const active = yield* AccountRepo.use.active()
+    const list = yield* AccountRepo.use.list()
 
     expect(Option.getOrThrow(row).url).toBe("https://control.example.com")
     expect(Option.getOrThrow(active).url).toBe("https://control.example.com")
@@ -112,7 +112,7 @@ it.live("persistAccount sets the active account and org", () =>
     )
 
     // Last persisted account is active with its org
-    const active = yield* AccountRepo.Service.use((r) => r.active())
+    const active = yield* AccountRepo.use.active()
     expect(Option.isSome(active)).toBe(true)
     expect(Option.getOrThrow(active).id).toBe(AccountID.make("user-2"))
     expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
@@ -148,7 +148,7 @@ it.live("list returns all accounts", () =>
       }),
     )
 
-    const accounts = yield* AccountRepo.Service.use((r) => r.list())
+    const accounts = yield* AccountRepo.use.list()
     expect(accounts.length).toBe(2)
     expect(accounts.map((a) => a.email).sort()).toEqual(["a@example.com", "b@example.com"])
   }),
@@ -170,9 +170,9 @@ it.live("remove deletes an account", () =>
       }),
     )
 
-    yield* AccountRepo.Service.use((r) => r.remove(id))
+    yield* AccountRepo.use.remove(id)
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     expect(Option.isNone(row)).toBe(true)
   }),
 )
@@ -207,12 +207,12 @@ it.live("use stores the selected org and marks the account active", () =>
     )
 
     yield* AccountRepo.Service.use((r) => r.use(id1, Option.some(OrgID.make("org-99"))))
-    const active1 = yield* AccountRepo.Service.use((r) => r.active())
+    const active1 = yield* AccountRepo.use.active()
     expect(Option.getOrThrow(active1).id).toBe(id1)
     expect(Option.getOrThrow(active1).active_org_id).toBe(OrgID.make("org-99"))
 
     yield* AccountRepo.Service.use((r) => r.use(id1, Option.none()))
-    const active2 = yield* AccountRepo.Service.use((r) => r.active())
+    const active2 = yield* AccountRepo.use.active()
     expect(Option.getOrThrow(active2).active_org_id).toBeNull()
   }),
 )
@@ -243,7 +243,7 @@ it.live("persistToken updates token fields", () =>
       }),
     )
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     const value = Option.getOrThrow(row)
     expect(value.access_token).toBe(AccessToken.make("new_token"))
     expect(value.refresh_token).toBe(RefreshToken.make("new_refresh"))
@@ -276,7 +276,7 @@ it.live("persistToken with no expiry sets token_expiry to null", () =>
       }),
     )
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     expect(Option.getOrThrow(row).token_expiry).toBeNull()
   }),
 )
@@ -309,14 +309,14 @@ it.live("persistAccount upserts on conflict", () =>
       }),
     )
 
-    const accounts = yield* AccountRepo.Service.use((r) => r.list())
+    const accounts = yield* AccountRepo.use.list()
     expect(accounts.length).toBe(1)
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     const value = Option.getOrThrow(row)
     expect(value.access_token).toBe(AccessToken.make("at_v2"))
 
-    const active = yield* AccountRepo.Service.use((r) => r.active())
+    const active = yield* AccountRepo.use.active()
     expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
   }),
 )
@@ -337,9 +337,9 @@ it.live("remove clears active state when deleting the active account", () =>
       }),
     )
 
-    yield* AccountRepo.Service.use((r) => r.remove(id))
+    yield* AccountRepo.use.remove(id)
 
-    const active = yield* AccountRepo.Service.use((r) => r.active())
+    const active = yield* AccountRepo.use.active()
     expect(Option.isNone(active)).toBe(true)
   }),
 )

+ 9 - 9
packages/opencode/test/account/service.test.ts

@@ -88,7 +88,7 @@ it.live("login normalizes trailing slashes in the provided server URL", () =>
       }),
     )
 
-    const result = yield* Account.Service.use((s) => s.login("https://one.example.com/")).pipe(
+    const result = yield* Account.use.login("https://one.example.com/").pipe(
       Effect.provide(live(client)),
     )
 
@@ -109,7 +109,7 @@ it.live("login maps transport failures to account transport errors", () =>
     )
 
     const error = yield* Effect.flip(
-      Account.Service.use((s) => s.login("https://one.example.com")).pipe(Effect.provide(live(client))),
+      Account.use.login("https://one.example.com").pipe(Effect.provide(live(client))),
     )
 
     expect(error).toBeInstanceOf(AccountTransportError)
@@ -163,7 +163,7 @@ it.live("orgsByAccount groups orgs per account", () =>
       }),
     )
 
-    const rows = yield* Account.Service.use((s) => s.orgsByAccount()).pipe(Effect.provide(live(client)))
+    const rows = yield* Account.use.orgsByAccount().pipe(Effect.provide(live(client)))
 
     expect(rows.map((row) => [row.account.id, row.orgs.map((org) => org.id)]).map(([id, orgs]) => [id, orgs])).toEqual([
       [AccountID.make("user-1"), [OrgID.make("org-1")]],
@@ -201,12 +201,12 @@ it.live("token refresh persists the new token", () =>
       ),
     )
 
-    const token = yield* Account.Service.use((s) => s.token(id)).pipe(Effect.provide(live(client)))
+    const token = yield* Account.use.token(id).pipe(Effect.provide(live(client)))
 
     expect(Option.getOrThrow(token)).toBeDefined()
     expect(String(Option.getOrThrow(token))).toBe("at_new")
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     const value = Option.getOrThrow(row)
     expect(value.access_token).toBe(AccessToken.make("at_new"))
     expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
@@ -246,12 +246,12 @@ it.live("token refreshes before expiry when inside the eager refresh window", ()
       }),
     )
 
-    const token = yield* Account.Service.use((s) => s.token(id)).pipe(Effect.provide(live(client)))
+    const token = yield* Account.use.token(id).pipe(Effect.provide(live(client)))
 
     expect(String(Option.getOrThrow(token))).toBe("at_new")
     expect(refreshCalls).toBe(1)
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     const value = Option.getOrThrow(row)
     expect(value.access_token).toBe(AccessToken.make("at_new"))
     expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
@@ -315,7 +315,7 @@ it.live("concurrent config and token requests coalesce token refresh", () =>
     expect(String(Option.getOrThrow(token))).toBe("at_new")
     expect(refreshCalls).toBe(1)
 
-    const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
+    const row = yield* AccountRepo.use.getRow(id)
     const value = Option.getOrThrow(row)
     expect(value.access_token).toBe(AccessToken.make("at_new"))
     expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
@@ -388,7 +388,7 @@ it.live("poll stores the account and first org on success", () =>
       expect(res.email).toBe("user@example.com")
     }
 
-    const active = yield* AccountRepo.Service.use((r) => r.active())
+    const active = yield* AccountRepo.use.active()
     expect(Option.getOrThrow(active)).toEqual(
       expect.objectContaining({
         id: "user-1",

+ 6 - 6
packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts

@@ -46,8 +46,8 @@ function testAgent(input: {
 
 it.instance("[#26514] subagent spawned from plan mode inherits read-only restriction (edit denied)", () =>
   Effect.gen(function* () {
-    const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
-    const generalAgent = yield* Agent.Service.use((svc) => svc.get("general"))
+    const planAgent = yield* Agent.use.get("plan")
+    const generalAgent = yield* Agent.use.get("general")
 
     expect(planAgent).toBeDefined()
     expect(generalAgent).toBeDefined()
@@ -83,8 +83,8 @@ it.instance("[#26514] explore subagent launched from plan mode also stays read-o
   // should propagate the parent **agent** permissions, not just deny edit
   // when the subagent happens to already deny it.
   Effect.gen(function* () {
-    const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
-    const explore = yield* Agent.Service.use((svc) => svc.get("explore"))
+    const planAgent = yield* Agent.use.get("plan")
+    const explore = yield* Agent.use.get("explore")
     expect(planAgent).toBeDefined()
     expect(explore).toBeDefined()
 
@@ -108,8 +108,8 @@ it.instance(
   // be able to edit when the parent agent is `plan`.
   () =>
     Effect.gen(function* () {
-      const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
-      const my = yield* Agent.Service.use((svc) => svc.get("my_subagent"))
+      const planAgent = yield* Agent.use.get("plan")
+      const my = yield* Agent.use.get("my_subagent")
       expect(planAgent).toBeDefined()
       expect(my).toBeDefined()
 

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

@@ -51,7 +51,7 @@ it.instance(
   () =>
     Effect.gen(function* () {
       yield* Plugin.Service.use((p) => p.init())
-      const agents = yield* Agent.Service.use((svc) => svc.list())
+      const agents = yield* Agent.use.list()
       const added = agents.find((agent) => agent.name === PLUGIN_AGENT.name)
       expect(added?.description).toBe(PLUGIN_AGENT.description)
       expect(added?.mode).toBe(PLUGIN_AGENT.mode)

+ 3 - 3
packages/opencode/test/config/agent-color.test.ts

@@ -11,7 +11,7 @@ it.instance(
   "agent color parsed from project config",
   () =>
     Effect.gen(function* () {
-      const cfg = yield* Config.Service.use((svc) => svc.get())
+      const cfg = yield* Config.use.get()
       expect(cfg.agent?.["build"]?.color).toBe("#FFA500")
       expect(cfg.agent?.["plan"]?.color).toBe("primary")
     }),
@@ -30,9 +30,9 @@ it.instance(
   "Agent.get includes color from config",
   () =>
     Effect.gen(function* () {
-      const plan = yield* AgentSvc.Service.use((svc) => svc.get("plan"))
+      const plan = yield* AgentSvc.use.get("plan")
       expect(plan?.color).toBe("#A855F7")
-      const build = yield* AgentSvc.Service.use((svc) => svc.get("build"))
+      const build = yield* AgentSvc.use.get("build")
       expect(build?.color).toBe("accent")
     }),
   {

+ 43 - 43
packages/opencode/test/config/config.test.ts

@@ -70,14 +70,14 @@ const load = (ctx: InstanceContext) =>
   )
 const saveGlobal = (config: Config.Info) =>
   Effect.runPromise(
-    Config.Service.use((svc) => svc.updateGlobal(config)).pipe(
+    Config.use.updateGlobal(config).pipe(
       Effect.map((result) => result.info),
       Effect.scoped,
       Effect.provide(layer),
     ),
   )
 const clear = async (wait = false) => {
-  await Effect.runPromise(Config.Service.use((svc) => svc.invalidate()).pipe(Effect.scoped, Effect.provide(layer)))
+  await Effect.runPromise(Config.use.invalidate().pipe(Effect.scoped, Effect.provide(layer)))
   if (wait) await InstanceRuntime.disposeAllInstances()
 }
 const listDirs = (ctx: InstanceContext) =>
@@ -162,7 +162,7 @@ async function check(map: (dir: string) => string) {
 
 it.instance("loads config with defaults when no files exist", () =>
   Effect.gen(function* () {
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.username).toBeDefined()
   }),
 )
@@ -218,7 +218,7 @@ test("does not create global config when OPENCODE_CONFIG_DIR is set", async () =
 it.instance(
   "loads JSON config file",
   Effect.gen(function* () {
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.model).toBe("test/model")
     expect(config.username).toBe("testuser")
   }),
@@ -228,7 +228,7 @@ it.instance(
 it.instance(
   "loads shell config field",
   Effect.gen(function* () {
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.shell).toBe("bash")
   }),
   { config: { shell: "bash" } },
@@ -313,7 +313,7 @@ test("updates global config and omits empty shell key in jsonc", async () => {
 it.instance(
   "loads formatter boolean config",
   Effect.gen(function* () {
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.formatter).toBe(true)
   }),
   { config: { formatter: true } },
@@ -322,7 +322,7 @@ it.instance(
 it.instance(
   "loads lsp boolean config",
   Effect.gen(function* () {
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.lsp).toBe(true)
   }),
   { config: { lsp: true } },
@@ -355,7 +355,7 @@ it.instance("ignores legacy tui keys in opencode config", () =>
       tui: { scroll_speed: 4 },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.model).toBe("test/model")
     expect((config as Record<string, unknown>).theme).toBeUndefined()
     expect((config as Record<string, unknown>).tui).toBeUndefined()
@@ -376,7 +376,7 @@ it.instance("loads JSONC config file", () =>
       }`,
       ),
     )
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.model).toBe("test/model")
     expect(config.username).toBe("testuser")
   }),
@@ -398,7 +398,7 @@ it.instance("jsonc overrides json in the same directory", () =>
       $schema: "https://opencode.ai/config.json",
       model: "override",
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.model).toBe("base")
     expect(config.username).toBe("base")
   }),
@@ -414,7 +414,7 @@ it.instance("handles environment variable substitution", () =>
         $schema: "https://opencode.ai/config.json",
         username: "{env:TEST_VAR}",
       })
-      const config = yield* Config.Service.use((svc) => svc.get())
+      const config = yield* Config.use.get()
       expect(config.username).toBe("test-user")
     }),
   ),
@@ -435,7 +435,7 @@ it.instance("preserves env variables when adding $schema to config", () =>
           }),
         ),
       )
-      const config = yield* Config.Service.use((svc) => svc.get())
+      const config = yield* Config.use.get()
       expect(config.username).toBe("secret_value")
 
       // Read the file to verify the env variable was preserved
@@ -455,7 +455,7 @@ it.instance("handles file inclusion substitution", () =>
       $schema: "https://opencode.ai/config.json",
       username: "{file:included.txt}",
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.username).toBe("test-user")
   }),
 )
@@ -470,7 +470,7 @@ it.instance("handles file inclusion with replacement tokens", () =>
       $schema: "https://opencode.ai/config.json",
       username: "{file:included.md}",
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.username).toBe("const out = await Bun.$`echo hi`")
   }),
 )
@@ -547,7 +547,7 @@ it.instance("validates config schema and throws on invalid fields", () =>
       $schema: "https://opencode.ai/config.json",
       invalid_field: "should cause error",
     })
-    const exit = yield* Config.Service.use((svc) => svc.get()).pipe(Effect.exit)
+    const exit = yield* Config.use.get().pipe(Effect.exit)
     expect(Exit.isFailure(exit)).toBe(true)
   }),
 )
@@ -556,7 +556,7 @@ it.instance("throws error for invalid JSON", () =>
   Effect.gen(function* () {
     const test = yield* TestInstance
     yield* Effect.promise(() => Filesystem.write(path.join(test.directory, "opencode.json"), "{ invalid json }"))
-    const exit = yield* Config.Service.use((svc) => svc.get()).pipe(Effect.exit)
+    const exit = yield* Config.use.get().pipe(Effect.exit)
     expect(Exit.isFailure(exit)).toBe(true)
   }),
 )
@@ -574,7 +574,7 @@ it.instance("handles agent configuration", () =>
         },
       },
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test_agent"]).toEqual(
       expect.objectContaining({
         model: "test/model",
@@ -598,7 +598,7 @@ it.instance("treats agent variant as model-scoped setting (not provider option)"
         },
       },
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     const agent = config.agent?.["test_agent"]
 
     expect(agent?.variant).toBe("xhigh")
@@ -622,7 +622,7 @@ it.instance("handles command configuration", () =>
         },
       },
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.command?.["test_command"]).toEqual({
       template: "test template",
       description: "test command",
@@ -638,7 +638,7 @@ it.instance("migrates autoshare to share field", () =>
       $schema: "https://opencode.ai/config.json",
       autoshare: true,
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.share).toBe("auto")
     expect(config.autoshare).toBe(true)
   }),
@@ -656,7 +656,7 @@ it.instance("migrates mode field to agent field", () =>
         },
       },
     })
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test_mode"]).toEqual({
       model: "test/model",
       temperature: 0.5,
@@ -679,7 +679,7 @@ model: test/model
 Test agent prompt`,
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]).toEqual(
       expect.objectContaining({
         name: "test",
@@ -705,7 +705,7 @@ permission:
 Ordered permissions`,
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"])
   }),
 )
@@ -732,7 +732,7 @@ mode: subagent
 Nested agent prompt`,
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
 
     expect(config.agent?.["helper"]).toMatchObject({
       name: "helper",
@@ -770,7 +770,7 @@ description: Nested command
 Nested command template`,
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
 
     expect(config.command?.["hello"]).toEqual({
       description: "Test command",
@@ -804,7 +804,7 @@ description: Nested command
 Nested command template`,
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
 
     expect(config.command?.["hello"]).toEqual({
       description: "Test command",
@@ -834,7 +834,7 @@ it.instance("updates config and writes to file", () =>
 
 it.instance("gets config directories", () =>
   Effect.gen(function* () {
-    const dirs = yield* Config.Service.use((svc) => svc.directories())
+    const dirs = yield* Config.use.directories()
     expect(dirs.length).toBeGreaterThanOrEqual(1)
   }),
 )
@@ -950,7 +950,7 @@ it.instance("resolves scoped npm plugins in config", () =>
     yield* writeTextEffect(path.join(pluginDir, "index.js"), "export default {}\n")
     yield* writeConfigEffect(test.directory, { plugin: ["@scope/plugin"] })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.plugin ?? []).toContain("@scope/plugin")
   }),
 )
@@ -1014,7 +1014,7 @@ mode: subagent
 Helper subagent prompt`,
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["helper"]).toMatchObject({
       name: "helper",
       model: "test/model",
@@ -1212,7 +1212,7 @@ it.instance("migrates legacy tools config to permissions - allow", () =>
       agent: { test: { tools: { bash: true, read: true } } },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]?.permission).toEqual({
       bash: "allow",
       read: "allow",
@@ -1228,7 +1228,7 @@ it.instance("migrates legacy tools config to permissions - deny", () =>
       agent: { test: { tools: { bash: false, webfetch: false } } },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]?.permission).toEqual({
       bash: "deny",
       webfetch: "deny",
@@ -1244,7 +1244,7 @@ it.instance("migrates legacy write tool to edit permission", () =>
       agent: { test: { tools: { write: true } } },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]?.permission).toEqual({ edit: "allow" })
   }),
 )
@@ -1261,7 +1261,7 @@ it.instance(
       share: "disabled",
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.model).toBe("managed/model")
     expect(config.share).toBe("disabled")
     expect(config.username).toBe("testuser")
@@ -1278,7 +1278,7 @@ it.instance(
       disabled_providers: ["openai"],
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.autoupdate).toBe(false)
     expect(config.disabled_providers).toEqual(["openai"])
   }),
@@ -1288,7 +1288,7 @@ it.instance(
 it.instance(
   "missing managed settings file is not an error",
   Effect.gen(function* () {
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.model).toBe("user/model")
   }),
   { config: { model: "user/model" } },
@@ -1302,7 +1302,7 @@ it.instance("migrates legacy edit tool to edit permission", () =>
       agent: { test: { tools: { edit: false } } },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]?.permission).toEqual({ edit: "deny" })
   }),
 )
@@ -1315,7 +1315,7 @@ it.instance("migrates legacy patch tool to edit permission", () =>
       agent: { test: { tools: { patch: true } } },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]?.permission).toEqual({ edit: "allow" })
   }),
 )
@@ -1328,7 +1328,7 @@ it.instance("migrates mixed legacy tools config", () =>
       agent: { test: { tools: { bash: true, write: true, read: false, webfetch: true } } },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]?.permission).toEqual({
       bash: "allow",
       edit: "allow",
@@ -1346,7 +1346,7 @@ it.instance("merges legacy tools with existing permission config", () =>
       agent: { test: { permission: { glob: "allow" }, tools: { bash: true } } },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.agent?.["test"]?.permission).toEqual({
       glob: "allow",
       bash: "allow",
@@ -1375,7 +1375,7 @@ it.instance("permission config preserves user key order", () =>
       },
     })
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(Object.keys(config.permission!)).toEqual([
       "*",
       "edit",
@@ -1451,7 +1451,7 @@ it.instance("project config can override MCP server enabled status", () =>
       "opencode.jsonc",
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.mcp?.jira).toEqual({
       type: "remote",
       url: "https://jira.example.com/mcp",
@@ -1496,7 +1496,7 @@ it.instance("MCP config deep merges preserving base config properties", () =>
       "opencode.jsonc",
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.mcp?.myserver).toEqual({
       type: "remote",
       url: "https://myserver.example.com/mcp",
@@ -1537,7 +1537,7 @@ it.instance("local .opencode config can override MCP from project config", () =>
       "opencode.json",
     )
 
-    const config = yield* Config.Service.use((svc) => svc.get())
+    const config = yield* Config.use.get()
     expect(config.mcp?.docs?.enabled).toBe(true)
   }),
 )

+ 1 - 1
packages/opencode/test/config/tui.test.ts

@@ -93,7 +93,7 @@ it.instance("keeps server and tui plugin merge semantics aligned", () =>
         plugin: [["shared-plugin@2.0.0", { source: "local" }], "local-only@1.0.0"],
       })
 
-      const server = yield* Config.Service.use((svc) => svc.get())
+      const server = yield* Config.use.get()
       const tui = yield* getTuiConfig(test.directory)
       const serverPlugins = (server.plugin ?? []).map((item) => ConfigPlugin.pluginSpecifier(item))
       const tuiPlugins = (tui.plugin ?? []).map((item) => ConfigPlugin.pluginSpecifier(item))

+ 451 - 475
packages/opencode/test/control-plane/workspace.test.ts

@@ -5,31 +5,31 @@ import Http from "node:http"
 import path from "node:path"
 import { setTimeout as delay } from "node:timers/promises"
 import { NodeHttpServer } from "@effect/platform-node"
-import { Effect, Exit, Fiber, Layer, Schema } from "effect"
+import { Effect, Layer, Schema } from "effect"
 import { FetchHttpClient, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
 import { eq } from "drizzle-orm"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import * as Log from "@opencode-ai/core/util/log"
 import { GlobalBus, type GlobalEvent } from "@/bus/global"
 import { Database } from "@/storage/db"
 import { ProjectID } from "@/project/schema"
 import { ProjectTable } from "@/project/project.sql"
+import { context, type InstanceContext } from "@/project/instance-context"
 import { InstanceRef } from "@/effect/instance-ref"
-import { InstanceState } from "@/effect/instance-state"
 import { Session as SessionNs } from "@/session/session"
 import { SessionID } from "@/session/schema"
 import { SessionTable } from "@/session/session.sql"
 import { SyncEvent } from "@/sync"
 import { EventSequenceTable } from "@/sync/event.sql"
 import { resetDatabase } from "../fixture/db"
-import { disposeAllInstances, provideTmpdirInstance, TestInstance, tmpdirScoped } from "../fixture/fixture"
+import { disposeAllInstances, provideTmpdirInstance, TestInstance, tmpdir } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 import { registerAdapter } from "../../src/control-plane/adapters"
 import { WorkspaceID } from "../../src/control-plane/schema"
 import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
 import type { Target, WorkspaceAdapter, WorkspaceInfo } from "../../src/control-plane/types"
 import * as Workspace from "../../src/control-plane/workspace"
+import { AppRuntime } from "@/effect/app-runtime"
 import { InstanceStore } from "@/project/instance-store"
 import { InstanceBootstrap } from "@/project/bootstrap"
 import { Auth } from "@/auth"
@@ -66,8 +66,6 @@ const testServerLayer = Layer.mergeAll(
   NodeHttpServer.layer(Http.createServer, { host: "127.0.0.1", port: 0 }),
   workspaceLayer(true),
   SessionNs.defaultLayer,
-  InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer)),
-  CrossSpawnSpawner.defaultLayer,
 )
 const it = testEffect(testServerLayer)
 
@@ -123,6 +121,12 @@ afterEach(async () => {
   await resetDatabase()
 })
 
+async function withInstance<T>(fn: (ctx: InstanceContext) => T | Promise<T>) {
+  await using tmp = await tmpdir({ git: true })
+  const ctx = await AppRuntime.runPromise(InstanceStore.use.load({ directory: tmp.path }))
+  return await context.provide(ctx, () => fn(ctx))
+}
+
 async function initGitRepo(dir: string) {
   await fs.mkdir(dir, { recursive: true })
   await $`git init`.cwd(dir).quiet()
@@ -135,6 +139,47 @@ async function initGitRepo(dir: string) {
   await $`git commit -m "base"`.cwd(dir).quiet()
 }
 
+function currentInstance() {
+  try {
+    return context.use()
+  } catch {
+    return undefined
+  }
+}
+
+const runWorkspace = <A, E>(effect: Effect.Effect<A, E, Workspace.Service>) => {
+  const ctx = currentInstance()
+  return AppRuntime.runPromise(ctx ? effect.pipe(Effect.provideService(InstanceRef, ctx)) : effect)
+}
+const createWorkspace = (input: Workspace.CreateInput) =>
+  runWorkspace(Workspace.use.create(input))
+const warpWorkspaceSession = (input: Workspace.SessionWarpInput) =>
+  runWorkspace(Workspace.use.sessionWarp(input))
+const listWorkspaces = (project: Parameters<Workspace.Interface["list"]>[0]) =>
+  runWorkspace(Workspace.use.list(project))
+const syncListWorkspaces = (project: Parameters<Workspace.Interface["syncList"]>[0]) =>
+  runWorkspace(Workspace.use.syncList(project))
+const getWorkspace = (id: WorkspaceID) => runWorkspace(Workspace.use.get(id))
+const removeWorkspace = (id: WorkspaceID) => runWorkspace(Workspace.use.remove(id))
+const workspaceStatus = () => runWorkspace(Workspace.use.status())
+const isWorkspaceSyncing = (id: WorkspaceID) =>
+  runWorkspace(Workspace.use.isSyncing(id))
+const startWorkspaceSyncing = (projectID: ProjectID) => {
+  void runWorkspace(Workspace.use.startWorkspaceSyncing(projectID))
+}
+const startWorkspaceSyncingWithFlag = (projectID: ProjectID, experimentalWorkspaces: boolean) =>
+  Effect.runPromise(
+    Workspace.use.startWorkspaceSyncing(projectID).pipe(
+      Effect.provide(workspaceLayer(experimentalWorkspaces)),
+    ),
+  )
+const waitForWorkspaceSync = (
+  workspaceID: WorkspaceID,
+  state: Record<string, number>,
+  signal?: AbortSignal,
+  timeout?: number,
+) => runWorkspace(Workspace.use.waitForSync(workspaceID, state, signal, timeout))
+
 function captureGlobalEvents() {
   const events: GlobalEvent[] = []
   const handler = (event: GlobalEvent) => events.push(event)
@@ -376,326 +421,289 @@ describe("workspace schemas and exports", () => {
 })
 
 describe("workspace CRUD", () => {
-  it.instance(
-    "get returns undefined for a missing workspace",
-    () =>
-      Effect.gen(function* () {
-        expect(yield* Workspace.use.get(WorkspaceID.ascending("wrk_missing_get"))).toBeUndefined()
-      }),
-    { git: true },
-  )
-
-  it.instance(
-    "list maps database rows, filters by project, and sorts by id",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const otherProjectID = ProjectID.make("project-other")
-        insertProject(otherProjectID, "/tmp/other")
-        const a = workspaceInfo(instance.project.id, "manual", {
-          id: WorkspaceID.ascending("wrk_a_list"),
-          branch: "a",
-          directory: "/a",
-          extra: { a: true },
-        })
-        const b = workspaceInfo(instance.project.id, "manual", {
-          id: WorkspaceID.ascending("wrk_b_list"),
-          branch: "b",
-          directory: "/b",
-          extra: ["b"],
-        })
-        const other = workspaceInfo(otherProjectID, "manual", { id: WorkspaceID.ascending("wrk_c_list") })
-        insertWorkspace(b)
-        insertWorkspace(other)
-        insertWorkspace(a)
-
-        expect(yield* Workspace.use.list(instance.project)).toEqual([a, b])
-      }),
-    { git: true },
-  )
-
-  it.instance(
-    "create configures, persists, creates, starts local sync, and passes environment",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        process.env.OPENCODE_AUTH_CONTENT = JSON.stringify({ test: { type: "api", key: "secret" } })
-        process.env.OTEL_EXPORTER_OTLP_HEADERS = "authorization=otel"
-        process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://otel.test"
-        process.env.OTEL_RESOURCE_ATTRIBUTES = "service.name=opencode-test"
-
-        const workspaceID = WorkspaceID.ascending("wrk_create_local")
-        const type = unique("create-local")
-        const targetDir = path.join(instance.directory, "created-local")
-        const recorded = recordedAdapter({
-          configure(info) {
-            return {
-              ...info,
-              branch: "configured-branch",
-              name: "Configured Name",
-              directory: targetDir,
-              extra: { configured: true },
-            }
-          },
-          async create() {
-            await fs.mkdir(targetDir, { recursive: true })
-          },
-          target() {
-            return { type: "local", directory: targetDir }
-          },
-        })
-        registerAdapter(instance.project.id, type, recorded.adapter)
-
-        const info = yield* Workspace.use.create({
-          id: workspaceID,
-          type,
-          branch: null,
-          projectID: instance.project.id,
-          extra: null,
-        })
+  test("get returns undefined for a missing workspace", async () => {
+    await withInstance(async () => {
+      expect(await getWorkspace(WorkspaceID.ascending("wrk_missing_get"))).toBeUndefined()
+    })
+  })
 
-        expect(info).toEqual({
-          id: workspaceID,
-          type,
-          branch: "configured-branch",
-          name: "Configured Name",
-          directory: targetDir,
-          extra: { configured: true },
-          projectID: instance.project.id,
-          timeUsed: info.timeUsed,
-        })
-        expect(yield* Workspace.use.get(workspaceID)).toEqual(info)
-        expect(yield* Workspace.use.list(instance.project)).toEqual([info])
-        expect(recorded.calls.configure).toHaveLength(1)
-        expect(recorded.calls.configure[0]).toMatchObject({ id: workspaceID, type, directory: null })
-        expect(recorded.calls.create).toHaveLength(1)
-        expect(recorded.calls.create[0].info).toEqual({
-          id: workspaceID,
-          type,
-          branch: "configured-branch",
-          name: "Configured Name",
-          directory: targetDir,
-          extra: { configured: true },
-          projectID: instance.project.id,
-        })
-        expect(JSON.parse(recorded.calls.create[0].env.OPENCODE_AUTH_CONTENT ?? "{}")).toEqual({
-          test: { type: "api", key: "secret" },
-        })
-        expect(recorded.calls.create[0].env.OPENCODE_WORKSPACE_ID).toBe(workspaceID)
-        expect(recorded.calls.create[0].env.OPENCODE_EXPERIMENTAL_WORKSPACES).toBe("true")
-        expect(recorded.calls.create[0].env.OTEL_EXPORTER_OTLP_HEADERS).toBe("authorization=otel")
-        expect(recorded.calls.create[0].env.OTEL_EXPORTER_OTLP_ENDPOINT).toBe("https://otel.test")
-        expect(recorded.calls.create[0].env.OTEL_RESOURCE_ATTRIBUTES).toBe("service.name=opencode-test")
-        expect((yield* Workspace.use.status()).find((item) => item.workspaceID === workspaceID)?.status).toBe(
-          "connected",
-        )
+  test("list maps database rows, filters by project, and sorts by id", async () => {
+    await withInstance(async (instance) => {
+      const otherProjectID = ProjectID.make("project-other")
+      insertProject(otherProjectID, "/tmp/other")
+      const a = workspaceInfo(instance.project.id, "manual", {
+        id: WorkspaceID.ascending("wrk_a_list"),
+        branch: "a",
+        directory: "/a",
+        extra: { a: true },
+      })
+      const b = workspaceInfo(instance.project.id, "manual", {
+        id: WorkspaceID.ascending("wrk_b_list"),
+        branch: "b",
+        directory: "/b",
+        extra: ["b"],
+      })
+      const other = workspaceInfo(otherProjectID, "manual", { id: WorkspaceID.ascending("wrk_c_list") })
+      insertWorkspace(b)
+      insertWorkspace(other)
+      insertWorkspace(a)
 
-        yield* Workspace.use.remove(workspaceID)
-        expect((yield* Workspace.use.status()).find((item) => item.workspaceID === workspaceID)?.status).toBeUndefined()
-      }),
-    { git: true },
-  )
+      expect(await listWorkspaces(instance.project)).toEqual([a, b])
+    })
+  })
 
-  it.instance(
-    "create propagates configure failures and does not insert a workspace",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const type = unique("configure-failure")
-        registerAdapter(
-          instance.project.id,
-          type,
-          recordedAdapter({
-            configure() {
-              throw new Error("configure exploded")
-            },
-            target() {
-              return { type: "local", directory: "/unused" }
-            },
-          }).adapter,
-        )
+  test("create configures, persists, creates, starts local sync, and passes environment", async () => {
+    await withInstance(async (instance) => {
+      process.env.OPENCODE_AUTH_CONTENT = JSON.stringify({ test: { type: "api", key: "secret" } })
+      process.env.OTEL_EXPORTER_OTLP_HEADERS = "authorization=otel"
+      process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://otel.test"
+      process.env.OTEL_RESOURCE_ATTRIBUTES = "service.name=opencode-test"
+
+      const workspaceID = WorkspaceID.ascending("wrk_create_local")
+      const type = unique("create-local")
+      const targetDir = path.join(instance.directory, "created-local")
+      const recorded = recordedAdapter({
+        configure(info) {
+          return {
+            ...info,
+            branch: "configured-branch",
+            name: "Configured Name",
+            directory: targetDir,
+            extra: { configured: true },
+          }
+        },
+        async create() {
+          await fs.mkdir(targetDir, { recursive: true })
+        },
+        target() {
+          return { type: "local", directory: targetDir }
+        },
+      })
+      registerAdapter(instance.project.id, type, recorded.adapter)
+
+      const info = await createWorkspace({
+        id: workspaceID,
+        type,
+        branch: null,
+        projectID: instance.project.id,
+        extra: null,
+      })
 
-        const exit = yield* Workspace.use
-          .create({ type, branch: null, projectID: instance.project.id, extra: null })
-          .pipe(Effect.exit)
-        expect(Exit.isFailure(exit)).toBe(true)
-        if (Exit.isFailure(exit)) expect(String(exit.cause)).toContain("configure exploded")
-        expect(yield* Workspace.use.list(instance.project)).toEqual([])
-      }),
-    { git: true },
-  )
+      expect(info).toEqual({
+        id: workspaceID,
+        type,
+        branch: "configured-branch",
+        name: "Configured Name",
+        directory: targetDir,
+        extra: { configured: true },
+        projectID: instance.project.id,
+        timeUsed: info.timeUsed,
+      })
+      expect(await getWorkspace(workspaceID)).toEqual(info)
+      expect(await listWorkspaces(instance.project)).toEqual([info])
+      expect(recorded.calls.configure).toHaveLength(1)
+      expect(recorded.calls.configure[0]).toMatchObject({ id: workspaceID, type, directory: null })
+      expect(recorded.calls.create).toHaveLength(1)
+      expect(recorded.calls.create[0].info).toEqual({
+        id: workspaceID,
+        type,
+        branch: "configured-branch",
+        name: "Configured Name",
+        directory: targetDir,
+        extra: { configured: true },
+        projectID: instance.project.id,
+      })
+      expect(JSON.parse(recorded.calls.create[0].env.OPENCODE_AUTH_CONTENT ?? "{}")).toEqual({
+        test: { type: "api", key: "secret" },
+      })
+      expect(recorded.calls.create[0].env.OPENCODE_WORKSPACE_ID).toBe(workspaceID)
+      expect(recorded.calls.create[0].env.OPENCODE_EXPERIMENTAL_WORKSPACES).toBe("true")
+      expect(recorded.calls.create[0].env.OTEL_EXPORTER_OTLP_HEADERS).toBe("authorization=otel")
+      expect(recorded.calls.create[0].env.OTEL_EXPORTER_OTLP_ENDPOINT).toBe("https://otel.test")
+      expect(recorded.calls.create[0].env.OTEL_RESOURCE_ATTRIBUTES).toBe("service.name=opencode-test")
+      expect((await workspaceStatus()).find((item) => item.workspaceID === workspaceID)?.status).toBe("connected")
+
+      await removeWorkspace(workspaceID)
+      expect((await workspaceStatus()).find((item) => item.workspaceID === workspaceID)?.status).toBeUndefined()
+    })
+  })
 
-  it.instance(
-    "create leaves the inserted row when adapter create fails",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const type = unique("create-failure")
-        const recorded = recordedAdapter({
-          async create() {
-            throw new Error("create exploded")
+  test("create propagates configure failures and does not insert a workspace", async () => {
+    await withInstance(async (instance) => {
+      const type = unique("configure-failure")
+      registerAdapter(
+        instance.project.id,
+        type,
+        recordedAdapter({
+          configure() {
+            throw new Error("configure exploded")
           },
           target() {
             return { type: "local", directory: "/unused" }
           },
-        })
-        registerAdapter(instance.project.id, type, recorded.adapter)
-
-        const exit = yield* Workspace.use
-          .create({ type, branch: "branch", projectID: instance.project.id, extra: { x: 1 } })
-          .pipe(Effect.exit)
-        expect(Exit.isFailure(exit)).toBe(true)
-        if (Exit.isFailure(exit)) expect(String(exit.cause)).toContain("create exploded")
-
-        const rows = yield* Workspace.use.list(instance.project)
-        expect(rows).toHaveLength(1)
-        expect(rows[0]).toMatchObject({ type, branch: "branch", extra: { x: 1 } })
-        expect(recorded.calls.target).toHaveLength(0)
-        yield* Workspace.use.remove(rows[0].id)
-      }),
-    { git: true },
-  )
+        }).adapter,
+      )
 
-  it.instance(
-    "create returns after a local workspace reports error",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const type = unique("local-error")
-        const missing = path.join(instance.directory, "missing-local-target")
-        const recorded = localAdapter(missing, { createDir: false })
-        registerAdapter(instance.project.id, type, recorded.adapter)
+      await expect(
+        createWorkspace({ type, branch: null, projectID: instance.project.id, extra: null }),
+      ).rejects.toThrow("configure exploded")
+      expect(await listWorkspaces(instance.project)).toEqual([])
+    })
+  })
 
-        const info = yield* Workspace.use.create({ type, branch: null, projectID: instance.project.id, extra: null })
+  test("create leaves the inserted row when adapter create fails", async () => {
+    await withInstance(async (instance) => {
+      const type = unique("create-failure")
+      const recorded = recordedAdapter({
+        async create() {
+          throw new Error("create exploded")
+        },
+        target() {
+          return { type: "local", directory: "/unused" }
+        },
+      })
+      registerAdapter(instance.project.id, type, recorded.adapter)
 
-        expect(info.directory).toBe(missing)
-        expect((yield* Workspace.use.status()).find((item) => item.workspaceID === info.id)?.status).toBe("error")
-        yield* Workspace.use.remove(info.id)
-      }),
-    { git: true },
-  )
+      await expect(
+        createWorkspace({ type, branch: "branch", projectID: instance.project.id, extra: { x: 1 } }),
+      ).rejects.toThrow("create exploded")
 
-  it.instance(
-    "syncList registers adapter-listed workspaces that are missing by name",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const type = unique("list-sync")
-        const existing = workspaceInfo(instance.project.id, type, {
-          id: WorkspaceID.ascending("wrk_list_sync_existing"),
-          name: "existing",
-          directory: path.join(instance.directory, "existing"),
-        })
-        insertWorkspace(existing)
+      const rows = await listWorkspaces(instance.project)
+      expect(rows).toHaveLength(1)
+      expect(rows[0]).toMatchObject({ type, branch: "branch", extra: { x: 1 } })
+      expect(recorded.calls.target).toHaveLength(0)
+      await removeWorkspace(rows[0].id)
+    })
+  })
 
-        const discovered = {
-          type,
-          name: "discovered",
-          branch: "feature/discovered",
-          directory: path.join(instance.directory, "discovered"),
-          extra: { source: "adapter" },
-          projectID: instance.project.id,
-        }
-        const recorded = recordedAdapter({
-          list() {
-            return [
-              {
-                type,
-                name: existing.name,
-                branch: "ignored",
-                directory: path.join(instance.directory, "ignored"),
-                extra: null,
-                projectID: instance.project.id,
-              },
-              discovered,
-            ]
-          },
-          target(info) {
-            return { type: "local", directory: info.directory ?? instance.directory }
-          },
-        })
-        registerAdapter(instance.project.id, type, recorded.adapter)
+  test("create returns after a local workspace reports error", async () => {
+    await withInstance(async (instance) => {
+      const type = unique("local-error")
+      const missing = path.join(instance.directory, "missing-local-target")
+      const recorded = localAdapter(missing, { createDir: false })
+      registerAdapter(instance.project.id, type, recorded.adapter)
 
-        yield* Workspace.use.syncList(instance.project)
-        const synced = (yield* Workspace.use.list(instance.project)).filter((item) => item.name === discovered.name)
-
-        expect(synced).toHaveLength(1)
-        expect(synced[0]).toMatchObject(discovered)
-        expect(synced[0]?.id).toStartWith("wrk_")
-        expect(yield* Workspace.use.list(instance.project)).toEqual(expect.arrayContaining([existing, synced[0]]))
-        expect(recorded.calls.list).toBe(1)
-        expect(recorded.calls.configure).toHaveLength(0)
-        expect(recorded.calls.create).toHaveLength(0)
-        expect(recorded.calls.target).toHaveLength(1)
-      }),
-    { git: true },
-  )
+      const info = await createWorkspace({ type, branch: null, projectID: instance.project.id, extra: null })
 
-  it.instance(
-    "syncList calls every registered adapter with a list method",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const typeA = unique("list-sync-a")
-        const typeB = unique("list-sync-b")
-        const adapterA = recordedAdapter({
-          list() {
-            return [
-              {
-                type: typeA,
-                name: "adapter-a",
-                branch: null,
-                directory: path.join(instance.directory, "adapter-a"),
-                extra: null,
-                projectID: instance.project.id,
-              },
-            ]
-          },
-          target(info) {
-            return { type: "local", directory: info.directory ?? instance.directory }
-          },
-        })
-        const adapterB = recordedAdapter({
-          list() {
-            return [
-              {
-                type: typeB,
-                name: "adapter-b",
-                branch: null,
-                directory: path.join(instance.directory, "adapter-b"),
-                extra: null,
-                projectID: instance.project.id,
-              },
-            ]
-          },
-          target(info) {
-            return { type: "local", directory: info.directory ?? instance.directory }
-          },
-        })
-        const noList = recordedAdapter({
-          target() {
-            return { type: "local", directory: instance.directory }
-          },
-        })
-        registerAdapter(instance.project.id, typeA, adapterA.adapter)
-        registerAdapter(instance.project.id, typeB, adapterB.adapter)
-        registerAdapter(instance.project.id, unique("list-sync-none"), noList.adapter)
+      expect(info.directory).toBe(missing)
+      expect((await workspaceStatus()).find((item) => item.workspaceID === info.id)?.status).toBe("error")
+      await removeWorkspace(info.id)
+    })
+  })
 
-        yield* Workspace.use.syncList(instance.project)
-        const synced = yield* Workspace.use.list(instance.project)
+  test("syncList registers adapter-listed workspaces that are missing by name", async () => {
+    await withInstance(async (instance) => {
+      const type = unique("list-sync")
+      const existing = workspaceInfo(instance.project.id, type, {
+        id: WorkspaceID.ascending("wrk_list_sync_existing"),
+        name: "existing",
+        directory: path.join(instance.directory, "existing"),
+      })
+      insertWorkspace(existing)
+
+      const discovered = {
+        type,
+        name: "discovered",
+        branch: "feature/discovered",
+        directory: path.join(instance.directory, "discovered"),
+        extra: { source: "adapter" },
+        projectID: instance.project.id,
+      }
+      const recorded = recordedAdapter({
+        list() {
+          return [
+            {
+              type,
+              name: existing.name,
+              branch: "ignored",
+              directory: path.join(instance.directory, "ignored"),
+              extra: null,
+              projectID: instance.project.id,
+            },
+            discovered,
+          ]
+        },
+        target(info) {
+          return { type: "local", directory: info.directory ?? instance.directory }
+        },
+      })
+      registerAdapter(instance.project.id, type, recorded.adapter)
+
+      await syncListWorkspaces(instance.project)
+      const synced = (await listWorkspaces(instance.project)).filter((item) => item.name === discovered.name)
+
+      expect(synced).toHaveLength(1)
+      expect(synced[0]).toMatchObject(discovered)
+      expect(synced[0]?.id).toStartWith("wrk_")
+      expect(await listWorkspaces(instance.project)).toEqual(expect.arrayContaining([existing, synced[0]]))
+      expect(recorded.calls.list).toBe(1)
+      expect(recorded.calls.configure).toHaveLength(0)
+      expect(recorded.calls.create).toHaveLength(0)
+      expect(recorded.calls.target).toHaveLength(1)
+    })
+  })
 
-        expect(
-          synced
-            .filter((item) => item.type === typeA || item.type === typeB)
-            .map((item) => item.name)
-            .toSorted(),
-        ).toEqual(["adapter-a", "adapter-b"])
-        expect(adapterA.calls.list).toBe(1)
-        expect(adapterB.calls.list).toBe(1)
-        expect(noList.calls.list).toBe(0)
-      }),
-    { git: true },
-  )
+  test("syncList calls every registered adapter with a list method", async () => {
+    await withInstance(async (instance) => {
+      const typeA = unique("list-sync-a")
+      const typeB = unique("list-sync-b")
+      const adapterA = recordedAdapter({
+        list() {
+          return [
+            {
+              type: typeA,
+              name: "adapter-a",
+              branch: null,
+              directory: path.join(instance.directory, "adapter-a"),
+              extra: null,
+              projectID: instance.project.id,
+            },
+          ]
+        },
+        target(info) {
+          return { type: "local", directory: info.directory ?? instance.directory }
+        },
+      })
+      const adapterB = recordedAdapter({
+        list() {
+          return [
+            {
+              type: typeB,
+              name: "adapter-b",
+              branch: null,
+              directory: path.join(instance.directory, "adapter-b"),
+              extra: null,
+              projectID: instance.project.id,
+            },
+          ]
+        },
+        target(info) {
+          return { type: "local", directory: info.directory ?? instance.directory }
+        },
+      })
+      const noList = recordedAdapter({
+        target() {
+          return { type: "local", directory: instance.directory }
+        },
+      })
+      registerAdapter(instance.project.id, typeA, adapterA.adapter)
+      registerAdapter(instance.project.id, typeB, adapterB.adapter)
+      registerAdapter(instance.project.id, unique("list-sync-none"), noList.adapter)
+
+      await syncListWorkspaces(instance.project)
+      const synced = await listWorkspaces(instance.project)
+
+      expect(
+        synced
+          .filter((item) => item.type === typeA || item.type === typeB)
+          .map((item) => item.name)
+          .toSorted(),
+      ).toEqual(["adapter-a", "adapter-b"])
+      expect(adapterA.calls.list).toBe(1)
+      expect(adapterB.calls.list).toBe(1)
+      expect(noList.calls.list).toBe(0)
+    })
+  })
 
   it.live("remote create connects to routed event and history endpoints", () => {
     const calls: FetchCall[] = []
@@ -747,14 +755,11 @@ describe("workspace CRUD", () => {
     })
   })
 
-  it.instance(
-    "remove returns undefined for a missing workspace",
-    () =>
-      Effect.gen(function* () {
-        expect(yield* Workspace.use.remove(WorkspaceID.ascending("wrk_missing_remove"))).toBeUndefined()
-      }),
-    { git: true },
-  )
+  test("remove returns undefined for a missing workspace", async () => {
+    await withInstance(async () => {
+      expect(await removeWorkspace(WorkspaceID.ascending("wrk_missing_remove"))).toBeUndefined()
+    })
+  })
 
   it.instance(
     "remove deletes the workspace, associated sessions, adapter resources, and status",
@@ -790,32 +795,28 @@ describe("workspace CRUD", () => {
     { git: true },
   )
 
-  it.instance(
-    "remove still deletes the row when the adapter cannot remove resources",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const type = unique("remove-throws")
-        const info = workspaceInfo(instance.project.id, type, { id: WorkspaceID.ascending("wrk_remove_throws") })
-        registerAdapter(
-          instance.project.id,
-          type,
-          recordedAdapter({
-            async remove() {
-              throw new Error("remove exploded")
-            },
-            target() {
-              return { type: "local", directory: "/unused" }
-            },
-          }).adapter,
-        )
-        insertWorkspace(info)
+  test("remove still deletes the row when the adapter cannot remove resources", async () => {
+    await withInstance(async (instance) => {
+      const type = unique("remove-throws")
+      const info = workspaceInfo(instance.project.id, type, { id: WorkspaceID.ascending("wrk_remove_throws") })
+      registerAdapter(
+        instance.project.id,
+        type,
+        recordedAdapter({
+          async remove() {
+            throw new Error("remove exploded")
+          },
+          target() {
+            return { type: "local", directory: "/unused" }
+          },
+        }).adapter,
+      )
+      insertWorkspace(info)
 
-        expect(yield* Workspace.use.remove(info.id)).toEqual(info)
-        expect(yield* Workspace.use.get(info.id)).toBeUndefined()
-      }),
-    { git: true },
-  )
+      expect(await removeWorkspace(info.id)).toEqual(info)
+      expect(await getWorkspace(info.id)).toBeUndefined()
+    })
+  })
 
   it.instance(
     "sessionWarp moves a session into a local workspace and claims ownership",
@@ -923,40 +924,42 @@ describe("workspace CRUD", () => {
     { git: true },
   )
 
-  it.instance(
-    "sessionWarp detaches to the source project when invoked from a workspace instance",
-    () =>
-      Effect.gen(function* () {
-        const instance = yield* InstanceState.context
-        const projectID = instance.project.id
-        const workspaceDir = yield* tmpdirScoped({ git: true })
-        const previousType = unique("warp-detach-workspace-instance")
-        const previous = workspaceInfo(projectID, previousType)
-        insertWorkspace(previous)
-        registerAdapter(projectID, previousType, localAdapter(workspaceDir, { createDir: false }).adapter)
-        const session = yield* SessionNs.use.create({})
-        attachSessionToWorkspace(session.id, previous.id)
+  test("sessionWarp detaches to the source project when invoked from a workspace instance", async () => {
+    await withInstance(async (instance) => {
+      const projectID = instance.project.id
+      await using workspaceTmp = await tmpdir({ git: true })
+      const previousType = unique("warp-detach-workspace-instance")
+      const previous = workspaceInfo(projectID, previousType)
+      insertWorkspace(previous)
+      registerAdapter(projectID, previousType, localAdapter(workspaceTmp.path, { createDir: false }).adapter)
+      const session = await AppRuntime.runPromise(
+        SessionNs.use.create({}).pipe(Effect.provideService(InstanceRef, instance)),
+      )
+      attachSessionToWorkspace(session.id, previous.id)
 
-        const workspaceCtx = yield* InstanceStore.Service.use((store) => store.load({ directory: workspaceDir }))
-        expect(workspaceCtx.project.id).not.toBe(projectID)
-        yield* Workspace.use
-          .sessionWarp({ workspaceID: null, sessionID: session.id })
-          .pipe(Effect.provideService(InstanceRef, workspaceCtx))
+      const workspaceCtx = await AppRuntime.runPromise(
+        InstanceStore.use.load({ directory: workspaceTmp.path }),
+      )
+      const workspaceProjectID = await context.provide(workspaceCtx, async () => {
+        const id = workspaceCtx.project.id
+        expect(id).not.toBe(projectID)
+        await warpWorkspaceSession({ workspaceID: null, sessionID: session.id })
+        return id
+      })
 
-        expect(
-          Database.use((db) =>
-            db
-              .select({ workspaceID: SessionTable.workspace_id })
-              .from(SessionTable)
-              .where(eq(SessionTable.id, session.id))
-              .get(),
-          )?.workspaceID,
-        ).toBeNull()
-        expect(sessionSequenceOwner(session.id)).toBe(projectID)
-        expect(sessionSequenceOwner(session.id)).not.toBe(workspaceCtx.project.id)
-      }),
-    { git: true },
-  )
+      expect(
+        Database.use((db) =>
+          db
+            .select({ workspaceID: SessionTable.workspace_id })
+            .from(SessionTable)
+            .where(eq(SessionTable.id, session.id))
+            .get(),
+        )?.workspaceID,
+      ).toBeNull()
+      expect(sessionSequenceOwner(session.id)).toBe(projectID)
+      expect(sessionSequenceOwner(session.id)).not.toBe(workspaceProjectID)
+    })
+  })
 
   it.live("sessionWarp syncs previous remote history, replays it, steals, and claims the sequence", () => {
     const calls: FetchCall[] = []
@@ -1069,12 +1072,7 @@ describe("workspace sync state", () => {
         insertWorkspace(info)
         registerAdapter(instance.project.id, type, localAdapter(path.join(dir, "flag-disabled")).adapter)
 
-        // Isolated runtime with experimentalWorkspaces=false so we can verify the flag gates sync.
-        yield* Effect.promise(() =>
-          Effect.runPromise(
-            Workspace.use.startWorkspaceSyncing(instance.project.id).pipe(Effect.provide(workspaceLayer(false))),
-          ),
-        )
+        yield* Effect.promise(() => startWorkspaceSyncingWithFlag(instance.project.id, false))
         yield* Effect.sleep("25 millis")
 
         expect((yield* workspace.status()).find((item) => item.workspaceID === info.id)?.status).toBeUndefined()
@@ -1565,111 +1563,89 @@ describe("workspace sync state", () => {
 })
 
 describe("workspace waitForSync", () => {
-  it.instance(
-    "returns immediately for an empty fence",
-    () =>
-      Effect.gen(function* () {
-        expect(yield* Workspace.use.waitForSync(WorkspaceID.ascending("wrk_wait_empty"), {})).toBeUndefined()
-      }),
-    { git: true },
-  )
+  test("returns immediately for an empty fence", async () => {
+    await withInstance(async () => {
+      await expect(waitForWorkspaceSync(WorkspaceID.ascending("wrk_wait_empty"), {})).resolves.toBeUndefined()
+    })
+  })
 
-  it.instance(
-    "returns immediately when the stored sequence already satisfies the fence",
-    () =>
-      Effect.gen(function* () {
-        const sessionID = SessionID.descending("ses_wait_done")
-        Database.use((db) => db.insert(EventSequenceTable).values({ aggregate_id: sessionID, seq: 4 }).run())
+  test("returns immediately when the stored sequence already satisfies the fence", async () => {
+    await withInstance(async () => {
+      const sessionID = SessionID.descending("ses_wait_done")
+      Database.use((db) => db.insert(EventSequenceTable).values({ aggregate_id: sessionID, seq: 4 }).run())
+
+      await expect(
+        waitForWorkspaceSync(WorkspaceID.ascending("wrk_wait_done"), { [sessionID]: 4 }),
+      ).resolves.toBeUndefined()
+      await expect(
+        waitForWorkspaceSync(WorkspaceID.ascending("wrk_wait_done_2"), { [sessionID]: 3 }),
+      ).resolves.toBeUndefined()
+    })
+  })
 
-        expect(
-          yield* Workspace.use.waitForSync(WorkspaceID.ascending("wrk_wait_done"), { [sessionID]: 4 }),
-        ).toBeUndefined()
-        expect(
-          yield* Workspace.use.waitForSync(WorkspaceID.ascending("wrk_wait_done_2"), { [sessionID]: 3 }),
-        ).toBeUndefined()
-      }),
-    { git: true },
-  )
+  test("waits until the database reaches the requested sequence and a workspace event arrives", async () => {
+    await withInstance(async () => {
+      const workspaceID = WorkspaceID.ascending("wrk_wait_event")
+      const sessionID = SessionID.descending("ses_wait_event")
+      Database.use((db) => db.insert(EventSequenceTable).values({ aggregate_id: sessionID, seq: 1 }).run())
 
-  it.instance(
-    "waits until the database reaches the requested sequence and a workspace event arrives",
-    () =>
-      Effect.gen(function* () {
-        const workspaceID = WorkspaceID.ascending("wrk_wait_event")
-        const sessionID = SessionID.descending("ses_wait_event")
-        Database.use((db) => db.insert(EventSequenceTable).values({ aggregate_id: sessionID, seq: 1 }).run())
+      const waited = waitForWorkspaceSync(workspaceID, { [sessionID]: 2 })
+      await delay(10)
+      Database.use((db) =>
+        db.update(EventSequenceTable).set({ seq: 2 }).where(eq(EventSequenceTable.aggregate_id, sessionID)).run(),
+      )
+      GlobalBus.emit("event", { workspace: workspaceID, payload: { type: "anything" } })
 
-        const waited = yield* Workspace.use.waitForSync(workspaceID, { [sessionID]: 2 }).pipe(Effect.forkScoped)
-        yield* Effect.sleep("10 millis")
-        Database.use((db) =>
-          db.update(EventSequenceTable).set({ seq: 2 }).where(eq(EventSequenceTable.aggregate_id, sessionID)).run(),
-        )
-        GlobalBus.emit("event", { workspace: workspaceID, payload: { type: "anything" } })
+      await expect(waited).resolves.toBeUndefined()
+    })
+  })
 
-        expect(yield* Fiber.join(waited)).toBeUndefined()
-      }),
-    { git: true },
-  )
+  test("a sync event for a different workspace can also release the fence", async () => {
+    await withInstance(async () => {
+      const workspaceID = WorkspaceID.ascending("wrk_wait_sync_any")
+      const sessionID = SessionID.descending("ses_wait_sync_any")
+      Database.use((db) => db.insert(EventSequenceTable).values({ aggregate_id: sessionID, seq: 0 }).run())
 
-  it.instance(
-    "a sync event for a different workspace can also release the fence",
-    () =>
-      Effect.gen(function* () {
-        const workspaceID = WorkspaceID.ascending("wrk_wait_sync_any")
-        const sessionID = SessionID.descending("ses_wait_sync_any")
-        Database.use((db) => db.insert(EventSequenceTable).values({ aggregate_id: sessionID, seq: 0 }).run())
+      const waited = waitForWorkspaceSync(workspaceID, { [sessionID]: 1 })
+      await delay(10)
+      Database.use((db) =>
+        db.update(EventSequenceTable).set({ seq: 1 }).where(eq(EventSequenceTable.aggregate_id, sessionID)).run(),
+      )
+      GlobalBus.emit("event", {
+        workspace: WorkspaceID.ascending("wrk_other_workspace"),
+        payload: { type: "sync" },
+      })
 
-        const waited = yield* Workspace.use.waitForSync(workspaceID, { [sessionID]: 1 }).pipe(Effect.forkScoped)
-        yield* Effect.sleep("10 millis")
-        Database.use((db) =>
-          db.update(EventSequenceTable).set({ seq: 1 }).where(eq(EventSequenceTable.aggregate_id, sessionID)).run(),
-        )
-        GlobalBus.emit("event", {
-          workspace: WorkspaceID.ascending("wrk_other_workspace"),
-          payload: { type: "sync" },
-        })
+      await expect(waited).resolves.toBeUndefined()
+    })
+  })
 
-        expect(yield* Fiber.join(waited)).toBeUndefined()
-      }),
-    { git: true },
-  )
+  test("rejects with the abort reason when aborted", async () => {
+    await withInstance(async () => {
+      const abort = new AbortController()
+      const reason = new Error("caller aborted")
+      const waited = waitForWorkspaceSync(
+        WorkspaceID.ascending("wrk_wait_abort"),
+        { [SessionID.descending("ses_wait_abort")]: 1 },
+        abort.signal,
+      )
+      abort.abort(reason)
 
-  it.instance(
-    "rejects with the abort reason when aborted",
-    () =>
-      Effect.gen(function* () {
-        const abort = new AbortController()
-        const reason = new Error("caller aborted")
-        const waited = yield* Workspace.use
-          .waitForSync(
-            WorkspaceID.ascending("wrk_wait_abort"),
-            { [SessionID.descending("ses_wait_abort")]: 1 },
-            abort.signal,
-          )
-          .pipe(Effect.flip, Effect.forkScoped)
-        abort.abort(reason)
-        const error = yield* Fiber.join(waited)
-        expect(error).toMatchObject({
-          _tag: "WorkspaceSyncAbortedError",
-          message: reason.message,
-          cause: reason,
-        })
-      }),
-    { git: true },
-  )
+      await expect(waited).rejects.toMatchObject({
+        _tag: "WorkspaceSyncAbortedError",
+        message: reason.message,
+        cause: reason,
+      })
+    })
+  })
 
-  it.instance(
-    "times out with the requested fence in the error message",
-    () =>
-      Effect.gen(function* () {
-        const sessionID = SessionID.descending("ses_wait_timeout")
+  test("times out with the requested fence in the error message", async () => {
+    await withInstance(async () => {
+      const sessionID = SessionID.descending("ses_wait_timeout")
 
-        const failure = yield* Workspace.use
-          .waitForSync(WorkspaceID.ascending("wrk_wait_timeout"), { [sessionID]: 1 }, undefined, 25)
-          .pipe(Effect.flip)
-        expect(String(failure)).toContain(`Timed out waiting for sync fence: {"${sessionID}":1}`)
-      }),
-    { git: true, config: undefined },
-    7000,
-  )
+      await expect(
+        waitForWorkspaceSync(WorkspaceID.ascending("wrk_wait_timeout"), { [sessionID]: 1 }, undefined, 25),
+      ).rejects.toThrow(`Timed out waiting for sync fence: {"${sessionID}":1}`)
+    })
+  }, 7000)
 })

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

@@ -36,7 +36,7 @@ describe("file fsmonitor", () => {
         const before = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
         expect(before.exitCode).not.toBe(0)
 
-        yield* File.Service.use((svc) => svc.status())
+        yield* File.use.status()
 
         const after = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
         expect(after.exitCode).not.toBe(0)
@@ -63,7 +63,7 @@ describe("file fsmonitor", () => {
         const before = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
         expect(before.exitCode).not.toBe(0)
 
-        yield* File.Service.use((svc) => svc.read("tracked.txt"))
+        yield* File.use.read("tracked.txt")
 
         const after = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
         expect(after.exitCode).not.toBe(0)

+ 2 - 2
packages/opencode/test/file/path-traversal.test.ts

@@ -10,8 +10,8 @@ import { TestInstance } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 
 const it = testEffect(File.defaultLayer)
-const read = (file: string) => File.Service.use((svc) => svc.read(file))
-const list = (dir?: string) => File.Service.use((svc) => svc.list(dir))
+const read = (file: string) => File.use.read(file)
+const list = (dir?: string) => File.use.list(dir)
 const expectAccessDenied = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
   Effect.gen(function* () {
     const exit = yield* effect.pipe(Effect.exit)

+ 6 - 6
packages/opencode/test/file/ripgrep.test.ts

@@ -85,7 +85,7 @@ describe("file.ripgrep", () => {
     Effect.gen(function* () {
       const dir = yield* tmpdir((dir) => write(path.join(dir, "match.ts"), "const value = 'other'\n"))
 
-      const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" }))
+      const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle" })
       expect(result.partial).toBe(false)
       expect(result.items).toEqual([])
     }),
@@ -100,7 +100,7 @@ describe("file.ripgrep", () => {
         }),
       )
 
-      const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" }))
+      const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle" })
       expect(result.partial).toBe(false)
       expect(result.items).toHaveLength(1)
       expect(result.items[0]?.path.text).toBe(path.join("src", "match.ts"))
@@ -118,7 +118,7 @@ describe("file.ripgrep", () => {
         }),
       )
 
-      const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle", glob: ["*.ts"] }))
+      const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle", glob: ["*.ts"] })
       expect(result.partial).toBe(false)
       expect(result.items).toHaveLength(1)
       expect(result.items[0]?.path.text).toContain("match.ts")
@@ -136,7 +136,7 @@ describe("file.ripgrep", () => {
       )
 
       const file = path.join(dir, "match.ts")
-      const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle", file: [file] }))
+      const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle", file: [file] })
       expect(result.partial).toBe(false)
       expect(result.items).toHaveLength(1)
       expect(result.items[0]?.path.text).toBe(file)
@@ -200,7 +200,7 @@ describe("file.ripgrep", () => {
 
       const result = yield* withRipgrepConfig(
         path.join(dir, "missing-ripgreprc"),
-        Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })),
+        Ripgrep.use.search({ cwd: dir, pattern: "needle" }),
       )
       expect(result.items).toHaveLength(1)
     }),
@@ -212,7 +212,7 @@ describe("file.ripgrep", () => {
 
       const result = yield* withRipgrepConfig(
         path.join(dir, "missing-ripgreprc"),
-        Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })),
+        Ripgrep.use.search({ cwd: dir, pattern: "needle" }),
       )
       expect(result.items).toHaveLength(1)
     }),

+ 3 - 3
packages/opencode/test/format/format.test.ts

@@ -133,7 +133,7 @@ describe("Format", () => {
           const file = `${dir}/test.txt`
           yield* Effect.promise(() => Bun.write(file, "x"))
 
-          const formatted = yield* Format.Service.use((fmt) => fmt.file(file))
+          const formatted = yield* Format.use.file(file)
           expect(formatted).toBe(false)
         }),
       {
@@ -146,10 +146,10 @@ describe("Format", () => {
 
   it.live("status() initializes formatter state per directory", () =>
     Effect.gen(function* () {
-      const a = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
+      const a = yield* provideTmpdirInstance(() => Format.use.status(), {
         config: { formatter: false },
       })
-      const b = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
+      const b = yield* provideTmpdirInstance(() => Format.use.status(), {
         config: {
           formatter: true,
         },

+ 9 - 9
packages/opencode/test/installation/installation.test.ts

@@ -58,7 +58,7 @@ describe("installation", () => {
       "reads release version from GitHub releases",
       () =>
         Effect.gen(function* () {
-          const result = yield* Installation.Service.use((svc) => svc.latest("unknown"))
+          const result = yield* Installation.use.latest("unknown")
           expect(result).toBe("1.2.3")
         }),
     )
@@ -67,7 +67,7 @@ describe("installation", () => {
       "strips v prefix from GitHub release tag",
       () =>
         Effect.gen(function* () {
-          const result = yield* Installation.Service.use((svc) => svc.latest("curl"))
+          const result = yield* Installation.use.latest("curl")
           expect(result).toBe("4.0.0-beta.1")
         }),
     )
@@ -80,7 +80,7 @@ describe("installation", () => {
       }),
     ).effect("reads npm versions via registry", () =>
       Effect.gen(function* () {
-        const result = yield* Installation.Service.use((svc) => svc.latest("npm"))
+        const result = yield* Installation.use.latest("npm")
         expect(result).toBe("1.5.0")
         expect(npmCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`)
       }),
@@ -94,7 +94,7 @@ describe("installation", () => {
       }),
     ).effect("reads bun versions via registry", () =>
       Effect.gen(function* () {
-        const result = yield* Installation.Service.use((svc) => svc.latest("bun"))
+        const result = yield* Installation.use.latest("bun")
         expect(result).toBe("1.6.0")
         expect(bunCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`)
       }),
@@ -108,7 +108,7 @@ describe("installation", () => {
       }),
     ).effect("reads pnpm versions via registry", () =>
       Effect.gen(function* () {
-        const result = yield* Installation.Service.use((svc) => svc.latest("pnpm"))
+        const result = yield* Installation.use.latest("pnpm")
         expect(result).toBe("1.7.0")
         expect(pnpmCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`)
       }),
@@ -116,7 +116,7 @@ describe("installation", () => {
 
     testEffect(testLayer(() => jsonResponse({ version: "2.3.4" }))).effect("reads scoop manifest versions", () =>
       Effect.gen(function* () {
-        const result = yield* Installation.Service.use((svc) => svc.latest("scoop"))
+        const result = yield* Installation.use.latest("scoop")
         expect(result).toBe("2.3.4")
       }),
     )
@@ -125,7 +125,7 @@ describe("installation", () => {
       "reads chocolatey feed versions",
       () =>
         Effect.gen(function* () {
-          const result = yield* Installation.Service.use((svc) => svc.latest("choco"))
+          const result = yield* Installation.use.latest("choco")
           expect(result).toBe("3.4.5")
         }),
     )
@@ -142,7 +142,7 @@ describe("installation", () => {
       ),
     ).effect("reads brew formulae API versions", () =>
       Effect.gen(function* () {
-        const result = yield* Installation.Service.use((svc) => svc.latest("brew"))
+        const result = yield* Installation.use.latest("brew")
         expect(result).toBe("2.0.0")
       }),
     )
@@ -161,7 +161,7 @@ describe("installation", () => {
       ),
     ).effect("reads brew tap info JSON via CLI", () =>
       Effect.gen(function* () {
-        const result = yield* Installation.Service.use((svc) => svc.latest("brew"))
+        const result = yield* Installation.use.latest("brew")
         expect(result).toBe("2.1.0")
       }),
     )

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

@@ -175,7 +175,7 @@ mcpTest.instance("state() generates a new state when none is saved", () =>
       auth,
     )
 
-    const entryBefore = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
+    const entryBefore = yield* McpAuth.use.get("test-state-gen")
     expect(entryBefore?.oauthState).toBeUndefined()
 
     // state() should generate and return a new state, not throw
@@ -184,7 +184,7 @@ mcpTest.instance("state() generates a new state when none is saved", () =>
     expect(state.length).toBe(64) // 32 bytes as hex
 
     // The generated state should be persisted
-    const entryAfter = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
+    const entryAfter = yield* McpAuth.use.get("test-state-gen")
     expect(entryAfter?.oauthState).toBe(state)
   }),
 )
@@ -202,7 +202,7 @@ mcpTest.instance("state() returns existing state when one is saved", () =>
 
     // Pre-save a state
     const existingState = "pre-saved-state-value"
-    yield* McpAuth.Service.use((auth) => auth.updateOAuthState("test-state-existing", existingState))
+    yield* McpAuth.use.updateOAuthState("test-state-existing", existingState)
 
     // state() should return the existing state
     const state = yield* Effect.promise(() => provider.state())

+ 1 - 1
packages/opencode/test/permission-task.test.ts

@@ -6,7 +6,7 @@ import { testEffect } from "./lib/effect"
 
 const it = testEffect(Config.defaultLayer)
 
-const load = Config.Service.use((svc) => svc.get())
+const load = Config.use.get()
 
 describe("Permission.evaluate for permission.task", () => {
   const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): Permission.Ruleset =>

+ 2 - 2
packages/opencode/test/plugin/auth-override.test.ts

@@ -72,10 +72,10 @@ describe("plugin.auth-override", () => {
 
         const plain = yield* tmpdirScoped({ git: true })
         const plugin = pathToFileURL(path.join(pluginDir, "custom-copilot-auth.ts")).href
-        const methods = yield* ProviderAuth.Service.use((svc) => svc.methods()).pipe(
+        const methods = yield* ProviderAuth.use.methods().pipe(
           Effect.provide(layer(tmp.directory, [plugin])),
         )
-        const plainMethods = yield* ProviderAuth.Service.use((svc) => svc.methods()).pipe(
+        const plainMethods = yield* ProviderAuth.use.methods().pipe(
           Effect.provide(layer(plain, [])),
           provideInstance(plain),
         )

+ 2 - 2
packages/opencode/test/provider/amazon-bedrock.test.ts

@@ -18,7 +18,7 @@ const set = (k: string, v: string) =>
   Effect.gen(function* () {
     if (!originalEnv.has(k)) originalEnv.set(k, process.env[k])
     process.env[k] = v
-    yield* Env.Service.use((svc) => svc.set(k, v))
+    yield* Env.use.set(k, v)
   })
 
 afterEach(async () => {
@@ -30,7 +30,7 @@ afterEach(async () => {
   await disposeAllInstances()
 })
 
-const list = Provider.Service.use((svc) => svc.list())
+const list = Provider.use.list()
 
 const withAuthJson = (contents: string) =>
   Effect.acquireRelease(

+ 7 - 7
packages/opencode/test/provider/provider.test.ts

@@ -35,14 +35,14 @@ const set = (k: string, v: string) =>
   Effect.gen(function* () {
     rememberEnv(k)
     process.env[k] = v
-    yield* Env.Service.use((svc) => svc.set(k, v))
+    yield* Env.use.set(k, v)
   })
 
 const remove = (k: string) =>
   Effect.gen(function* () {
     rememberEnv(k)
     delete process.env[k]
-    yield* Env.Service.use((svc) => svc.remove(k))
+    yield* Env.use.remove(k)
   })
 
 afterEach(async () => {
@@ -332,7 +332,7 @@ test("parseModel handles model IDs with slashes", () => {
 it.instance("defaultModel returns first available model when no config set", () =>
   Effect.gen(function* () {
     yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
-    const model = yield* Provider.Service.use((provider) => provider.defaultModel())
+    const model = yield* Provider.use.defaultModel()
     expect(model.providerID).toBeDefined()
     expect(model.modelID).toBeDefined()
   }),
@@ -342,7 +342,7 @@ it.instance(
   "defaultModel respects config model setting",
   Effect.gen(function* () {
     yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
-    const model = yield* Provider.Service.use((provider) => provider.defaultModel())
+    const model = yield* Provider.use.defaultModel()
     expect(String(model.providerID)).toBe("anthropic")
     expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
   }),
@@ -1032,7 +1032,7 @@ it.instance("getProvider returns undefined for nonexistent provider", () =>
 it.instance("getProvider returns provider info", () =>
   Effect.gen(function* () {
     yield* set("ANTHROPIC_API_KEY", "test-api-key")
-    const provider = yield* Provider.Service.use((svc) => svc.getProvider(ProviderID.anthropic))
+    const provider = yield* Provider.use.getProvider(ProviderID.anthropic)
     expect(provider).toBeDefined()
     expect(String(provider?.id)).toBe("anthropic")
   }),
@@ -1680,7 +1680,7 @@ it.effect("opencode loader keeps paid models when config apiKey is present", ()
     })
 
     const listIn = (directory: string) =>
-      Provider.Service.use((svc) => svc.list())
+      Provider.use.list()
         .pipe(provideInstanceEffect(directory))
         .pipe(Effect.provide(InstanceLayer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer))
 
@@ -1698,7 +1698,7 @@ it.effect("opencode loader keeps paid models when auth exists", () =>
     const keyedDir = yield* tmpdirScoped()
 
     const listIn = (directory: string) =>
-      Provider.Service.use((svc) => svc.list())
+      Provider.use.list()
         .pipe(provideInstanceEffect(directory))
         .pipe(Effect.provide(InstanceLayer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer))
 

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

@@ -13,7 +13,7 @@ const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, Project.defaultLaye
 
 const withSession = (input?: Parameters<SessionNs.Interface["create"]>[0]) =>
   Effect.acquireRelease(
-    SessionNs.Service.use((session) => session.create(input)),
+    SessionNs.use.create(input),
     (created) => SessionNs.Service.use((session) => session.remove(created.id).pipe(Effect.ignore)),
   )
 
@@ -34,8 +34,8 @@ describe("session.listGlobal", () => {
         expect(ids).toContain(firstSession.id)
         expect(ids).toContain(secondSession.id)
 
-        const firstProject = yield* Project.Service.use((project) => project.get(firstSession.projectID))
-        const secondProject = yield* Project.Service.use((project) => project.get(secondSession.projectID))
+        const firstProject = yield* Project.use.get(firstSession.projectID)
+        const secondProject = yield* Project.use.get(secondSession.projectID)
 
         const firstItem = sessions.find((session) => session.id === firstSession.id)
         const secondItem = sessions.find((session) => session.id === secondSession.id)

+ 1 - 1
packages/opencode/test/server/httpapi-experimental.test.ts

@@ -31,7 +31,7 @@ function request(path: string, directory: string, init: RequestInit = {}) {
 }
 
 function createSession(input?: Session.CreateInput) {
-  return Session.Service.use((svc) => svc.create(input))
+  return Session.use.create(input)
 }
 
 function json<T>(response: Response) {

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

@@ -74,7 +74,7 @@ const createLocalWorkspace = (input: { projectID: Project.Info["id"]; type: stri
         projectID: input.projectID,
       })
     }),
-    (info) => Workspace.Service.use((workspace) => workspace.remove(info.id)).pipe(Effect.ignore),
+    (info) => Workspace.use.remove(info.id).pipe(Effect.ignore),
   )
 
 const probeInstanceContext = Effect.gen(function* () {

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

@@ -52,7 +52,7 @@ function pathFor(path: string, params: Record<string, string>) {
 }
 
 function createSession(input?: Session.CreateInput) {
-  return Session.Service.use((svc) => svc.create(input))
+  return Session.use.create(input)
 }
 
 function createTextMessage(sessionID: SessionIDType, text: string) {
@@ -101,7 +101,7 @@ const createLocalWorkspace = (input: { projectID: Project.Info["id"]; type: stri
         }),
       )
     }),
-    (info) => Workspace.Service.use((svc) => svc.remove(info.id)).pipe(Effect.ignore),
+    (info) => Workspace.use.remove(info.id).pipe(Effect.ignore),
   )
 
 const insertLegacyAssistantMessage = (sessionID: SessionIDType, time = 1) =>

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

@@ -36,7 +36,7 @@ describe("sync HttpApi", () => {
         const tmp = yield* TestInstance
         const headers = { "x-opencode-directory": tmp.directory, "content-type": "application/json" }
         const info = spyOn(Log.create({ service: "server.sync" }), "info")
-        const session = yield* Session.Service.use((svc) => svc.create({ title: "sync" }))
+        const session = yield* Session.use.create({ title: "sync" })
 
         const started = yield* Effect.promise(() =>
           Promise.resolve(app().request(SyncPaths.start, { method: "POST", headers })),

+ 1 - 1
packages/opencode/test/server/httpapi-workspace-routing.test.ts

@@ -128,7 +128,7 @@ const createWorkspace = (input: { projectID: Project.Info["id"]; type: string; a
         projectID: input.projectID,
       })
     }),
-    (info) => Workspace.Service.use((workspace) => workspace.remove(info.id)).pipe(Effect.ignore),
+    (info) => Workspace.use.remove(info.id).pipe(Effect.ignore),
   )
 
 const createRemoteWorkspace = (input: {

+ 2 - 2
packages/opencode/test/server/httpapi-workspace.test.ts

@@ -208,7 +208,7 @@ describe("workspace HttpApi", () => {
       const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info
       expect(workspace).toMatchObject({ type: "local-test", name: "local-test" })
 
-      const session = yield* Session.Service.use((svc) => svc.create({})).pipe(provideInstance(dir))
+      const session = yield* Session.use.create({}).pipe(provideInstance(dir))
       const warped = yield* request(WorkspacePaths.warp, dir, {
         method: "POST",
         headers: { "content-type": "application/json" },
@@ -424,7 +424,7 @@ describe("workspace HttpApi", () => {
         body: JSON.stringify({ type: "remote-session-target", branch: null }),
       })
       const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info
-      const session = yield* Session.Service.use((svc) => svc.create()).pipe(
+      const session = yield* Session.use.create().pipe(
         Effect.provideService(WorkspaceRef, workspace.id),
         provideInstance(dir),
       )

+ 1 - 1
packages/opencode/test/server/project-init-git.test.ts

@@ -83,7 +83,7 @@ describe("project.initGit endpoint", () => {
         worktree: tmp.directory,
       })
 
-      const ctx = yield* InstanceStore.Service.use((store) => store.reload({ directory: tmp.directory }))
+      const ctx = yield* InstanceStore.use.reload({ directory: tmp.directory })
       const tracked = yield* Snapshot.Service.use((snapshot) => snapshot.track()).pipe(
         Effect.provideService(InstanceRef, ctx),
       )

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

@@ -22,8 +22,8 @@ describe("session action routes", () => {
       Effect.gen(function* () {
         const test = yield* TestInstance
         const session = yield* Effect.acquireRelease(
-          SessionNs.Service.use((svc) => svc.create({})),
-          (created) => SessionNs.Service.use((svc) => svc.remove(created.id)).pipe(Effect.ignore),
+          SessionNs.use.create({}),
+          (created) => SessionNs.use.remove(created.id).pipe(Effect.ignore),
         )
 
         const res = yield* Effect.promise(() =>

+ 2 - 2
packages/opencode/test/server/session-diff-missing-patch.test.ts

@@ -35,8 +35,8 @@ function pathFor(template: string, params: Record<string, string>) {
 
 const withSession = (input?: Parameters<Session.Interface["create"]>[0]) =>
   Effect.acquireRelease(
-    Session.Service.use((session) => session.create(input)),
-    (created) => Session.Service.use((session) => session.remove(created.id)).pipe(Effect.ignore),
+    Session.use.create(input),
+    (created) => Session.use.remove(created.id).pipe(Effect.ignore),
   )
 
 describe("session diff with missing patch (#26574)", () => {

+ 5 - 5
packages/opencode/test/server/session-list.test.ts

@@ -28,7 +28,7 @@ const it = testEffect(
 
 const withSession = (input?: Parameters<SessionNs.Interface["create"]>[0]) =>
   Effect.acquireRelease(
-    SessionNs.Service.use((session) => session.create(input)),
+    SessionNs.use.create(input),
     (created) => SessionNs.Service.use((session) => session.remove(created.id).pipe(Effect.ignore)),
   )
 
@@ -56,7 +56,7 @@ describe("session.list", () => {
           provideInstance(path.join(test.directory, "packages", "app")),
         )
 
-        const ids = (yield* SessionNs.Service.use((session) => session.list())).map((session) => session.id)
+        const ids = (yield* SessionNs.use.list()).map((session) => session.id)
         expect(ids).toContain(root.id)
         expect(ids).toContain(parent.id)
         expect(ids).toContain(current.id)
@@ -179,7 +179,7 @@ describe("session.list", () => {
         const root = yield* withSession({ title: "root-session" })
         const child = yield* withSession({ title: "child-session", parentID: root.id })
 
-        const sessions = yield* SessionNs.Service.use((session) => session.list({ roots: true }))
+        const sessions = yield* SessionNs.use.list({ roots: true })
         const ids = sessions.map((session) => session.id)
 
         expect(ids).toContain(root.id)
@@ -206,7 +206,7 @@ describe("session.list", () => {
         yield* withSession({ title: "unique-search-term-abc" })
         yield* withSession({ title: "other-session-xyz" })
 
-        const sessions = yield* SessionNs.Service.use((session) => session.list({ search: "unique-search" }))
+        const sessions = yield* SessionNs.use.list({ search: "unique-search" })
         const titles = sessions.map((session) => session.title)
 
         expect(titles).toContain("unique-search-term-abc")
@@ -223,7 +223,7 @@ describe("session.list", () => {
         yield* withSession({ title: "session-2" })
         yield* withSession({ title: "session-3" })
 
-        const sessions = yield* SessionNs.Service.use((session) => session.list({ limit: 2 }))
+        const sessions = yield* SessionNs.use.list({ limit: 2 })
         expect(sessions.length).toBe(2)
       }),
     { git: true },

+ 2 - 2
packages/opencode/test/server/session-messages.test.ts

@@ -40,8 +40,8 @@ const withoutWatcher = <A, E, R>(effect: Effect.Effect<A, E, R>) => {
 }
 
 const sessionScoped = Effect.acquireRelease(
-  SessionNs.Service.use((svc) => svc.create({})),
-  (session) => SessionNs.Service.use((svc) => svc.remove(session.id)).pipe(Effect.ignore),
+  SessionNs.use.create({}),
+  (session) => SessionNs.use.remove(session.id).pipe(Effect.ignore),
 )
 
 const fill = Effect.fn("SessionMessagesTest.fill")(function* (

+ 1 - 1
packages/opencode/test/server/session-select.test.ts

@@ -16,7 +16,7 @@ describe("tui.selectSession endpoint", () => {
     () =>
       Effect.gen(function* () {
         const tmp = yield* TestInstance
-        const session = yield* Session.Service.use((svc) => svc.create({}))
+        const session = yield* Session.use.create({})
 
         const app = Server.Default().app
         const response = yield* Effect.promise(() =>

+ 1 - 1
packages/opencode/test/session/compaction.test.ts

@@ -292,7 +292,7 @@ function createSummaryCompaction(sessionID: SessionID) {
 }
 
 function readCompactionPart(sessionID: SessionID) {
-  return SessionNs.Service.use((ssn) => ssn.messages({ sessionID })).pipe(
+  return SessionNs.use.messages({ sessionID }).pipe(
     Effect.map((messages) =>
       messages.at(-2)?.parts.find((item): item is MessageV2.CompactionPart => item.type === "compaction"),
     ),

+ 1 - 1
packages/opencode/test/session/session.test.ts

@@ -35,7 +35,7 @@ const awaitDeferred = <T>(deferred: Deferred.Deferred<T>, message: string) =>
     Effect.sleep("2 seconds").pipe(Effect.flatMap(() => Effect.fail(new Error(message)))),
   )
 
-const remove = (id: SessionID) => SessionNs.Service.use((svc) => svc.remove(id))
+const remove = (id: SessionID) => SessionNs.use.remove(id)
 
 const subscribeGlobal = (type: string, callback: (event: NonNullable<GlobalEvent["payload"]>) => void) => {
   const listener = (event: GlobalEvent) => {

+ 7 - 7
packages/opencode/test/share/share-next.test.ts

@@ -128,7 +128,7 @@ describe("ShareNext", () => {
       Effect.gen(function* () {
         yield* seed("https://control.example.com", "org-1")
 
-        const req = yield* ShareNext.Service.use((svc) => svc.request()).pipe(Effect.provide(live(none)))
+        const req = yield* ShareNext.use.request().pipe(Effect.provide(live(none)))
 
         expect(req.api.create).toBe("/api/shares")
         expect(req.api.sync("shr_123")).toBe("/api/shares/shr_123/sync")
@@ -147,7 +147,7 @@ describe("ShareNext", () => {
     provideTmpdirInstance(
       () =>
         Effect.gen(function* () {
-          const session = yield* Session.Service.use((svc) => svc.create({ title: "test" }))
+          const session = yield* Session.use.create({ title: "test" })
           const seen: HttpClientRequest.HttpClientRequest[] = []
           const client = HttpClient.make((req) => {
             seen.push(req)
@@ -163,7 +163,7 @@ describe("ShareNext", () => {
             return Effect.succeed(json(req, { ok: true }))
           })
 
-          const result = yield* ShareNext.Service.use((svc) => svc.create(session.id)).pipe(
+          const result = yield* ShareNext.use.create(session.id).pipe(
             Effect.provide(live(client)),
           )
 
@@ -188,7 +188,7 @@ describe("ShareNext", () => {
     provideTmpdirInstance(
       () =>
         Effect.gen(function* () {
-          const session = yield* Session.Service.use((svc) => svc.create({ title: "test" }))
+          const session = yield* Session.use.create({ title: "test" })
           const seen: HttpClientRequest.HttpClientRequest[] = []
           const client = HttpClient.make((req) => {
             seen.push(req)
@@ -205,8 +205,8 @@ describe("ShareNext", () => {
           })
 
           yield* Effect.gen(function* () {
-            yield* ShareNext.Service.use((svc) => svc.create(session.id))
-            yield* ShareNext.Service.use((svc) => svc.remove(session.id))
+            yield* ShareNext.use.create(session.id)
+            yield* ShareNext.use.remove(session.id)
           }).pipe(Effect.provide(live(client)))
 
           expect(share(session.id)).toBeUndefined()
@@ -222,7 +222,7 @@ describe("ShareNext", () => {
   it.live("create fails on a non-ok response and does not persist a share", () =>
     provideTmpdirInstance(() =>
       Effect.gen(function* () {
-        const session = yield* Session.Service.use((svc) => svc.create({ title: "test" }))
+        const session = yield* Session.use.create({ title: "test" })
         const client = HttpClient.make((req) => Effect.succeed(json(req, { error: "bad" }, 500)))
 
         const exit = yield* ShareNext.Service.use((svc) => Effect.exit(svc.create(session.id))).pipe(