import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Agent } from "@/agent/agent" import { BackgroundJob } from "@/background/job" import { Bus } from "@/bus" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Session } from "@/session/session" import { MessageID } from "@/session/schema" import { SessionStatus } from "@/session/status" import { TaskStatusTool } from "@/tool/task_status" import { Truncate } from "@/tool/truncate" import { RuntimeFlags } from "@/effect/runtime-flags" import { disposeAllInstances } from "../fixture/fixture" import { testEffect } from "../lib/effect" afterEach(async () => { await disposeAllInstances() }) const layer = (flags: Partial = {}) => Layer.mergeAll( Agent.defaultLayer, BackgroundJob.defaultLayer, Bus.defaultLayer, CrossSpawnSpawner.defaultLayer, Session.defaultLayer, SessionStatus.defaultLayer, Truncate.defaultLayer, RuntimeFlags.layer(flags), ) const it = testEffect(layer({ experimentalBackgroundSubagents: true })) describe("tool.task_status", () => { it.instance("returns completed background job output", () => Effect.gen(function* () { const jobs = yield* BackgroundJob.Service const sessions = yield* Session.Service const tool = yield* TaskStatusTool const def = yield* tool.init() const chat = yield* sessions.create({}) yield* jobs.start({ id: chat.id, type: "task", run: Effect.succeed("all done") }) const result = yield* def.execute( { task_id: chat.id, wait: true, timeout_ms: 1_000 }, { sessionID: chat.id, messageID: MessageID.ascending(), agent: "build", abort: new AbortController().signal, messages: [], metadata: () => Effect.void, ask: () => Effect.void, }, ) expect(result.output).toContain("state: completed") expect(result.output).toContain("all done") expect(result.metadata.timed_out).toBe(false) }), ) it.instance("wait=true times out while the background job is running", () => Effect.gen(function* () { const jobs = yield* BackgroundJob.Service const sessions = yield* Session.Service const tool = yield* TaskStatusTool const def = yield* tool.init() const chat = yield* sessions.create({}) yield* jobs.start({ id: chat.id, type: "task", run: Effect.never }) const result = yield* def.execute( { task_id: chat.id, wait: true, timeout_ms: 50 }, { sessionID: chat.id, messageID: MessageID.ascending(), agent: "build", abort: new AbortController().signal, messages: [], metadata: () => Effect.void, ask: () => Effect.void, }, ) expect(result.output).toContain("state: running") expect(result.output).toContain("Timed out after 50ms") expect(result.metadata.timed_out).toBe(true) }), ) })