import { describe, expect, test } from "bun:test" import { fileURLToPath } from "node:url" import type { Message, Part, ToolPart } from "@opencode-ai/sdk/v2" import { createDebugFrameTransport } from "../../../src/cli/cmd/tui/debug/frame" const fixture = fileURLToPath( new URL("../../../src/cli/cmd/tui/debug/fixtures/subagent-lifecycle.json", import.meta.url), ) describe("TUI debug frames", () => { test("compiles completed direct and child-tool subagents into distinct sessions", async () => { const transport = await createDebugFrameTransport({ file: fixture, frame: "completed", directory: "/tmp/project" }) const transcript = (await ( await transport.fetch(`http://opencode.debug/session/${transport.sessionID}/message`) ).json()) as Array<{ info: Message; parts: Part[] }> const tasks = transcript[1]!.parts.filter((part): part is ToolPart => part.type === "tool" && part.tool === "task") const directID = completedMetadata(tasks[1]!).sessionId as string const readID = completedMetadata(tasks[2]!).sessionId as string const direct = (await ( await transport.fetch(`http://opencode.debug/session/${directID}/message`) ).json()) as Array<{ info: Message; parts: Part[] }> const read = (await (await transport.fetch(`http://opencode.debug/session/${readID}/message`)).json()) as Array<{ info: Message parts: Part[] }> expect(directID).not.toBe(readID) expect(direct[1]!.parts).toHaveLength(0) expect(read[1]!.parts.filter((part) => part.type === "tool")).toHaveLength(1) if (direct[1]!.info.role !== "assistant" || direct[1]!.info.time.completed === undefined) { throw new Error("Expected completed child response") } expect(direct[1]!.info.time.completed - direct[0]!.info.time.created).toBe(501) }) test("marks only active background children busy", async () => { const transport = await createDebugFrameTransport({ file: fixture, frame: "active-background", directory: "/tmp/project", }) const status = (await (await transport.fetch("http://opencode.debug/session/status")).json()) as Record< string, { type: string } > expect(Object.values(status)).toEqual([{ type: "busy" }]) }) test("reports available frames for unknown selection", async () => { await expect( createDebugFrameTransport({ file: fixture, frame: "missing", directory: "/tmp/project" }), ).rejects.toThrow("Available frames: running, active-background, completed") }) test("rejects mutations against static debug frames", async () => { const transport = await createDebugFrameTransport({ file: fixture, frame: "completed", directory: "/tmp/project" }) await expect( transport.fetch(`http://opencode.debug/session/${transport.sessionID}/message`, { method: "POST" }), ).rejects.toThrow("Unexpected debug frame request: POST") }) }) function completedMetadata(part: ToolPart) { if (part.state.status !== "completed") throw new Error("Expected completed task") return part.state.metadata }