|
@@ -1,5 +1,5 @@
|
|
|
import { afterEach, expect } from "bun:test"
|
|
import { afterEach, expect } from "bun:test"
|
|
|
-import { Cause, Effect, Exit, Fiber, Layer } from "effect"
|
|
|
|
|
|
|
+import { Cause, Effect, Exit, Fiber, Layer, Queue } from "effect"
|
|
|
import { Question } from "../../src/question"
|
|
import { Question } from "../../src/question"
|
|
|
import { Instance } from "../../src/project/instance"
|
|
import { Instance } from "../../src/project/instance"
|
|
|
import { InstanceRuntime } from "../../src/project/instance-runtime"
|
|
import { InstanceRuntime } from "../../src/project/instance-runtime"
|
|
@@ -8,8 +8,11 @@ import { disposeAllInstances, provideInstance, reloadTestInstance, tmpdirScoped
|
|
|
import { SessionID } from "../../src/session/schema"
|
|
import { SessionID } from "../../src/session/schema"
|
|
|
import { testEffect } from "../lib/effect"
|
|
import { testEffect } from "../lib/effect"
|
|
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
|
|
|
|
+import { Bus } from "../../src/bus"
|
|
|
|
|
|
|
|
-const it = testEffect(Layer.mergeAll(Question.defaultLayer, CrossSpawnSpawner.defaultLayer))
|
|
|
|
|
|
|
+const it = testEffect(
|
|
|
|
|
+ Layer.mergeAll(Question.layer.pipe(Layer.provideMerge(Bus.layer)), CrossSpawnSpawner.defaultLayer),
|
|
|
|
|
+)
|
|
|
|
|
|
|
|
const askEffect = Effect.fn("QuestionTest.ask")(function* (input: {
|
|
const askEffect = Effect.fn("QuestionTest.ask")(function* (input: {
|
|
|
sessionID: SessionID
|
|
sessionID: SessionID
|
|
@@ -44,15 +47,19 @@ const rejectAll = Effect.gen(function* () {
|
|
|
yield* Effect.forEach(yield* listEffect, (req) => rejectEffect(req.id), { discard: true })
|
|
yield* Effect.forEach(yield* listEffect, (req) => rejectEffect(req.id), { discard: true })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const waitForPending = (count: number) =>
|
|
|
|
|
- Effect.gen(function* () {
|
|
|
|
|
- for (let i = 0; i < 100; i++) {
|
|
|
|
|
- const pending = yield* listEffect
|
|
|
|
|
- if (pending.length === count) return pending
|
|
|
|
|
- yield* Effect.sleep("10 millis")
|
|
|
|
|
- }
|
|
|
|
|
- return yield* Effect.fail(new Error(`timed out waiting for ${count} pending question request(s)`))
|
|
|
|
|
- })
|
|
|
|
|
|
|
+const waitForPending = Effect.fn("QuestionTest.waitForPending")(function* (count: number) {
|
|
|
|
|
+ const question = yield* Question.Service
|
|
|
|
|
+ const bus = yield* Bus.Service
|
|
|
|
|
+ const asked = yield* Queue.unbounded<void>()
|
|
|
|
|
+ const off = yield* bus.subscribeCallback(Question.Event.Asked, () => Queue.offerUnsafe(asked, undefined))
|
|
|
|
|
+ yield* Effect.addFinalizer(() => Effect.sync(off))
|
|
|
|
|
+
|
|
|
|
|
+ for (;;) {
|
|
|
|
|
+ const pending = yield* question.list()
|
|
|
|
|
+ if (pending.length === count) return pending
|
|
|
|
|
+ yield* Queue.take(asked).pipe(Effect.timeout("2 seconds"))
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
it.instance(
|
|
it.instance(
|
|
|
"ask - remains pending until answered",
|
|
"ask - remains pending until answered",
|