/** @jsxImportSource @opentui/solid */ import { expect, test } from "bun:test" import { testRender } from "@opentui/solid" import type { Event, GlobalEvent } from "@opencode-ai/sdk/v2" import { onMount } from "solid-js" import { ProjectProvider } from "../../../src/cli/cmd/tui/context/project" import { SDKProvider } from "../../../src/cli/cmd/tui/context/sdk" import { SyncProviderV2, useSyncV2 } from "../../../src/cli/cmd/tui/context/sync-v2" import { createEventSource, createFetch, directory } from "../../fixture/tui-sdk" async function wait(fn: () => boolean, timeout = 2000) { const start = Date.now() while (!fn()) { if (Date.now() - start > timeout) throw new Error("timed out waiting for condition") await Bun.sleep(10) } } function global(payload: Event): GlobalEvent { return { directory, project: "proj_test", payload } } test("sync v2 settles pending tools when a live failure arrives", async () => { const events = createEventSource() const calls = createFetch() let sync!: ReturnType let ready!: () => void const mounted = new Promise((resolve) => { ready = resolve }) function Probe() { sync = useSyncV2() onMount(ready) return } const app = await testRender(() => ( )) try { await mounted events.emit( global({ id: "agent-1", type: "session.next.agent.switched", properties: { sessionID: "session-1", timestamp: 0, agent: "build" }, }), ) events.emit( global({ id: "model-1", type: "session.next.model.switched", properties: { sessionID: "session-1", timestamp: 0, model: { id: "model-1", providerID: "provider-1" }, }, }), ) events.emit( global({ id: "assistant-1", type: "session.next.step.started", properties: { sessionID: "session-1", timestamp: 1, agent: "build", model: { id: "model-1", providerID: "provider-1" }, }, }), ) events.emit( global({ id: "input-1", type: "session.next.tool.input.started", properties: { sessionID: "session-1", timestamp: 2, assistantMessageID: "assistant-1", callID: "call-1", name: "bash", }, }), ) events.emit( global({ id: "failed-1", type: "session.next.tool.failed", properties: { sessionID: "session-1", timestamp: 3, assistantMessageID: "assistant-1", callID: "call-1", error: { type: "unknown", message: "aborted" }, provider: { executed: false }, }, }), ) await wait(() => { const assistant = sync.session.message.fromSession("session-1")[0] return ( assistant?.type === "assistant" && assistant.content[0]?.type === "tool" && assistant.content[0].state.status === "error" ) }) const assistant = sync.session.message.fromSession("session-1")[0] expect(assistant?.type).toBe("assistant") if (assistant?.type !== "assistant") return const tool = assistant.content[0] expect(tool?.type).toBe("tool") if (tool?.type !== "tool") return expect(tool.state.status).toBe("error") if (tool.state.status !== "error") return expect(tool.state.error).toEqual({ type: "unknown", message: "aborted" }) expect(tool.state.input).toEqual({}) expect(tool.state.structured).toEqual({}) expect(tool.state.content).toEqual([]) expect(sync.session.message.fromSession("session-1").map((message) => message.type)).toEqual([ "assistant", "model-switched", "agent-switched", ]) } finally { app.renderer.destroy() } })