Bläddra i källkod

test: add live effect helper mode

Default the shared effect test helper to support both test-clock and live execution, and switch the current opencode effect tests to the live path for real integration behavior.
Kit Langton 2 veckor sedan
förälder
incheckning
6ea467b0ac

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

@@ -16,21 +16,21 @@ const truncate = Layer.effectDiscard(
 
 const it = testEffect(Layer.merge(AccountRepo.layer, truncate))
 
-it.effect("list returns empty when no accounts exist", () =>
+it.live("list returns empty when no accounts exist", () =>
   Effect.gen(function* () {
     const accounts = yield* AccountRepo.use((r) => r.list())
     expect(accounts).toEqual([])
   }),
 )
 
-it.effect("active returns none when no accounts exist", () =>
+it.live("active returns none when no accounts exist", () =>
   Effect.gen(function* () {
     const active = yield* AccountRepo.use((r) => r.active())
     expect(Option.isNone(active)).toBe(true)
   }),
 )
 
-it.effect("persistAccount inserts and getRow retrieves", () =>
+it.live("persistAccount inserts and getRow retrieves", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
     yield* AccountRepo.use((r) =>
@@ -56,7 +56,7 @@ it.effect("persistAccount inserts and getRow retrieves", () =>
   }),
 )
 
-it.effect("persistAccount sets the active account and org", () =>
+it.live("persistAccount sets the active account and org", () =>
   Effect.gen(function* () {
     const id1 = AccountID.make("user-1")
     const id2 = AccountID.make("user-2")
@@ -93,7 +93,7 @@ it.effect("persistAccount sets the active account and org", () =>
   }),
 )
 
-it.effect("list returns all accounts", () =>
+it.live("list returns all accounts", () =>
   Effect.gen(function* () {
     const id1 = AccountID.make("user-1")
     const id2 = AccountID.make("user-2")
@@ -128,7 +128,7 @@ it.effect("list returns all accounts", () =>
   }),
 )
 
-it.effect("remove deletes an account", () =>
+it.live("remove deletes an account", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
 
@@ -151,7 +151,7 @@ it.effect("remove deletes an account", () =>
   }),
 )
 
-it.effect("use stores the selected org and marks the account active", () =>
+it.live("use stores the selected org and marks the account active", () =>
   Effect.gen(function* () {
     const id1 = AccountID.make("user-1")
     const id2 = AccountID.make("user-2")
@@ -191,7 +191,7 @@ it.effect("use stores the selected org and marks the account active", () =>
   }),
 )
 
-it.effect("persistToken updates token fields", () =>
+it.live("persistToken updates token fields", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
 
@@ -225,7 +225,7 @@ it.effect("persistToken updates token fields", () =>
   }),
 )
 
-it.effect("persistToken with no expiry sets token_expiry to null", () =>
+it.live("persistToken with no expiry sets token_expiry to null", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
 
@@ -255,7 +255,7 @@ it.effect("persistToken with no expiry sets token_expiry to null", () =>
   }),
 )
 
-it.effect("persistAccount upserts on conflict", () =>
+it.live("persistAccount upserts on conflict", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
 
@@ -295,7 +295,7 @@ it.effect("persistAccount upserts on conflict", () =>
   }),
 )
 
-it.effect("remove clears active state when deleting the active account", () =>
+it.live("remove clears active state when deleting the active account", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
 
@@ -318,7 +318,7 @@ it.effect("remove clears active state when deleting the active account", () =>
   }),
 )
 
-it.effect("getRow returns none for nonexistent account", () =>
+it.live("getRow returns none for nonexistent account", () =>
   Effect.gen(function* () {
     const row = yield* AccountRepo.use((r) => r.getRow(AccountID.make("nope")))
     expect(Option.isNone(row)).toBe(true)

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

@@ -54,7 +54,7 @@ const deviceTokenClient = (body: unknown, status = 400) =>
 const poll = (body: unknown, status = 400) =>
   Account.Service.use((s) => s.poll(login())).pipe(Effect.provide(live(deviceTokenClient(body, status))))
 
-it.effect("orgsByAccount groups orgs per account", () =>
+it.live("orgsByAccount groups orgs per account", () =>
   Effect.gen(function* () {
     yield* AccountRepo.use((r) =>
       r.persistAccount({
@@ -107,7 +107,7 @@ it.effect("orgsByAccount groups orgs per account", () =>
   }),
 )
 
-it.effect("token refresh persists the new token", () =>
+it.live("token refresh persists the new token", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
 
@@ -148,7 +148,7 @@ it.effect("token refresh persists the new token", () =>
   }),
 )
 
-it.effect("config sends the selected org header", () =>
+it.live("config sends the selected org header", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
 
@@ -188,7 +188,7 @@ it.effect("config sends the selected org header", () =>
   }),
 )
 
-it.effect("poll stores the account and first org on success", () =>
+it.live("poll stores the account and first org on success", () =>
   Effect.gen(function* () {
     const client = HttpClient.make((req) =>
       Effect.succeed(
@@ -259,7 +259,7 @@ for (const [name, body, expectedTag] of [
     "PollExpired",
   ],
 ] as const) {
-  it.effect(`poll returns ${name} for ${body.error}`, () =>
+  it.live(`poll returns ${name} for ${body.error}`, () =>
     Effect.gen(function* () {
       const result = yield* poll(body)
       expect(result._tag).toBe(expectedTag)
@@ -267,7 +267,7 @@ for (const [name, body, expectedTag] of [
   )
 }
 
-it.effect("poll returns poll error for other OAuth errors", () =>
+it.live("poll returns poll error for other OAuth errors", () =>
   Effect.gen(function* () {
     const result = yield* poll({
       error: "server_error",

+ 5 - 5
packages/opencode/test/bus/bus-effect.test.ts

@@ -22,7 +22,7 @@ const live = Layer.mergeAll(Bus.layer, node)
 const it = testEffect(live)
 
 describe("Bus (Effect-native)", () => {
-  it.effect("publish + subscribe stream delivers events", () =>
+  it.live("publish + subscribe stream delivers events", () =>
     provideTmpdirInstance(() =>
       Effect.gen(function* () {
         const bus = yield* Bus.Service
@@ -46,7 +46,7 @@ describe("Bus (Effect-native)", () => {
     ),
   )
 
-  it.effect("subscribe filters by event type", () =>
+  it.live("subscribe filters by event type", () =>
     provideTmpdirInstance(() =>
       Effect.gen(function* () {
         const bus = yield* Bus.Service
@@ -70,7 +70,7 @@ describe("Bus (Effect-native)", () => {
     ),
   )
 
-  it.effect("subscribeAll receives all types", () =>
+  it.live("subscribeAll receives all types", () =>
     provideTmpdirInstance(() =>
       Effect.gen(function* () {
         const bus = yield* Bus.Service
@@ -95,7 +95,7 @@ describe("Bus (Effect-native)", () => {
     ),
   )
 
-  it.effect("multiple subscribers each receive the event", () =>
+  it.live("multiple subscribers each receive the event", () =>
     provideTmpdirInstance(() =>
       Effect.gen(function* () {
         const bus = yield* Bus.Service
@@ -129,7 +129,7 @@ describe("Bus (Effect-native)", () => {
     ),
   )
 
-  it.effect("subscribeAll stream sees InstanceDisposed on disposal", () =>
+  it.live("subscribeAll stream sees InstanceDisposed on disposal", () =>
     Effect.gen(function* () {
       const dir = yield* tmpdirScoped()
       const types: string[] = []

+ 24 - 24
packages/opencode/test/effect/runner.test.ts

@@ -6,7 +6,7 @@ import { it } from "../lib/effect"
 describe("Runner", () => {
   // --- ensureRunning semantics ---
 
-  it.effect(
+  it.live(
     "ensureRunning starts work and returns result",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -18,7 +18,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "ensureRunning propagates work failures",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -29,7 +29,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "concurrent callers share the same run",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -51,7 +51,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "concurrent callers all receive same error",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -71,7 +71,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "ensureRunning can be called again after previous run completes",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -81,7 +81,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "second ensureRunning ignores new work if already running",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -110,7 +110,7 @@ describe("Runner", () => {
 
   // --- cancel semantics ---
 
-  it.effect(
+  it.live(
     "cancel interrupts running work",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -128,7 +128,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "cancel on idle is a no-op",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -138,7 +138,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "cancel with onInterrupt resolves callers gracefully",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -154,7 +154,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "cancel with queued callers resolves all",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -175,7 +175,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "work can be started after cancel",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -245,7 +245,7 @@ describe("Runner", () => {
 
   // --- shell semantics ---
 
-  it.effect(
+  it.live(
     "shell runs exclusively",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -256,7 +256,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "shell rejects when run is active",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -272,7 +272,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "shell rejects when another shell is running",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -292,7 +292,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "shell rejects via busy callback and cancel still stops the first shell",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -323,7 +323,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "cancel interrupts shell that ignores abort signal",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -349,7 +349,7 @@ describe("Runner", () => {
 
   // --- shell→run handoff ---
 
-  it.effect(
+  it.live(
     "ensureRunning queues behind shell then runs after",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -376,7 +376,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "multiple ensureRunning callers share the queued run behind shell",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -407,7 +407,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "cancel during shell_then_run cancels both",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -441,7 +441,7 @@ describe("Runner", () => {
 
   // --- lifecycle callbacks ---
 
-  it.effect(
+  it.live(
     "onIdle fires when returning to idle from running",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -454,7 +454,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "onIdle fires on cancel",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -470,7 +470,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "onBusy fires when shell starts",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -485,7 +485,7 @@ describe("Runner", () => {
 
   // --- busy flag ---
 
-  it.effect(
+  it.live(
     "busy is true during run",
     Effect.gen(function* () {
       const s = yield* Scope.Scope
@@ -502,7 +502,7 @@ describe("Runner", () => {
     }),
   )
 
-  it.effect(
+  it.live(
     "busy is true during shell",
     Effect.gen(function* () {
       const s = yield* Scope.Scope

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

@@ -10,7 +10,7 @@ import * as Formatter from "../../src/format/formatter"
 const it = testEffect(Layer.mergeAll(Format.defaultLayer, CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer))
 
 describe("Format", () => {
-  it.effect("status() returns built-in formatters when no config overrides", () =>
+  it.live("status() returns built-in formatters when no config overrides", () =>
     provideTmpdirInstance(() =>
       Format.Service.use((fmt) =>
         Effect.gen(function* () {
@@ -32,7 +32,7 @@ describe("Format", () => {
     ),
   )
 
-  it.effect("status() returns empty list when formatter is disabled", () =>
+  it.live("status() returns empty list when formatter is disabled", () =>
     provideTmpdirInstance(
       () =>
         Format.Service.use((fmt) =>
@@ -44,7 +44,7 @@ describe("Format", () => {
     ),
   )
 
-  it.effect("status() excludes formatters marked as disabled in config", () =>
+  it.live("status() excludes formatters marked as disabled in config", () =>
     provideTmpdirInstance(
       () =>
         Format.Service.use((fmt) =>
@@ -64,11 +64,11 @@ describe("Format", () => {
     ),
   )
 
-  it.effect("service initializes without error", () =>
+  it.live("service initializes without error", () =>
     provideTmpdirInstance(() => Format.Service.use(() => Effect.void)),
   )
 
-  it.effect("status() initializes formatter state per directory", () =>
+  it.live("status() initializes formatter state per directory", () =>
     Effect.gen(function* () {
       const a = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
         config: { formatter: false },
@@ -80,7 +80,7 @@ describe("Format", () => {
     }),
   )
 
-  it.effect("runs enabled checks for matching formatters in parallel", () =>
+  it.live("runs enabled checks for matching formatters in parallel", () =>
     provideTmpdirInstance((path) =>
       Effect.gen(function* () {
         const file = `${path}/test.parallel`
@@ -144,7 +144,7 @@ describe("Format", () => {
     ),
   )
 
-  it.effect("runs matching formatters sequentially for the same file", () =>
+  it.live("runs matching formatters sequentially for the same file", () =>
     provideTmpdirInstance(
       (path) =>
         Effect.gen(function* () {

+ 24 - 8
packages/opencode/test/lib/effect.ts

@@ -1,10 +1,10 @@
 import { test, type TestOptions } from "bun:test"
 import { Cause, Effect, Exit, Layer } from "effect"
 import type * as Scope from "effect/Scope"
+import * as TestClock from "effect/testing/TestClock"
 import * as TestConsole from "effect/testing/TestConsole"
 
 type Body<A, E, R> = Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>)
-const env = TestConsole.layer
 
 const body = <A, E, R>(value: Body<A, E, R>) => Effect.suspend(() => (typeof value === "function" ? value() : value))
 
@@ -19,19 +19,35 @@ const run = <A, E, R, E2>(value: Body<A, E, R | Scope.Scope>, layer: Layer.Layer
     return yield* exit
   }).pipe(Effect.runPromise)
 
-const make = <R, E>(layer: Layer.Layer<R, E, never>) => {
+const make = <R, E>(testLayer: Layer.Layer<R, E, never>, liveLayer: Layer.Layer<R, E, never>) => {
   const effect = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
-    test(name, () => run(value, layer), opts)
+    test(name, () => run(value, testLayer), opts)
 
   effect.only = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
-    test.only(name, () => run(value, layer), opts)
+    test.only(name, () => run(value, testLayer), opts)
 
   effect.skip = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
-    test.skip(name, () => run(value, layer), opts)
+    test.skip(name, () => run(value, testLayer), opts)
 
-  return { effect }
+  const live = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
+    test(name, () => run(value, liveLayer), opts)
+
+  live.only = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
+    test.only(name, () => run(value, liveLayer), opts)
+
+  live.skip = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
+    test.skip(name, () => run(value, liveLayer), opts)
+
+  return { effect, live }
 }
 
-export const it = make(env)
+// Test environment with TestClock and TestConsole
+const testEnv = Layer.mergeAll(TestConsole.layer, TestClock.layer())
+
+// Live environment - uses real clock, but keeps TestConsole for output capture
+const liveEnv = TestConsole.layer
+
+export const it = make(testEnv, liveEnv)
 
-export const testEffect = <R, E>(layer: Layer.Layer<R, E, never>) => make(Layer.provideMerge(layer, env))
+export const testEffect = <R, E>(layer: Layer.Layer<R, E, never>) =>
+  make(Layer.provideMerge(layer, testEnv), Layer.provideMerge(layer, liveEnv))

+ 10 - 10
packages/opencode/test/session/processor-effect.test.ts

@@ -264,7 +264,7 @@ const env = SessionProcessor.layer.pipe(Layer.provideMerge(deps))
 
 const it = testEffect(env)
 
-it.effect("session.processor effect tests capture llm input cleanly", () => {
+it.live("session.processor effect tests capture llm input cleanly", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -316,7 +316,7 @@ it.effect("session.processor effect tests capture llm input cleanly", () => {
   )
 })
 
-it.effect("session.processor effect tests stop after token overflow requests compaction", () => {
+it.live("session.processor effect tests stop after token overflow requests compaction", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -376,7 +376,7 @@ it.effect("session.processor effect tests stop after token overflow requests com
   )
 })
 
-it.effect("session.processor effect tests reset reasoning state across retries", () => {
+it.live("session.processor effect tests reset reasoning state across retries", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -449,7 +449,7 @@ it.effect("session.processor effect tests reset reasoning state across retries",
   )
 })
 
-it.effect("session.processor effect tests do not retry unknown json errors", () => {
+it.live("session.processor effect tests do not retry unknown json errors", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -495,7 +495,7 @@ it.effect("session.processor effect tests do not retry unknown json errors", ()
   )
 })
 
-it.effect("session.processor effect tests retry recognized structured json errors", () => {
+it.live("session.processor effect tests retry recognized structured json errors", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -544,7 +544,7 @@ it.effect("session.processor effect tests retry recognized structured json error
   )
 })
 
-it.effect("session.processor effect tests publish retry status updates", () => {
+it.live("session.processor effect tests publish retry status updates", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -611,7 +611,7 @@ it.effect("session.processor effect tests publish retry status updates", () => {
   )
 })
 
-it.effect("session.processor effect tests compact on structured context overflow", () => {
+it.live("session.processor effect tests compact on structured context overflow", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -656,7 +656,7 @@ it.effect("session.processor effect tests compact on structured context overflow
   )
 })
 
-it.effect("session.processor effect tests mark pending tools as aborted on cleanup", () => {
+it.live("session.processor effect tests mark pending tools as aborted on cleanup", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -725,7 +725,7 @@ it.effect("session.processor effect tests mark pending tools as aborted on clean
   )
 })
 
-it.effect("session.processor effect tests record aborted errors and idle state", () => {
+it.live("session.processor effect tests record aborted errors and idle state", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -807,7 +807,7 @@ it.effect("session.processor effect tests record aborted errors and idle state",
   )
 })
 
-it.effect("session.processor effect tests mark interruptions aborted without manual abort", () => {
+it.live("session.processor effect tests mark interruptions aborted without manual abort", () => {
   return provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {

+ 15 - 15
packages/opencode/test/session/prompt-effect.test.ts

@@ -416,7 +416,7 @@ const boot = Effect.fn("test.boot")(function* (input?: { title?: string }) {
 
 // Loop semantics
 
-it.effect("loop exits immediately when last assistant has stop finish", () =>
+it.live("loop exits immediately when last assistant has stop finish", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -432,7 +432,7 @@ it.effect("loop exits immediately when last assistant has stop finish", () =>
   ),
 )
 
-it.effect("loop calls LLM and returns assistant message", () =>
+it.live("loop calls LLM and returns assistant message", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -450,7 +450,7 @@ it.effect("loop calls LLM and returns assistant message", () =>
   ),
 )
 
-it.effect("loop continues when finish is tool-calls", () =>
+it.live("loop continues when finish is tool-calls", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -471,7 +471,7 @@ it.effect("loop continues when finish is tool-calls", () =>
   ),
 )
 
-it.effect("failed subtask preserves metadata on error tool state", () =>
+it.live("failed subtask preserves metadata on error tool state", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -532,7 +532,7 @@ it.effect("failed subtask preserves metadata on error tool state", () =>
   ),
 )
 
-it.effect("loop sets status to busy then idle", () =>
+it.live("loop sets status to busy then idle", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -567,7 +567,7 @@ it.effect("loop sets status to busy then idle", () =>
 
 // Cancel semantics
 
-it.effect(
+it.live(
   "cancel interrupts loop and resolves with an assistant message",
   () =>
     provideTmpdirInstance(
@@ -598,7 +598,7 @@ it.effect(
   30_000,
 )
 
-it.effect(
+it.live(
   "cancel records MessageAbortedError on interrupted process",
   () =>
     provideTmpdirInstance(
@@ -632,7 +632,7 @@ it.effect(
   30_000,
 )
 
-it.effect(
+it.live(
   "cancel finalizes subtask tool state",
   () =>
     provideTmpdirInstance(
@@ -695,7 +695,7 @@ it.effect(
   30_000,
 )
 
-it.effect(
+it.live(
   "cancel with queued callers resolves all cleanly",
   () =>
     provideTmpdirInstance(
@@ -733,7 +733,7 @@ it.effect(
 
 // Queue semantics
 
-it.effect("concurrent loop callers get same result", () =>
+it.live("concurrent loop callers get same result", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -752,7 +752,7 @@ it.effect("concurrent loop callers get same result", () =>
   ),
 )
 
-it.effect("concurrent loop callers all receive same error result", () =>
+it.live("concurrent loop callers all receive same error result", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -780,7 +780,7 @@ it.effect("concurrent loop callers all receive same error result", () =>
   ),
 )
 
-it.effect(
+it.live(
   "prompt submitted during an active run is included in the next LLM input",
   () =>
     provideTmpdirInstance(
@@ -860,7 +860,7 @@ it.effect(
   30_000,
 )
 
-it.effect(
+it.live(
   "assertNotBusy throws BusyError when loop running",
   () =>
     provideTmpdirInstance(
@@ -897,7 +897,7 @@ it.effect(
   30_000,
 )
 
-it.effect("assertNotBusy succeeds when idle", () =>
+it.live("assertNotBusy succeeds when idle", () =>
   provideTmpdirInstance(
     (dir) =>
       Effect.gen(function* () {
@@ -914,7 +914,7 @@ it.effect("assertNotBusy succeeds when idle", () =>
 
 // Shell semantics
 
-it.effect(
+it.live(
   "shell rejects with BusyError when loop running",
   () =>
     provideTmpdirInstance(

+ 2 - 2
packages/opencode/test/session/prompt-provider.test.ts

@@ -52,7 +52,7 @@ function makeConfig(url: string) {
 }
 
 describe("session.prompt provider integration", () => {
-  it.effect("loop returns assistant text through local provider", () =>
+  it.live("loop returns assistant text through local provider", () =>
     Effect.gen(function* () {
       const llm = yield* TestLLMServer
       return yield* provideTmpdirInstance(
@@ -87,7 +87,7 @@ describe("session.prompt provider integration", () => {
     }),
   )
 
-  it.effect("loop consumes queued replies across turns", () =>
+  it.live("loop consumes queued replies across turns", () =>
     Effect.gen(function* () {
       const llm = yield* TestLLMServer
       return yield* provideTmpdirInstance(

+ 1 - 1
packages/opencode/test/tool/truncation.test.ts

@@ -140,7 +140,7 @@ describe("Truncate", () => {
     const DAY_MS = 24 * 60 * 60 * 1000
     const it = testEffect(Layer.mergeAll(TruncateSvc.defaultLayer, NodeFileSystem.layer))
 
-    it.effect("deletes files older than 7 days and preserves recent files", () =>
+    it.live("deletes files older than 7 days and preserves recent files", () =>
       Effect.gen(function* () {
         const fs = yield* FileSystem.FileSystem