import { CommandId, DEFAULT_SERVER_SETTINGS, type DesktopBridge, EnvironmentId, type VcsStatusResult, ProjectId, type OrchestrationShellStreamItem, ProviderDriverKind, ProviderInstanceId, type ServerConfig, type ServerProvider, type TerminalAttachStreamEvent, type TerminalMetadataStreamEvent, ThreadId,} from "@t3tools/contracts";import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { ContextMenuItem } from "@t3tools/contracts"; const showContextMenuFallbackMock = vi.fn< <T extends string>( items: readonly ContextMenuItem<T>[], position?: { x: number; y: number }, ) => Promise<T | null> >(); function registerListener<T>(listeners: Set<(event: T) => void>, listener: (event: T) => void) { listeners.add(listener); return () => { listeners.delete(listener); };} const terminalAttachListeners = new Set<(event: TerminalAttachStreamEvent) => void>();const terminalMetadataListeners = new Set<(event: TerminalMetadataStreamEvent) => void>();const shellStreamListeners = new Set<(event: OrchestrationShellStreamItem) => void>();const gitStatusListeners = new Set<(event: VcsStatusResult) => void>(); const rpcClientMock = { dispose: vi.fn(), terminal: { open: vi.fn(), attach: vi.fn((_input: unknown, listener: (event: TerminalAttachStreamEvent) => void) => registerListener(terminalAttachListeners, listener), ), write: vi.fn(), resize: vi.fn(), clear: vi.fn(), restart: vi.fn(), close: vi.fn(), onMetadata: vi.fn((listener: (event: TerminalMetadataStreamEvent) => void) => registerListener(terminalMetadataListeners, listener), ), }, projects: { searchEntries: vi.fn(), writeFile: vi.fn(), }, filesystem: { browse: vi.fn(), }, sourceControl: { lookupRepository: vi.fn(), cloneRepository: vi.fn(), publishRepository: vi.fn(), }, shell: { openInEditor: vi.fn(), }, vcs: { pull: vi.fn(), refreshStatus: vi.fn(), onStatus: vi.fn((input: { cwd: string }, listener: (event: VcsStatusResult) => void) => registerListener(gitStatusListeners, listener), ), listRefs: vi.fn(), createWorktree: vi.fn(), removeWorktree: vi.fn(), createRef: vi.fn(), switchRef: vi.fn(), init: vi.fn(), }, git: { runStackedAction: vi.fn(), resolvePullRequest: vi.fn(), preparePullRequestThread: vi.fn(), }, review: { getDiffPreview: vi.fn(), }, server: { getConfig: vi.fn(), refreshProviders: vi.fn(), updateProvider: vi.fn(), upsertKeybinding: vi.fn(), getSettings: vi.fn(), updateSettings: vi.fn(), discoverSourceControl: vi.fn(), subscribeConfig: vi.fn(), subscribeLifecycle: vi.fn(), subscribeAuthAccess: vi.fn(), }, orchestration: { dispatchCommand: vi.fn(), getTurnDiff: vi.fn(), getFullThreadDiff: vi.fn(), subscribeShell: vi.fn((listener: (event: OrchestrationShellStreamItem) => void) => registerListener(shellStreamListeners, listener), ), subscribeThread: vi.fn(() => () => undefined), },}; vi.mock("./environments/runtime", () => ({ getPrimaryEnvironmentConnection: () => ({ kind: "primary" as const, knownEnvironment: { id: "environment-local", label: "Primary", source: "manual" as const, target: { httpBaseUrl: "http://localhost:3000", wsBaseUrl: "ws://localhost:3000", }, environmentId: EnvironmentId.make("environment-local"), }, client: rpcClientMock, environmentId: EnvironmentId.make("environment-local"), ensureBootstrapped: async () => undefined, reconnect: async () => undefined, dispose: async () => undefined, }), resetEnvironmentServiceForTests: vi.fn(), resetSavedEnvironmentRegistryStoreForTests: vi.fn(), resetSavedEnvironmentRuntimeStoreForTests: vi.fn(), subscribeEnvironmentConnections: vi.fn(() => () => undefined),})); vi.mock("./contextMenuFallback", () => ({ showContextMenuFallback: showContextMenuFallbackMock,})); function emitEvent<T>(listeners: Set<(event: T) => void>, event: T) { for (const listener of listeners) { listener(event); }} 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;} function createLocalStorageStub(): Storage { const store = new Map<string, string>(); return { getItem: (key) => store.get(key) ?? null, setItem: (key, value) => { store.set(key, value); }, removeItem: (key) => { store.delete(key); }, clear: () => { store.clear(); }, key: (index) => [...store.keys()][index] ?? null, get length() { return store.size; }, };} function makeDesktopBridge(overrides: Partial<DesktopBridge> = {}): DesktopBridge { return { getAppBranding: () => null, getLocalEnvironmentBootstrap: () => null, getClientSettings: async () => null, setClientSettings: async () => undefined, getSavedEnvironmentRegistry: async () => [], setSavedEnvironmentRegistry: async () => undefined, getSavedEnvironmentSecret: async () => null, setSavedEnvironmentSecret: async () => true, removeSavedEnvironmentSecret: async () => undefined, discoverSshHosts: async () => [], ensureSshEnvironment: async () => { throw new Error("ensureSshEnvironment not implemented in test"); }, disconnectSshEnvironment: async () => undefined, fetchSshEnvironmentDescriptor: async () => { throw new Error("fetchSshEnvironmentDescriptor not implemented in test"); }, bootstrapSshBearerSession: async () => { throw new Error("bootstrapSshBearerSession not implemented in test"); }, fetchSshSessionState: async () => { throw new Error("fetchSshSessionState not implemented in test"); }, issueSshWebSocketToken: async () => { throw new Error("issueSshWebSocketToken not implemented in test"); }, onSshPasswordPrompt: () => () => undefined, resolveSshPasswordPrompt: async () => undefined, getServerExposureState: async () => ({ mode: "local-only", endpointUrl: null, advertisedHost: null, tailscaleServeEnabled: false, tailscaleServePort: 443, }), setServerExposureMode: async () => ({ mode: "local-only", endpointUrl: null, advertisedHost: null, tailscaleServeEnabled: false, tailscaleServePort: 443, }), setTailscaleServeEnabled: async (input) => ({ mode: "local-only", endpointUrl: null, advertisedHost: null, tailscaleServeEnabled: input.enabled, tailscaleServePort: input.port ?? 443, }), getAdvertisedEndpoints: async () => [], pickFolder: async () => null, confirm: async () => true, setTheme: async () => undefined, showContextMenu: async () => null, openExternal: async () => true, onMenuAction: () => () => undefined, getUpdateState: async () => { throw new Error("getUpdateState not implemented in test"); }, setUpdateChannel: async () => { throw new Error("setUpdateChannel not implemented in test"); }, checkForUpdate: async () => { throw new Error("checkForUpdate not implemented in test"); }, downloadUpdate: async () => { throw new Error("downloadUpdate not implemented in test"); }, installUpdate: async () => { throw new Error("installUpdate not implemented in test"); }, onUpdateState: () => () => undefined, ...overrides, };} const defaultProviders: ReadonlyArray<ServerProvider> = [ { instanceId: ProviderInstanceId.make("codex"), driver: ProviderDriverKind.make("codex"), enabled: true, installed: true, version: "0.116.0", status: "ready", auth: { status: "authenticated" }, checkedAt: "2026-01-01T00:00:00.000Z", models: [], slashCommands: [], skills: [], },]; const baseEnvironment = { environmentId: EnvironmentId.make("environment-local"), label: "Local environment", platform: { os: "darwin" as const, arch: "arm64" as const, }, serverVersion: "0.0.0-test", capabilities: { repositoryIdentity: true, },}; const baseServerConfig: ServerConfig = { environment: baseEnvironment, auth: { policy: "loopback-browser", bootstrapMethods: ["one-time-token"], sessionMethods: ["browser-session-cookie", "bearer-session-token"], sessionCookieName: "t3_session", }, cwd: "/tmp/workspace", keybindingsConfigPath: "/tmp/workspace/.config/keybindings.json", keybindings: [], issues: [], providers: defaultProviders, availableEditors: ["cursor"], observability: { logsDirectoryPath: "/tmp/workspace/.config/logs", localTracingEnabled: true, otlpTracesEnabled: false, otlpMetricsEnabled: false, }, settings: DEFAULT_SERVER_SETTINGS,}; const baseGitStatus: VcsStatusResult = { isRepo: true, hasPrimaryRemote: true, isDefaultRef: false, refName: "feature/streamed", hasWorkingTreeChanges: false, workingTree: { files: [], insertions: 0, deletions: 0 }, hasUpstream: true, aheadCount: 0, behindCount: 0, pr: null,}; beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); showContextMenuFallbackMock.mockReset(); terminalAttachListeners.clear(); terminalMetadataListeners.clear(); shellStreamListeners.clear(); gitStatusListeners.clear(); const testWindow = getWindowForTest(); Reflect.deleteProperty(testWindow, "desktopBridge"); Object.defineProperty(testWindow, "localStorage", { configurable: true, value: createLocalStorageStub(), });}); afterEach(() => { vi.restoreAllMocks();}); describe("wsApi", () => { it("forwards server config fetches directly to the RPC client", async () => { rpcClientMock.server.getConfig.mockResolvedValue(baseServerConfig); const { createLocalApi } = await import("./localApi"); const api = createLocalApi(rpcClientMock as never); await expect(api.server.getConfig()).resolves.toEqual(baseServerConfig); expect(rpcClientMock.server.getConfig).toHaveBeenCalledWith(); expect(rpcClientMock.server.subscribeConfig).not.toHaveBeenCalled(); expect(rpcClientMock.server.subscribeLifecycle).not.toHaveBeenCalled(); }); it("forwards terminal attach, metadata, and shell stream events", async () => { const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const onTerminalAttachEvent = vi.fn(); const onTerminalMetadataEvent = vi.fn(); const onShellEvent = vi.fn(); api.terminal.attach({ threadId: "thread-1", terminalId: "terminal-1" }, onTerminalAttachEvent); api.terminal.onMetadata(onTerminalMetadataEvent); api.orchestration.subscribeShell(onShellEvent); const terminalAttachEvent = { threadId: "thread-1", terminalId: "terminal-1", type: "output", data: "hello", } satisfies TerminalAttachStreamEvent; emitEvent(terminalAttachListeners, terminalAttachEvent); const terminalMetadataEvent = { type: "upsert", terminal: { threadId: "thread-1", terminalId: "terminal-1", cwd: "/tmp/workspace", worktreePath: null, status: "running", pid: 123, exitCode: null, exitSignal: null, hasRunningSubprocess: true, label: "terminal-1", updatedAt: "2026-02-24T00:00:00.000Z", }, } satisfies TerminalMetadataStreamEvent; emitEvent(terminalMetadataListeners, terminalMetadataEvent); const shellEvent = { kind: "project-upserted" as const, sequence: 1, project: { id: ProjectId.make("project-1"), title: "Project", workspaceRoot: "/tmp/workspace", defaultModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, scripts: [], createdAt: "2026-02-24T00:00:00.000Z", updatedAt: "2026-02-24T00:00:00.000Z", }, } satisfies OrchestrationShellStreamItem; emitEvent(shellStreamListeners, shellEvent); expect(onTerminalAttachEvent).toHaveBeenCalledWith(terminalAttachEvent); expect(onTerminalMetadataEvent).toHaveBeenCalledWith(terminalMetadataEvent); expect(onShellEvent).toHaveBeenCalledWith(shellEvent); }); it("forwards git status stream events", async () => { const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const onStatus = vi.fn(); api.vcs.onStatus({ cwd: "/repo" }, onStatus); const gitStatus = baseGitStatus; emitEvent(gitStatusListeners, gitStatus); expect(rpcClientMock.vcs.onStatus).toHaveBeenCalledWith({ cwd: "/repo" }, onStatus, undefined); expect(onStatus).toHaveBeenCalledWith(gitStatus); }); it("forwards git status refreshes directly to the RPC client", async () => { rpcClientMock.vcs.refreshStatus.mockResolvedValue(baseGitStatus); const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); await api.vcs.refreshStatus({ cwd: "/repo" }); expect(rpcClientMock.vcs.refreshStatus).toHaveBeenCalledWith({ cwd: "/repo" }); }); it("forwards shell stream subscription options to the RPC client", async () => { const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const onShellEvent = vi.fn(); const onResubscribe = vi.fn(); api.orchestration.subscribeShell(onShellEvent, { onResubscribe }); expect(rpcClientMock.orchestration.subscribeShell).toHaveBeenCalledWith(onShellEvent, { onResubscribe, }); }); it("sends orchestration dispatch commands as the direct RPC payload", async () => { rpcClientMock.orchestration.dispatchCommand.mockResolvedValue({ sequence: 1 }); const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const command = { type: "project.create", commandId: CommandId.make("cmd-1"), projectId: ProjectId.make("project-1"), title: "Project", workspaceRoot: "/tmp/project", defaultModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, createdAt: "2026-02-24T00:00:00.000Z", } as const; await api.orchestration.dispatchCommand(command); expect(rpcClientMock.orchestration.dispatchCommand).toHaveBeenCalledWith(command); }); it("forwards workspace file writes to the project RPC", async () => { rpcClientMock.projects.writeFile.mockResolvedValue({ relativePath: "plan.md" }); const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); await api.projects.writeFile({ cwd: "/tmp/project", relativePath: "plan.md", contents: "# Plan\n", }); expect(rpcClientMock.projects.writeFile).toHaveBeenCalledWith({ cwd: "/tmp/project", relativePath: "plan.md", contents: "# Plan\n", }); }); it("forwards filesystem browse requests to the RPC client", async () => { rpcClientMock.filesystem.browse.mockResolvedValue({ parentPath: "/tmp/project/",Lines 1–500 of 751. Load more
import { CommandId, DEFAULT_SERVER_SETTINGS, type DesktopBridge, EnvironmentId, type VcsStatusResult, ProjectId, type OrchestrationShellStreamItem, ProviderDriverKind, ProviderInstanceId, type ServerConfig, type ServerProvider, type TerminalAttachStreamEvent, type TerminalMetadataStreamEvent, ThreadId, } from "@t3tools/contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { ContextMenuItem } from "@t3tools/contracts"; const showContextMenuFallbackMock = vi.fn< <T extends string>( items: readonly ContextMenuItem<T>[], position?: { x: number; y: number }, ) => Promise<T | null> >(); function registerListener<T>(listeners: Set<(event: T) => void>, listener: (event: T) => void) { listeners.add(listener); return () => { listeners.delete(listener); }; } const terminalAttachListeners = new Set<(event: TerminalAttachStreamEvent) => void>(); const terminalMetadataListeners = new Set<(event: TerminalMetadataStreamEvent) => void>(); const shellStreamListeners = new Set<(event: OrchestrationShellStreamItem) => void>(); const gitStatusListeners = new Set<(event: VcsStatusResult) => void>(); const rpcClientMock = { dispose: vi.fn(), terminal: { open: vi.fn(), attach: vi.fn((_input: unknown, listener: (event: TerminalAttachStreamEvent) => void) => registerListener(terminalAttachListeners, listener), ), write: vi.fn(), resize: vi.fn(), clear: vi.fn(), restart: vi.fn(), close: vi.fn(), onMetadata: vi.fn((listener: (event: TerminalMetadataStreamEvent) => void) => registerListener(terminalMetadataListeners, listener), ), }, projects: { searchEntries: vi.fn(), writeFile: vi.fn(), }, filesystem: { browse: vi.fn(), }, sourceControl: { lookupRepository: vi.fn(), cloneRepository: vi.fn(), publishRepository: vi.fn(), }, shell: { openInEditor: vi.fn(), }, vcs: { pull: vi.fn(), refreshStatus: vi.fn(), onStatus: vi.fn((input: { cwd: string }, listener: (event: VcsStatusResult) => void) => registerListener(gitStatusListeners, listener), ), listRefs: vi.fn(), createWorktree: vi.fn(), removeWorktree: vi.fn(), createRef: vi.fn(), switchRef: vi.fn(), init: vi.fn(), }, git: { runStackedAction: vi.fn(), resolvePullRequest: vi.fn(), preparePullRequestThread: vi.fn(), }, review: { getDiffPreview: vi.fn(), }, server: { getConfig: vi.fn(), refreshProviders: vi.fn(), updateProvider: vi.fn(), upsertKeybinding: vi.fn(), getSettings: vi.fn(), updateSettings: vi.fn(), discoverSourceControl: vi.fn(), subscribeConfig: vi.fn(), subscribeLifecycle: vi.fn(), subscribeAuthAccess: vi.fn(), }, orchestration: { dispatchCommand: vi.fn(), getTurnDiff: vi.fn(), getFullThreadDiff: vi.fn(), subscribeShell: vi.fn((listener: (event: OrchestrationShellStreamItem) => void) => registerListener(shellStreamListeners, listener), ), subscribeThread: vi.fn(() => () => undefined), }, }; vi.mock("./environments/runtime", () => ({ getPrimaryEnvironmentConnection: () => ({ kind: "primary" as const, knownEnvironment: { id: "environment-local", label: "Primary", source: "manual" as const, target: { httpBaseUrl: "http://localhost:3000", wsBaseUrl: "ws://localhost:3000", }, environmentId: EnvironmentId.make("environment-local"), }, client: rpcClientMock, environmentId: EnvironmentId.make("environment-local"), ensureBootstrapped: async () => undefined, reconnect: async () => undefined, dispose: async () => undefined, }), resetEnvironmentServiceForTests: vi.fn(), resetSavedEnvironmentRegistryStoreForTests: vi.fn(), resetSavedEnvironmentRuntimeStoreForTests: vi.fn(), subscribeEnvironmentConnections: vi.fn(() => () => undefined), })); vi.mock("./contextMenuFallback", () => ({ showContextMenuFallback: showContextMenuFallbackMock, })); function emitEvent<T>(listeners: Set<(event: T) => void>, event: T) { for (const listener of listeners) { listener(event); } } 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; } function createLocalStorageStub(): Storage { const store = new Map<string, string>(); return { getItem: (key) => store.get(key) ?? null, setItem: (key, value) => { store.set(key, value); }, removeItem: (key) => { store.delete(key); }, clear: () => { store.clear(); }, key: (index) => [...store.keys()][index] ?? null, get length() { return store.size; }, }; } function makeDesktopBridge(overrides: Partial<DesktopBridge> = {}): DesktopBridge { return { getAppBranding: () => null, getLocalEnvironmentBootstrap: () => null, getClientSettings: async () => null, setClientSettings: async () => undefined, getSavedEnvironmentRegistry: async () => [], setSavedEnvironmentRegistry: async () => undefined, getSavedEnvironmentSecret: async () => null, setSavedEnvironmentSecret: async () => true, removeSavedEnvironmentSecret: async () => undefined, discoverSshHosts: async () => [], ensureSshEnvironment: async () => { throw new Error("ensureSshEnvironment not implemented in test"); }, disconnectSshEnvironment: async () => undefined, fetchSshEnvironmentDescriptor: async () => { throw new Error("fetchSshEnvironmentDescriptor not implemented in test"); }, bootstrapSshBearerSession: async () => { throw new Error("bootstrapSshBearerSession not implemented in test"); }, fetchSshSessionState: async () => { throw new Error("fetchSshSessionState not implemented in test"); }, issueSshWebSocketToken: async () => { throw new Error("issueSshWebSocketToken not implemented in test"); }, onSshPasswordPrompt: () => () => undefined, resolveSshPasswordPrompt: async () => undefined, getServerExposureState: async () => ({ mode: "local-only", endpointUrl: null, advertisedHost: null, tailscaleServeEnabled: false, tailscaleServePort: 443, }), setServerExposureMode: async () => ({ mode: "local-only", endpointUrl: null, advertisedHost: null, tailscaleServeEnabled: false, tailscaleServePort: 443, }), setTailscaleServeEnabled: async (input) => ({ mode: "local-only", endpointUrl: null, advertisedHost: null, tailscaleServeEnabled: input.enabled, tailscaleServePort: input.port ?? 443, }), getAdvertisedEndpoints: async () => [], pickFolder: async () => null, confirm: async () => true, setTheme: async () => undefined, showContextMenu: async () => null, openExternal: async () => true, onMenuAction: () => () => undefined, getUpdateState: async () => { throw new Error("getUpdateState not implemented in test"); }, setUpdateChannel: async () => { throw new Error("setUpdateChannel not implemented in test"); }, checkForUpdate: async () => { throw new Error("checkForUpdate not implemented in test"); }, downloadUpdate: async () => { throw new Error("downloadUpdate not implemented in test"); }, installUpdate: async () => { throw new Error("installUpdate not implemented in test"); }, onUpdateState: () => () => undefined, ...overrides, }; } const defaultProviders: ReadonlyArray<ServerProvider> = [ { instanceId: ProviderInstanceId.make("codex"), driver: ProviderDriverKind.make("codex"), enabled: true, installed: true, version: "0.116.0", status: "ready", auth: { status: "authenticated" }, checkedAt: "2026-01-01T00:00:00.000Z", models: [], slashCommands: [], skills: [], }, ]; const baseEnvironment = { environmentId: EnvironmentId.make("environment-local"), label: "Local environment", platform: { os: "darwin" as const, arch: "arm64" as const, }, serverVersion: "0.0.0-test", capabilities: { repositoryIdentity: true, }, }; const baseServerConfig: ServerConfig = { environment: baseEnvironment, auth: { policy: "loopback-browser", bootstrapMethods: ["one-time-token"], sessionMethods: ["browser-session-cookie", "bearer-session-token"], sessionCookieName: "t3_session", }, cwd: "/tmp/workspace", keybindingsConfigPath: "/tmp/workspace/.config/keybindings.json", keybindings: [], issues: [], providers: defaultProviders, availableEditors: ["cursor"], observability: { logsDirectoryPath: "/tmp/workspace/.config/logs", localTracingEnabled: true, otlpTracesEnabled: false, otlpMetricsEnabled: false, }, settings: DEFAULT_SERVER_SETTINGS, }; const baseGitStatus: VcsStatusResult = { isRepo: true, hasPrimaryRemote: true, isDefaultRef: false, refName: "feature/streamed", hasWorkingTreeChanges: false, workingTree: { files: [], insertions: 0, deletions: 0 }, hasUpstream: true, aheadCount: 0, behindCount: 0, pr: null, }; beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); showContextMenuFallbackMock.mockReset(); terminalAttachListeners.clear(); terminalMetadataListeners.clear(); shellStreamListeners.clear(); gitStatusListeners.clear(); const testWindow = getWindowForTest(); Reflect.deleteProperty(testWindow, "desktopBridge"); Object.defineProperty(testWindow, "localStorage", { configurable: true, value: createLocalStorageStub(), }); }); afterEach(() => { vi.restoreAllMocks(); }); describe("wsApi", () => { it("forwards server config fetches directly to the RPC client", async () => { rpcClientMock.server.getConfig.mockResolvedValue(baseServerConfig); const { createLocalApi } = await import("./localApi"); const api = createLocalApi(rpcClientMock as never); await expect(api.server.getConfig()).resolves.toEqual(baseServerConfig); expect(rpcClientMock.server.getConfig).toHaveBeenCalledWith(); expect(rpcClientMock.server.subscribeConfig).not.toHaveBeenCalled(); expect(rpcClientMock.server.subscribeLifecycle).not.toHaveBeenCalled(); }); it("forwards terminal attach, metadata, and shell stream events", async () => { const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const onTerminalAttachEvent = vi.fn(); const onTerminalMetadataEvent = vi.fn(); const onShellEvent = vi.fn(); api.terminal.attach({ threadId: "thread-1", terminalId: "terminal-1" }, onTerminalAttachEvent); api.terminal.onMetadata(onTerminalMetadataEvent); api.orchestration.subscribeShell(onShellEvent); const terminalAttachEvent = { threadId: "thread-1", terminalId: "terminal-1", type: "output", data: "hello", } satisfies TerminalAttachStreamEvent; emitEvent(terminalAttachListeners, terminalAttachEvent); const terminalMetadataEvent = { type: "upsert", terminal: { threadId: "thread-1", terminalId: "terminal-1", cwd: "/tmp/workspace", worktreePath: null, status: "running", pid: 123, exitCode: null, exitSignal: null, hasRunningSubprocess: true, label: "terminal-1", updatedAt: "2026-02-24T00:00:00.000Z", }, } satisfies TerminalMetadataStreamEvent; emitEvent(terminalMetadataListeners, terminalMetadataEvent); const shellEvent = { kind: "project-upserted" as const, sequence: 1, project: { id: ProjectId.make("project-1"), title: "Project", workspaceRoot: "/tmp/workspace", defaultModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, scripts: [], createdAt: "2026-02-24T00:00:00.000Z", updatedAt: "2026-02-24T00:00:00.000Z", }, } satisfies OrchestrationShellStreamItem; emitEvent(shellStreamListeners, shellEvent); expect(onTerminalAttachEvent).toHaveBeenCalledWith(terminalAttachEvent); expect(onTerminalMetadataEvent).toHaveBeenCalledWith(terminalMetadataEvent); expect(onShellEvent).toHaveBeenCalledWith(shellEvent); }); it("forwards git status stream events", async () => { const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const onStatus = vi.fn(); api.vcs.onStatus({ cwd: "/repo" }, onStatus); const gitStatus = baseGitStatus; emitEvent(gitStatusListeners, gitStatus); expect(rpcClientMock.vcs.onStatus).toHaveBeenCalledWith({ cwd: "/repo" }, onStatus, undefined); expect(onStatus).toHaveBeenCalledWith(gitStatus); }); it("forwards git status refreshes directly to the RPC client", async () => { rpcClientMock.vcs.refreshStatus.mockResolvedValue(baseGitStatus); const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); await api.vcs.refreshStatus({ cwd: "/repo" }); expect(rpcClientMock.vcs.refreshStatus).toHaveBeenCalledWith({ cwd: "/repo" }); }); it("forwards shell stream subscription options to the RPC client", async () => { const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const onShellEvent = vi.fn(); const onResubscribe = vi.fn(); api.orchestration.subscribeShell(onShellEvent, { onResubscribe }); expect(rpcClientMock.orchestration.subscribeShell).toHaveBeenCalledWith(onShellEvent, { onResubscribe, }); }); it("sends orchestration dispatch commands as the direct RPC payload", async () => { rpcClientMock.orchestration.dispatchCommand.mockResolvedValue({ sequence: 1 }); const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); const command = { type: "project.create", commandId: CommandId.make("cmd-1"), projectId: ProjectId.make("project-1"), title: "Project", workspaceRoot: "/tmp/project", defaultModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, createdAt: "2026-02-24T00:00:00.000Z", } as const; await api.orchestration.dispatchCommand(command); expect(rpcClientMock.orchestration.dispatchCommand).toHaveBeenCalledWith(command); }); it("forwards workspace file writes to the project RPC", async () => { rpcClientMock.projects.writeFile.mockResolvedValue({ relativePath: "plan.md" }); const { createEnvironmentApi } = await import("./environmentApi"); const api = createEnvironmentApi(rpcClientMock as never); await api.projects.writeFile({ cwd: "/tmp/project", relativePath: "plan.md", contents: "# Plan\n", }); expect(rpcClientMock.projects.writeFile).toHaveBeenCalledWith({ cwd: "/tmp/project", relativePath: "plan.md", contents: "# Plan\n", }); }); it("forwards filesystem browse requests to the RPC client", async () => { rpcClientMock.filesystem.browse.mockResolvedValue({ parentPath: "/tmp/project/",