import { CommandId, type ContextMenuItem, EventId, ORCHESTRATION_WS_CHANNELS, ORCHESTRATION_WS_METHODS, type OrchestrationEvent, ProjectId, ThreadId, type WsPushChannel, type WsPushData, type WsPushMessage, WS_CHANNELS, WS_METHODS, type WsPush, type ServerProvider, } from "@t3tools/contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const requestMock = vi.fn<(...args: Array) => Promise>(); const showContextMenuFallbackMock = vi.fn< ( items: readonly ContextMenuItem[], position?: { x: number; y: number }, ) => Promise >(); const channelListeners = new Map void>>(); const latestPushByChannel = new Map(); const subscribeMock = vi.fn< ( channel: string, listener: (message: WsPush) => void, options?: { replayLatest?: boolean }, ) => () => void >((channel, listener, options) => { const listeners = channelListeners.get(channel) ?? new Set<(message: WsPush) => void>(); listeners.add(listener); channelListeners.set(channel, listeners); const latest = latestPushByChannel.get(channel); if (latest && options?.replayLatest) { listener(latest); } return () => { listeners.delete(listener); if (listeners.size === 0) { channelListeners.delete(channel); } }; }); vi.mock("./wsTransport", () => { return { WsTransport: class MockWsTransport { request = requestMock; subscribe = subscribeMock; getLatestPush(channel: string) { return latestPushByChannel.get(channel) ?? null; } }, }; }); vi.mock("./contextMenuFallback", () => ({ showContextMenuFallback: showContextMenuFallbackMock, })); let nextPushSequence = 1; function emitPush(channel: C, data: WsPushData): void { const listeners = channelListeners.get(channel); const message = { type: "push" as const, sequence: nextPushSequence++, channel, data, } as WsPushMessage; latestPushByChannel.set(channel, message); if (!listeners) return; for (const listener of listeners) { listener(message); } } function getWindowForTest(): Window & typeof globalThis & { desktopBridge?: unknown } { const testGlobal = globalThis as typeof globalThis & { window?: Window & typeof globalThis & { desktopBridge?: unknown }; }; if (!testGlobal.window) { testGlobal.window = {} as Window & typeof globalThis & { desktopBridge?: unknown }; } return testGlobal.window; } const defaultProviders: ReadonlyArray = [ { provider: "codex", enabled: true, installed: true, version: "0.116.0", status: "ready", auth: { status: "authenticated" }, checkedAt: "2026-01-01T00:00:00.000Z", models: [], }, ]; beforeEach(() => { vi.resetModules(); requestMock.mockReset(); showContextMenuFallbackMock.mockReset(); subscribeMock.mockClear(); channelListeners.clear(); latestPushByChannel.clear(); nextPushSequence = 1; Reflect.deleteProperty(getWindowForTest(), "desktopBridge"); }); afterEach(() => { vi.restoreAllMocks(); }); describe("wsNativeApi", () => { it("delivers and caches valid server.welcome payloads", async () => { const { createWsNativeApi, onServerWelcome } = await import("./wsNativeApi"); createWsNativeApi(); const listener = vi.fn(); onServerWelcome(listener); const payload = { cwd: "/tmp/workspace", projectName: "t3-code" }; emitPush(WS_CHANNELS.serverWelcome, payload); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith(expect.objectContaining(payload)); const lateListener = vi.fn(); onServerWelcome(lateListener); expect(lateListener).toHaveBeenCalledTimes(1); expect(lateListener).toHaveBeenCalledWith(expect.objectContaining(payload)); }); it("preserves bootstrap ids from server.welcome payloads", async () => { const { createWsNativeApi, onServerWelcome } = await import("./wsNativeApi"); createWsNativeApi(); const listener = vi.fn(); onServerWelcome(listener); emitPush(WS_CHANNELS.serverWelcome, { cwd: "/tmp/workspace", projectName: "t3-code", bootstrapProjectId: ProjectId.makeUnsafe("project-1"), bootstrapThreadId: ThreadId.makeUnsafe("thread-1"), }); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith( expect.objectContaining({ cwd: "/tmp/workspace", projectName: "t3-code", bootstrapProjectId: "project-1", bootstrapThreadId: "thread-1", }), ); }); it("delivers successive server.welcome payloads to active listeners", async () => { const { createWsNativeApi, onServerWelcome } = await import("./wsNativeApi"); createWsNativeApi(); const listener = vi.fn(); onServerWelcome(listener); emitPush(WS_CHANNELS.serverWelcome, { cwd: "/tmp/one", projectName: "one" }); emitPush(WS_CHANNELS.serverWelcome, { cwd: "/tmp/workspace", projectName: "t3-code" }); expect(listener).toHaveBeenCalledTimes(2); expect(listener).toHaveBeenLastCalledWith( expect.objectContaining({ cwd: "/tmp/workspace", projectName: "t3-code", }), ); }); it("delivers and caches valid server.configUpdated payloads", async () => { const { createWsNativeApi, onServerConfigUpdated } = await import("./wsNativeApi"); createWsNativeApi(); const listener = vi.fn(); onServerConfigUpdated(listener); const payload = { issues: [ { kind: "keybindings.invalid-entry", index: 1, message: "Entry at index 1 is invalid.", }, ], } as const; emitPush(WS_CHANNELS.serverConfigUpdated, payload); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith(payload); const lateListener = vi.fn(); onServerConfigUpdated(lateListener); expect(lateListener).toHaveBeenCalledTimes(1); expect(lateListener).toHaveBeenCalledWith(payload); }); it("delivers successive server.configUpdated payloads to active listeners", async () => { const { createWsNativeApi, onServerConfigUpdated } = await import("./wsNativeApi"); createWsNativeApi(); const listener = vi.fn(); onServerConfigUpdated(listener); emitPush(WS_CHANNELS.serverConfigUpdated, { issues: [{ kind: "keybindings.malformed-config", message: "bad json" }], }); emitPush(WS_CHANNELS.serverConfigUpdated, { issues: [], }); expect(listener).toHaveBeenCalledTimes(2); expect(listener).toHaveBeenLastCalledWith({ issues: [], }); }); it("delivers and caches valid server.providersUpdated payloads", async () => { const { createWsNativeApi, onServerProvidersUpdated } = await import("./wsNativeApi"); createWsNativeApi(); const listener = vi.fn(); onServerProvidersUpdated(listener); const payload = { providers: defaultProviders, } as const; emitPush(WS_CHANNELS.serverProvidersUpdated, payload); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith(payload); const lateListener = vi.fn(); onServerProvidersUpdated(lateListener); expect(lateListener).toHaveBeenCalledTimes(1); expect(lateListener).toHaveBeenCalledWith(payload); }); it("forwards valid terminal and orchestration events", async () => { const { createWsNativeApi } = await import("./wsNativeApi"); const api = createWsNativeApi(); const onTerminalEvent = vi.fn(); const onDomainEvent = vi.fn(); const onActionProgress = vi.fn(); api.terminal.onEvent(onTerminalEvent); api.orchestration.onDomainEvent(onDomainEvent); api.git.onActionProgress(onActionProgress); const terminalEvent = { threadId: "thread-1", terminalId: "terminal-1", createdAt: "2026-02-24T00:00:00.000Z", type: "output", data: "hello", } as const; emitPush(WS_CHANNELS.terminalEvent, terminalEvent); const orchestrationEvent = { sequence: 1, eventId: EventId.makeUnsafe("event-1"), aggregateKind: "project", aggregateId: ProjectId.makeUnsafe("project-1"), occurredAt: "2026-02-24T00:00:00.000Z", commandId: null, causationEventId: null, correlationId: null, metadata: {}, type: "project.created", payload: { projectId: ProjectId.makeUnsafe("project-1"), title: "Project", workspaceRoot: "/tmp/workspace", defaultModelSelection: null, scripts: [], createdAt: "2026-02-24T00:00:00.000Z", updatedAt: "2026-02-24T00:00:00.000Z", }, } satisfies Extract; emitPush(ORCHESTRATION_WS_CHANNELS.domainEvent, orchestrationEvent); emitPush(WS_CHANNELS.gitActionProgress, { actionId: "action-1", cwd: "/repo", action: "commit", kind: "phase_started", phase: "commit", label: "Committing...", }); expect(onTerminalEvent).toHaveBeenCalledTimes(1); expect(onTerminalEvent).toHaveBeenCalledWith(terminalEvent); expect(onDomainEvent).toHaveBeenCalledTimes(1); expect(onDomainEvent).toHaveBeenCalledWith(orchestrationEvent); expect(onActionProgress).toHaveBeenCalledTimes(1); expect(onActionProgress).toHaveBeenCalledWith({ actionId: "action-1", cwd: "/repo", action: "commit", kind: "phase_started", phase: "commit", label: "Committing...", }); }); it("wraps orchestration dispatch commands in the command envelope", async () => { requestMock.mockResolvedValue(undefined); const { createWsNativeApi } = await import("./wsNativeApi"); const api = createWsNativeApi(); const command = { type: "project.create", commandId: CommandId.makeUnsafe("cmd-1"), projectId: ProjectId.makeUnsafe("project-1"), title: "Project", workspaceRoot: "/tmp/project", defaultModelSelection: { provider: "codex", model: "gpt-5-codex", }, createdAt: "2026-02-24T00:00:00.000Z", } as const; await api.orchestration.dispatchCommand(command); expect(requestMock).toHaveBeenCalledWith(ORCHESTRATION_WS_METHODS.dispatchCommand, { command, }); }); it("forwards workspace file writes to the websocket project method", async () => { requestMock.mockResolvedValue({ relativePath: "plan.md" }); const { createWsNativeApi } = await import("./wsNativeApi"); const api = createWsNativeApi(); await api.projects.writeFile({ cwd: "/tmp/project", relativePath: "plan.md", contents: "# Plan\n", }); expect(requestMock).toHaveBeenCalledWith(WS_METHODS.projectsWriteFile, { cwd: "/tmp/project", relativePath: "plan.md", contents: "# Plan\n", }); }); it("uses no client timeout for git.runStackedAction", async () => { requestMock.mockResolvedValue({ action: "commit", branch: { status: "skipped_not_requested" }, commit: { status: "created", commitSha: "abc1234", subject: "Test" }, push: { status: "skipped_not_requested" }, pr: { status: "skipped_not_requested" }, }); const { createWsNativeApi } = await import("./wsNativeApi"); const api = createWsNativeApi(); await api.git.runStackedAction({ actionId: "action-1", cwd: "/repo", action: "commit", }); expect(requestMock).toHaveBeenCalledWith( WS_METHODS.gitRunStackedAction, { actionId: "action-1", cwd: "/repo", action: "commit", }, { timeoutMs: null }, ); }); it("forwards full-thread diff requests to the orchestration websocket method", async () => { requestMock.mockResolvedValue({ diff: "patch" }); const { createWsNativeApi } = await import("./wsNativeApi"); const api = createWsNativeApi(); await api.orchestration.getFullThreadDiff({ threadId: ThreadId.makeUnsafe("thread-1"), toTurnCount: 1, }); expect(requestMock).toHaveBeenCalledWith(ORCHESTRATION_WS_METHODS.getFullThreadDiff, { threadId: "thread-1", toTurnCount: 1, }); }); it("forwards context menu metadata to desktop bridge", async () => { const showContextMenu = vi.fn().mockResolvedValue("delete"); Object.defineProperty(getWindowForTest(), "desktopBridge", { configurable: true, writable: true, value: { showContextMenu, }, }); const { createWsNativeApi } = await import("./wsNativeApi"); const api = createWsNativeApi(); await api.contextMenu.show( [ { id: "rename", label: "Rename thread" }, { id: "delete", label: "Delete", destructive: true }, ], { x: 200, y: 300 }, ); expect(showContextMenu).toHaveBeenCalledWith( [ { id: "rename", label: "Rename thread" }, { id: "delete", label: "Delete", destructive: true }, ], { x: 200, y: 300 }, ); }); it("uses fallback context menu when desktop bridge is unavailable", async () => { showContextMenuFallbackMock.mockResolvedValue("delete"); Reflect.deleteProperty(getWindowForTest(), "desktopBridge"); const { createWsNativeApi } = await import("./wsNativeApi"); const api = createWsNativeApi(); await api.contextMenu.show([{ id: "delete", label: "Delete", destructive: true }], { x: 20, y: 30, }); expect(showContextMenuFallbackMock).toHaveBeenCalledWith( [{ id: "delete", label: "Delete", destructive: true }], { x: 20, y: 30 }, ); }); });