import { afterEach, expect, expectTypeOf, test, vi } from "vitest"; import { z } from "zod"; import { DaemonClient, type DaemonTransport } from "./daemon-client"; import { encodeFileTransferFrame, FileTransferOpcode, } from "@getpaseo/protocol/binary-frames/index"; import { asUint8Array, decodeTerminalResizePayload, decodeTerminalStreamFrame, encodeTerminalSnapshotPayload, encodeTerminalStreamFrame, TerminalStreamOpcode, } from "@getpaseo/protocol/terminal-stream-protocol"; expectTypeOf<"getGitDiff" extends keyof DaemonClient ? true : false>().toEqualTypeOf(); expectTypeOf< "getHighlightedDiff" extends keyof DaemonClient ? true : false >().toEqualTypeOf(); expectTypeOf< "exploreFileSystem" extends keyof DaemonClient ? true : false >().toEqualTypeOf(); function createMockLogger() { return { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; } function createMockTransport() { const sent: Array = []; let onMessage: (data: unknown) => void = () => {}; let onOpen: () => void = () => {}; let onClose: (_event?: unknown) => void = () => {}; let onError: (_event?: unknown) => void = () => {}; let serverInfoOrdinal = 1; const transport: DaemonTransport = { send: (data) => sent.push(data), close: () => {}, onMessage: (handler) => { onMessage = handler; return () => {}; }, onOpen: (handler) => { onOpen = handler; return () => {}; }, onClose: (handler) => { onClose = handler; return () => {}; }, onError: (handler) => { onError = handler; return () => {}; }, }; return { transport, sent, triggerOpen: (options?: { preserveSent?: boolean }) => { onOpen(); if (!options?.preserveSent) { // Ignore HELLO handshake payloads in assertions. sent.length = 0; } onMessage( JSON.stringify({ type: "session", message: { type: "status", payload: { status: "server_info", serverId: `srv_test_${serverInfoOrdinal++}`, hostname: null, version: null, }, }, }), ); }, triggerClose: (event?: unknown) => onClose(event), triggerError: (event?: unknown) => onError(event), triggerMessage: (data: unknown) => onMessage(data), }; } function wrapSessionMessage(message: unknown): string { return JSON.stringify({ type: "session", message, }); } function assertStr(data: string | Uint8Array | ArrayBuffer | undefined): string { if (typeof data !== "string") throw new Error("Expected string frame"); return data; } function parseSentFrame( data: string | Uint8Array | ArrayBuffer | undefined, ): Record { return z .object({ type: z.literal("session"), message: z.record(z.unknown()), }) .parse(JSON.parse(assertStr(data))).message; } const clients: DaemonClient[] = []; afterEach(async () => { await Promise.all(clients.map((client) => client.close())); clients.length = 0; }); test("dedupes in-flight checkout status requests per agentId", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const p1 = client.getCheckoutStatus("/tmp/project"); const p2 = client.getCheckoutStatus("/tmp/project"); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); const response = { type: "session", message: { type: "checkout_status_response", payload: { cwd: "/tmp/project", error: null, requestId: request.requestId, isGit: false, isPaseoOwnedWorktree: false, repoRoot: null, currentBranch: null, isDirty: null, baseRef: null, aheadBehind: null, aheadOfOrigin: null, behindOfOrigin: null, hasRemote: false, remoteUrl: null, }, }, }; mock.triggerMessage(JSON.stringify(response)); const [r1, r2] = await Promise.all([p1, p2]); expect(r1).toMatchObject({ cwd: "/tmp/project", requestId: request.requestId, isGit: false, }); expect(r2).toMatchObject({ cwd: "/tmp/project", requestId: request.requestId, isGit: false, }); // After completion, a new call should issue a new request. const p3 = client.getCheckoutStatus("/tmp/project"); expect(mock.sent).toHaveLength(2); const request2 = parseSentFrame(mock.sent[1]); mock.triggerMessage( JSON.stringify({ ...response, message: { ...response.message, payload: { ...response.message.payload, requestId: request2.requestId }, }, }), ); await expect(p3).resolves.toMatchObject({ cwd: "/tmp/project", requestId: request2.requestId, isGit: false, }); }); test("passes password as HTTP bearer header and WebSocket subprotocol", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const transportFactory = vi.fn(() => mock.transport); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", password: "shared-secret", logger, reconnect: { enabled: false }, transportFactory, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; expect(transportFactory).toHaveBeenCalledWith({ url: "ws://test", headers: { Authorization: "Bearer shared-secret" }, protocols: ["paseo.bearer.shared-secret"], }); }); test("advertises client capabilities in hello", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen({ preserveSent: true }); await connectPromise; expect(mock.sent).toHaveLength(1); expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "hello", clientId: "clsk_unit_test", clientType: "cli", protocolVersion: 1, capabilities: { custom_mode_icons: true, reasoning_merge_enum: true, }, }); }); test("does not reconnect after close when ensureConnected is called", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; expect(client.getConnectionState().status).toBe("connected"); await client.close(); expect(client.getConnectionState().status).toBe("disposed"); client.ensureConnected(); expect(client.getConnectionState().status).toBe("disposed"); }); test("keeps the transport connected when a session RPC ping times out", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; expect(client.getConnectionState().status).toBe("connected"); await expect(client.ping({ timeoutMs: 1 })).rejects.toThrow("Timeout waiting for message"); expect(client.getConnectionState().status).toBe("connected"); }); test("reconnects after repeated top-level liveness checks time out", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; expect(client.getConnectionState().status).toBe("connected"); await expect(client.checkLiveness({ timeoutMs: 1 })).rejects.toThrow("Liveness check timed out"); expect(client.getConnectionState().status).toBe("connected"); await expect(client.checkLiveness({ timeoutMs: 1 })).rejects.toThrow("Liveness check timed out"); expect(client.getConnectionState()).toEqual({ status: "disconnected", reason: "Liveness check timed out (1ms)", }); }); test("resets liveness failures after a top-level pong", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; await expect(client.checkLiveness({ timeoutMs: 1 })).rejects.toThrow("Liveness check timed out"); const healthyProbe = client.checkLiveness({ timeoutMs: 100 }); mock.triggerMessage(JSON.stringify({ type: "pong" })); await expect(healthyProbe).resolves.toEqual({ rttMs: expect.any(Number) }); await expect(client.checkLiveness({ timeoutMs: 1 })).rejects.toThrow("Liveness check timed out"); expect(client.getConnectionState().status).toBe("connected"); }); test("listDirectory sends a list file explorer request and returns directory entries", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const responsePromise = client.listDirectory("/tmp/project", "src", "req-list"); expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "file_explorer_request", cwd: "/tmp/project", path: "src", mode: "list", requestId: "req-list", }, }); mock.triggerMessage( wrapSessionMessage({ type: "file_explorer_response", payload: { cwd: "/tmp/project", path: "src", mode: "list", directory: { path: "src", entries: [ { name: "index.ts", path: "src/index.ts", kind: "file", size: 12, modifiedAt: "2026-05-02T00:00:00.000Z", }, ], }, file: null, error: null, requestId: "req-list", }, }), ); await expect(responsePromise).resolves.toEqual({ path: "src", entries: [ { name: "index.ts", path: "src/index.ts", kind: "file", size: 12, modifiedAt: "2026-05-02T00:00:00.000Z", }, ], }); }); test("readFile hides legacy base64 behind bytes", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const responsePromise = client.readFile("/tmp/project", "logo.png", "req-file"); expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "file_explorer_request", cwd: "/tmp/project", path: "logo.png", mode: "file", acceptBinary: true, requestId: "req-file", }, }); mock.triggerMessage( wrapSessionMessage({ type: "file_explorer_response", payload: { cwd: "/tmp/project", path: "logo.png", mode: "file", directory: null, file: { path: "logo.png", kind: "image", encoding: "base64", content: "aGVsbG8=", mimeType: "image/png", size: 5, modifiedAt: "2026-05-02T00:00:00.000Z", }, error: null, requestId: "req-file", }, }), ); const result = await responsePromise; expect(result).toMatchObject({ mime: "image/png", size: 5, path: "logo.png", kind: "image", modifiedAt: "2026-05-02T00:00:00.000Z", }); expect(new TextDecoder().decode(result.bytes)).toBe("hello"); }); test("readFile resolves from binary file frames when the daemon supports them", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const responsePromise = client.readFile("/tmp/project", "logo.png", "req-binary"); expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "file_explorer_request", cwd: "/tmp/project", path: "logo.png", mode: "file", acceptBinary: true, requestId: "req-binary", }, }); mock.triggerMessage( encodeFileTransferFrame({ opcode: FileTransferOpcode.FileBegin, requestId: "req-binary", metadata: { mime: "image/png", size: 5, encoding: "binary", modifiedAt: "2026-05-02T00:00:00.000Z", }, }), ); mock.triggerMessage( encodeFileTransferFrame({ opcode: FileTransferOpcode.FileChunk, requestId: "req-binary", payload: new TextEncoder().encode("hello"), }), ); mock.triggerMessage( encodeFileTransferFrame({ opcode: FileTransferOpcode.FileEnd, requestId: "req-binary", }), ); const result = await responsePromise; expect(result).toMatchObject({ mime: "image/png", size: 5, path: "logo.png", kind: "image", modifiedAt: "2026-05-02T00:00:00.000Z", }); expect(new TextDecoder().decode(result.bytes)).toBe("hello"); }); test("normalizes workspace_setup_progress into a workspace-scoped daemon event", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const events: Array[0]>[0]> = []; client.subscribe((event) => { events.push(event); }); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; mock.triggerMessage( wrapSessionMessage({ type: "workspace_setup_progress", payload: { workspaceId: "ws-feature-a", status: "running", detail: { type: "worktree_setup", worktreePath: "/tmp/project/.paseo/worktrees/feature-a", branchName: "feature-a", log: "phase-one\n", commands: [ { index: 1, command: "npm install", cwd: "/tmp/project/.paseo/worktrees/feature-a", log: "phase-one\n", status: "running", exitCode: null, }, ], }, error: null, }, }), ); expect(events).toContainEqual({ type: "workspace_setup_progress", workspaceId: "ws-feature-a", payload: { workspaceId: "ws-feature-a", status: "running", detail: { type: "worktree_setup", worktreePath: "/tmp/project/.paseo/worktrees/feature-a", branchName: "feature-a", log: "phase-one\n", commands: [ { index: 1, command: "npm install", cwd: "/tmp/project/.paseo/worktrees/feature-a", log: "phase-one\n", status: "running", exitCode: null, }, ], }, error: null, }, }); }); test("sends create_agent_request with string workspace ids", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createAgent({ provider: "codex", cwd: "/tmp/project/.paseo/worktrees/feature-a", workspaceId: "ws-feature-a", title: "Compat agent", modeId: "default", }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toEqual( expect.objectContaining({ type: "create_agent_request", workspaceId: "ws-feature-a", }), ); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "agent_create_failed", requestId: request.requestId, error: "compat test sentinel", }, }), ); await expect(createPromise).rejects.toThrow("compat test sentinel"); }); test("sends worktree target and autoArchive in create_agent_request", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createAgent({ provider: "codex", cwd: "/tmp/project", worktree: { mode: "branch-off", newBranch: "agent-lifecycle-dispatch", base: "main", }, autoArchive: true, }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toEqual( expect.objectContaining({ type: "create_agent_request", worktree: { mode: "branch-off", newBranch: "agent-lifecycle-dispatch", base: "main", }, autoArchive: true, }), ); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "agent_create_failed", requestId: request.requestId, error: "worktree auto archive sentinel", }, }), ); await expect(createPromise).rejects.toThrow("worktree auto archive sentinel"); }); test("sends structured attachments with create_agent_request", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createAgent({ provider: "codex", cwd: "/tmp/project", initialPrompt: "Review this PR", attachments: [ { type: "github_pr", mimeType: "application/github-pr", number: 123, title: "Fix race in worktree setup", url: "https://github.com/getpaseo/paseo/pull/123", baseRefName: "main", headRefName: "fix/worktree-race", }, ], }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.attachments).toEqual([ { type: "github_pr", mimeType: "application/github-pr", number: 123, title: "Fix race in worktree setup", url: "https://github.com/getpaseo/paseo/pull/123", baseRefName: "main", headRefName: "fix/worktree-race", }, ]); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "agent_create_failed", requestId: request.requestId, error: "attachment test sentinel", }, }), ); await expect(createPromise).rejects.toThrow("attachment test sentinel"); }); test("sends worktree base-ref fields in create_agent_request git options", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createAgent({ provider: "codex", cwd: "/tmp/project", requestId: "req-agent-ref", git: { createWorktree: true, worktreeSlug: "review-pr-123", refName: "feature/worktree-base-ref", action: "checkout", githubPrNumber: 123, }, }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.git).toEqual({ createWorktree: true, worktreeSlug: "review-pr-123", refName: "feature/worktree-base-ref", action: "checkout", githubPrNumber: 123, }); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "agent_create_failed", requestId: request.requestId, error: "git ref fields sentinel", }, }), ); await expect(createPromise).rejects.toThrow("git ref fields sentinel"); }); test("omitting create_agent_request worktree base-ref fields preserves legacy wire shape", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createAgent({ provider: "codex", cwd: "/tmp/project", requestId: "req-agent-legacy", git: { createWorktree: true, worktreeSlug: "feature-a", }, }); expect(assertStr(mock.sent[0])).toBe( JSON.stringify({ type: "session", message: { type: "create_agent_request", config: { provider: "codex", cwd: "/tmp/project", }, git: { createWorktree: true, worktreeSlug: "feature-a", }, labels: {}, requestId: "req-agent-legacy", }, }), ); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "agent_create_failed", requestId: "req-agent-legacy", error: "legacy git shape sentinel", }, }), ); await expect(createPromise).rejects.toThrow("legacy git shape sentinel"); }); test("sends structured first-agent context attachments with create_paseo_worktree_request", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createPaseoWorktree({ cwd: "/tmp/project", worktreeSlug: "review-pr-123", firstAgentContext: { attachments: [ { type: "github_pr", mimeType: "application/github-pr", number: 123, title: "Fix race in worktree setup", url: "https://github.com/getpaseo/paseo/pull/123", }, ], }, }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); const firstAgentContext = z .object({ attachments: z.array(z.unknown()) }) .parse(request.firstAgentContext); expect(firstAgentContext.attachments).toEqual([ { type: "github_pr", mimeType: "application/github-pr", number: 123, title: "Fix race in worktree setup", url: "https://github.com/getpaseo/paseo/pull/123", }, ]); mock.triggerMessage( wrapSessionMessage({ type: "create_paseo_worktree_response", payload: { requestId: request.requestId, workspace: null, error: "worktree attachment sentinel", setupTerminalId: null, }, }), ); await expect(createPromise).resolves.toEqual({ requestId: request.requestId, workspace: null, error: "worktree attachment sentinel", setupTerminalId: null, }); }); test("sends worktree base-ref fields in create_paseo_worktree_request", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createPaseoWorktree( { cwd: "/tmp/project", projectId: "remote:github.com/acme/project", worktreeSlug: "review-pr-123", refName: "feature/worktree-base-ref", action: "checkout", githubPrNumber: 123, }, "req-worktree-ref", ); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toEqual({ type: "create_paseo_worktree_request", cwd: "/tmp/project", projectId: "remote:github.com/acme/project", worktreeSlug: "review-pr-123", refName: "feature/worktree-base-ref", action: "checkout", githubPrNumber: 123, requestId: "req-worktree-ref", }); mock.triggerMessage( wrapSessionMessage({ type: "create_paseo_worktree_response", payload: { requestId: request.requestId, workspace: null, error: "worktree ref fields sentinel", setupTerminalId: null, }, }), ); await expect(createPromise).resolves.toEqual({ requestId: request.requestId, workspace: null, error: "worktree ref fields sentinel", setupTerminalId: null, }); }); test("omitting create_paseo_worktree_request worktree base-ref fields preserves legacy wire shape", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const createPromise = client.createPaseoWorktree( { cwd: "/tmp/project", worktreeSlug: "feature-a", }, "req-worktree-legacy", ); expect(assertStr(mock.sent[0])).toBe( JSON.stringify({ type: "session", message: { type: "create_paseo_worktree_request", cwd: "/tmp/project", worktreeSlug: "feature-a", requestId: "req-worktree-legacy", }, }), ); mock.triggerMessage( wrapSessionMessage({ type: "create_paseo_worktree_response", payload: { requestId: "req-worktree-legacy", workspace: null, error: "legacy worktree shape sentinel", setupTerminalId: null, }, }), ); await expect(createPromise).resolves.toEqual({ requestId: "req-worktree-legacy", workspace: null, error: "legacy worktree shape sentinel", setupTerminalId: null, }); }); test("sends explicit shutdown_server_request via shutdownServer", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const lifecycleClient = client as unknown as { shutdownServer: (requestId?: string) => Promise<{ status: "shutdown_requested"; clientId: string; requestId: string; }>; }; expect(typeof lifecycleClient.shutdownServer).toBe("function"); const promise = lifecycleClient.shutdownServer("req-shutdown-1"); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toEqual({ type: "shutdown_server_request", requestId: "req-shutdown-1", }); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "shutdown_requested", clientId: "clsk_unit_test", requestId: "req-shutdown-1", }, }), ); await expect(promise).resolves.toEqual({ status: "shutdown_requested", clientId: "clsk_unit_test", requestId: "req-shutdown-1", }); }); test("restartServer remains restart-only and sends restart_server_request", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.restartServer("settings_update", "req-restart-1"); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toEqual({ type: "restart_server_request", reason: "settings_update", requestId: "req-restart-1", }); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "restart_requested", clientId: "clsk_unit_test", reason: "settings_update", requestId: "req-restart-1", }, }), ); await expect(promise).resolves.toEqual({ status: "restart_requested", clientId: "clsk_unit_test", reason: "settings_update", requestId: "req-restart-1", }); }); test("transitions out of connecting when connect timeout elapses", async () => { vi.useFakeTimers(); try { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, connectTimeoutMs: 100, transportFactory: () => mock.transport, }); clients.push(client); const pendingConnect = client.connect().then( () => ({ ok: true as const }), (error) => ({ ok: false as const, error }), ); expect(client.getConnectionState().status).toBe("connecting"); await vi.advanceTimersByTimeAsync(120); const result = await pendingConnect; expect(result.ok).toBe(false); if (!result.ok) { expect(result.error).toBeInstanceOf(Error); if (result.error instanceof Error) { expect(result.error.message).toContain("Connection timed out"); } } expect(client.getConnectionState().status).toBe("disconnected"); } finally { vi.useRealTimers(); } }); test("reconnects after relay close with replaced-by-new-connection reason", async () => { vi.useFakeTimers(); try { const logger = createMockLogger(); const first = createMockTransport(); const second = createMockTransport(); const transports = [first, second]; let transportIndex = 0; const client = new DaemonClient({ url: "ws://relay.test/ws?role=client&serverId=srv_test&v=2", clientId: "clsk_test", logger, reconnect: { enabled: true, baseDelayMs: 5, maxDelayMs: 5, }, transportFactory: () => { const next = transports[Math.min(transportIndex, transports.length - 1)]; transportIndex += 1; return next.transport; }, }); clients.push(client); const connectPromise = client.connect(); first.triggerOpen(); await connectPromise; expect(client.getConnectionState().status).toBe("connected"); first.triggerClose({ code: 1008, reason: "Replaced by new connection" }); expect(client.getConnectionState().status).toBe("disconnected"); await vi.advanceTimersByTimeAsync(10); expect(client.getConnectionState().status).toBe("connecting"); second.triggerOpen(); expect(client.getConnectionState().status).toBe("connected"); } finally { vi.useRealTimers(); } }); test("requires non-empty clientId", () => { expect(() => { const _client = new DaemonClient({ url: "ws://relay.test/ws?role=client&serverId=srv_test&v=2", clientId: "", reconnect: { enabled: false }, }); void _client; }).toThrow("Daemon client requires a non-empty clientId"); }); test("requires non-empty clientId for direct connections", () => { expect(() => { const _client = new DaemonClient({ url: "ws://127.0.0.1:6767/ws", clientId: " ", reconnect: { enabled: false }, }); void _client; }).toThrow("Daemon client requires a non-empty clientId"); }); test("logs configured runtime generation in connection transition events", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, runtimeGeneration: 7, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const transitionPayloads = logger.debug.mock.calls.filter( ([, message]) => message === "DaemonClientTransition", ); expect(transitionPayloads.length).toBeGreaterThan(0); for (const [payload] of transitionPayloads) { expect( z.object({ generation: z.number().nullable().optional() }).parse(payload).generation, ).toBe(7); } }); test("subscribes to checkout diff updates via RPC handshake", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.subscribeCheckoutDiff( "/tmp/project", { mode: "uncommitted" }, { subscriptionId: "checkout-sub-1" }, ); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("subscribe_checkout_diff_request"); expect(request.subscriptionId).toBe("checkout-sub-1"); expect(request.cwd).toBe("/tmp/project"); expect(request.compare).toEqual({ mode: "uncommitted" }); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "subscribe_checkout_diff_response", payload: { subscriptionId: "checkout-sub-1", cwd: "/tmp/project", files: [], error: null, requestId: request.requestId, }, }, }), ); await expect(promise).resolves.toEqual({ subscriptionId: "checkout-sub-1", cwd: "/tmp/project", files: [], error: null, requestId: request.requestId, }); }); test("getCheckoutDiff uses one-shot subscription protocol", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.getCheckoutDiff("/tmp/project", { mode: "base", baseRef: "main" }); expect(mock.sent).toHaveLength(1); const subscribeRequest = parseSentFrame(mock.sent[0]); expect(subscribeRequest.type).toBe("subscribe_checkout_diff_request"); expect(subscribeRequest.cwd).toBe("/tmp/project"); expect(subscribeRequest.compare).toEqual({ mode: "base", baseRef: "main", }); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "subscribe_checkout_diff_response", payload: { subscriptionId: subscribeRequest.subscriptionId, cwd: "/tmp/project", files: [], error: null, requestId: subscribeRequest.requestId, }, }, }), ); await expect(promise).resolves.toEqual({ cwd: "/tmp/project", files: [], error: null, requestId: subscribeRequest.requestId, }); expect(mock.sent).toHaveLength(2); const unsubscribeRequest = parseSentFrame(mock.sent[1]); expect(unsubscribeRequest.type).toBe("unsubscribe_checkout_diff_request"); expect(unsubscribeRequest.subscriptionId).toBe(subscribeRequest.subscriptionId); }); test("requests branch suggestions via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.getBranchSuggestions( { cwd: "/tmp/project", query: "mai", limit: 5 }, "req-branches", ); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("branch_suggestions_request"); expect(request.cwd).toBe("/tmp/project"); expect(request.query).toBe("mai"); expect(request.limit).toBe(5); expect(request.requestId).toBe("req-branches"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "branch_suggestions_response", payload: { branches: ["main"], error: null, requestId: "req-branches", }, }, }), ); await expect(promise).resolves.toEqual({ branches: ["main"], error: null, requestId: "req-branches", }); }); test("reads project config via correlated RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.readProjectConfig("/repo/app", "read-project-config-1"); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toEqual({ type: "read_project_config_request", requestId: "read-project-config-1", repoRoot: "/repo/app", }); mock.triggerMessage( wrapSessionMessage({ type: "read_project_config_response", payload: { requestId: "read-project-config-1", repoRoot: "/repo/app", ok: true, config: { worktree: { setup: "npm install" } }, revision: { mtimeMs: 10, size: 20 }, }, }), ); await expect(promise).resolves.toEqual({ requestId: "read-project-config-1", repoRoot: "/repo/app", ok: true, config: { worktree: { setup: "npm install" } }, revision: { mtimeMs: 10, size: 20 }, }); }); test("writes project config via correlated RPC and returns inline failures", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.writeProjectConfig({ requestId: "write-project-config-1", repoRoot: "/repo/app", config: { worktree: { setup: ["npm install"] } }, expectedRevision: { mtimeMs: 10, size: 20 }, }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toEqual({ type: "write_project_config_request", requestId: "write-project-config-1", repoRoot: "/repo/app", config: { worktree: { setup: ["npm install"] } }, expectedRevision: { mtimeMs: 10, size: 20 }, }); mock.triggerMessage( wrapSessionMessage({ type: "write_project_config_response", payload: { requestId: "write-project-config-1", repoRoot: "/repo/app", ok: false, error: { code: "stale_project_config", currentRevision: { mtimeMs: 11, size: 21 }, }, }, }), ); await expect(promise).resolves.toEqual({ requestId: "write-project-config-1", repoRoot: "/repo/app", ok: false, error: { code: "stale_project_config", currentRevision: { mtimeMs: 11, size: 21 }, }, }); }); test("requests directory suggestions via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.getDirectorySuggestions( { query: "proj", limit: 10, cwd: "/tmp/project", includeFiles: true, includeDirectories: true, matchMode: "suffix", }, "req-directories", ); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("directory_suggestions_request"); expect(request.query).toBe("proj"); expect(request.cwd).toBe("/tmp/project"); expect(request.includeFiles).toBe(true); expect(request.includeDirectories).toBe(true); expect(request.matchMode).toBe("suffix"); expect(request.limit).toBe(10); expect(request.requestId).toBe("req-directories"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "directory_suggestions_response", payload: { directories: ["/Users/test/projects/paseo"], entries: [{ path: "README.md", kind: "file" }], error: null, requestId: "req-directories", }, }, }), ); await expect(promise).resolves.toEqual({ directories: ["/Users/test/projects/paseo"], entries: [{ path: "README.md", kind: "file" }], error: null, requestId: "req-directories", }); }); test("requests checkout merge from base via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.checkoutMergeFromBase( "/tmp/project", { baseRef: "main", requireCleanTarget: true }, "req-merge-from-base", ); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("checkout_merge_from_base_request"); expect(request.cwd).toBe("/tmp/project"); expect(request.baseRef).toBe("main"); expect(request.requireCleanTarget).toBe(true); expect(request.requestId).toBe("req-merge-from-base"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "checkout_merge_from_base_response", payload: { cwd: "/tmp/project", requestId: "req-merge-from-base", success: true, error: null, }, }, }), ); await expect(promise).resolves.toEqual({ cwd: "/tmp/project", requestId: "req-merge-from-base", success: true, error: null, }); }); test("requests GitHub auto-merge enable via namespaced RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.checkoutGithubSetAutoMerge( "/tmp/project", { enabled: true, method: "squash" }, "req-enable-auto-merge", ); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("checkout.github.set_auto_merge.request"); expect(request.cwd).toBe("/tmp/project"); expect(request.enabled).toBe(true); expect(request.mergeMethod).toBe("squash"); expect(request.requestId).toBe("req-enable-auto-merge"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "checkout.github.set_auto_merge.response", payload: { cwd: "/tmp/project", enabled: true, requestId: "req-enable-auto-merge", success: true, error: null, }, }, }), ); await expect(promise).resolves.toEqual({ cwd: "/tmp/project", enabled: true, requestId: "req-enable-auto-merge", success: true, error: null, }); }); test("requests GitHub auto-merge disable via namespaced RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.checkoutGithubSetAutoMerge( "/tmp/project", { enabled: false }, "req-disable-auto-merge", ); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("checkout.github.set_auto_merge.request"); expect(request.cwd).toBe("/tmp/project"); expect(request.enabled).toBe(false); expect(request.mergeMethod).toBeUndefined(); expect(request.requestId).toBe("req-disable-auto-merge"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "checkout.github.set_auto_merge.response", payload: { cwd: "/tmp/project", enabled: false, requestId: "req-disable-auto-merge", success: true, error: null, }, }, }), ); await expect(promise).resolves.toEqual({ cwd: "/tmp/project", enabled: false, requestId: "req-disable-auto-merge", success: true, error: null, }); }); test("requests checkout pull via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.checkoutPull("/tmp/project", "req-pull"); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("checkout_pull_request"); expect(request.cwd).toBe("/tmp/project"); expect(request.requestId).toBe("req-pull"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "checkout_pull_response", payload: { cwd: "/tmp/project", requestId: "req-pull", success: true, error: null, }, }, }), ); await expect(promise).resolves.toEqual({ cwd: "/tmp/project", requestId: "req-pull", success: true, error: null, }); }); test("renames a branch via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.renameBranch({ cwd: "/tmp/project", branch: "feature/new-name", requestId: "req-rename-branch", }); expect(mock.sent).toHaveLength(1); const request = JSON.parse(mock.sent[0]) as { type: "session"; message: { type: "checkout.rename_branch.request"; cwd: string; branch: string; requestId: string; }; }; expect(request.message.type).toBe("checkout.rename_branch.request"); expect(request.message.cwd).toBe("/tmp/project"); expect(request.message.branch).toBe("feature/new-name"); expect(request.message.requestId).toBe("req-rename-branch"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "checkout.rename_branch.response", payload: { requestId: "req-rename-branch", success: true, cwd: "/tmp/project", currentBranch: "feature/new-name", error: null, }, }, }), ); await expect(promise).resolves.toEqual({ requestId: "req-rename-branch", success: true, cwd: "/tmp/project", currentBranch: "feature/new-name", error: null, }); }); test("returns renameBranch business failures", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.renameBranch({ cwd: "/tmp/project", branch: "already-exists", requestId: "req-rename-branch-fail", }); expect(mock.sent).toHaveLength(1); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "checkout.rename_branch.response", payload: { requestId: "req-rename-branch-fail", success: false, cwd: "/tmp/project", currentBranch: null, error: { code: "NOT_ALLOWED", message: "Branch already exists" }, }, }, }), ); await expect(promise).resolves.toEqual({ requestId: "req-rename-branch-fail", success: false, cwd: "/tmp/project", currentBranch: null, error: { code: "NOT_ALLOWED", message: "Branch already exists" }, }); }); test("resubscribes checkout diff streams after reconnect", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const internal = client as unknown as { checkoutDiffSubscriptions: Map< string, { cwd: string; compare: { mode: "uncommitted" | "base"; baseRef?: string } } >; }; internal.checkoutDiffSubscriptions.set("checkout-sub-1", { cwd: "/tmp/project", compare: { mode: "base", baseRef: "main" }, }); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("subscribe_checkout_diff_request"); expect(request.subscriptionId).toBe("checkout-sub-1"); expect(request.cwd).toBe("/tmp/project"); expect(request.compare).toEqual({ mode: "base", baseRef: "main" }); expect(typeof request.requestId).toBe("string"); expect(z.string().parse(request.requestId).length).toBeGreaterThan(0); }); test("fetches agents via RPC with filters, sort, and pagination", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.fetchAgents({ filter: { labels: { surface: "workspace" } }, sort: [ { key: "status_priority", direction: "asc" }, { key: "created_at", direction: "desc" }, ], page: { limit: 25, cursor: "cursor-1" }, subscribe: { subscriptionId: "sub-1" }, }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("fetch_agents_request"); expect(request.sort).toEqual([ { key: "status_priority", direction: "asc" }, { key: "created_at", direction: "desc" }, ]); expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" }); expect(request.subscribe).toEqual({ subscriptionId: "sub-1" }); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "fetch_agents_response", payload: { requestId: request.requestId, subscriptionId: "sub-1", entries: [], pageInfo: { nextCursor: null, prevCursor: "cursor-1", hasMore: false, }, }, }, }), ); await expect(promise).resolves.toEqual({ requestId: request.requestId, subscriptionId: "sub-1", entries: [], pageInfo: { nextCursor: null, prevCursor: "cursor-1", hasMore: false, }, }); }); test("sends active-scoped fetch_agents_request", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.fetchAgents({ scope: "active", page: { limit: 50 }, }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request).toMatchObject({ type: "fetch_agents_request", scope: "active", }); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "fetch_agents_response", payload: { requestId: request.requestId, entries: [], pageInfo: { nextCursor: null, prevCursor: null, hasMore: false, }, }, }, }), ); await expect(promise).resolves.toMatchObject({ requestId: request.requestId, entries: [], }); }); test("fetches paginated agent history separately from active agents", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.fetchAgentHistory({ page: { limit: 25, cursor: "cursor-1" }, sort: [{ key: "updated_at", direction: "desc" }], }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("fetch_agent_history_request"); expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" }); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "fetch_agent_history_response", payload: { requestId: request.requestId, entries: [], pageInfo: { nextCursor: null, prevCursor: "cursor-1", hasMore: false, }, }, }, }), ); await expect(promise).resolves.toEqual({ requestId: request.requestId, entries: [], pageInfo: { nextCursor: null, prevCursor: "cursor-1", hasMore: false, }, }); }); test("fetches scoped recent provider sessions", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.fetchRecentProviderSessions({ cwd: "/tmp/repo", providers: ["my-claude"], since: "2026-04-30T00:00:00.000Z", limit: 25, }); expect(mock.sent).toHaveLength(1); const request = JSON.parse(String(mock.sent[0])) as { type: "session"; message: { type: "fetch_recent_provider_sessions_request"; requestId: string; cwd?: string; providers?: string[]; since?: string; limit?: number; }; }; expect(request.message).toMatchObject({ type: "fetch_recent_provider_sessions_request", cwd: "/tmp/repo", providers: ["my-claude"], since: "2026-04-30T00:00:00.000Z", limit: 25, }); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "fetch_recent_provider_sessions_response", payload: { requestId: request.message.requestId, entries: [ { providerId: "codex", providerLabel: "Codex", providerHandleId: "thread-1", cwd: "/tmp/repo", title: "Import me", firstPromptPreview: "first prompt", lastPromptPreview: "last prompt", lastActivityAt: "2026-04-30T12:34:56.000Z", }, ], }, }, }), ); await expect(promise).resolves.toEqual({ requestId: request.message.requestId, entries: [ { providerId: "codex", providerLabel: "Codex", providerHandleId: "thread-1", cwd: "/tmp/repo", title: "Import me", firstPromptPreview: "first prompt", lastPromptPreview: "last prompt", lastActivityAt: "2026-04-30T12:34:56.000Z", }, ], }); }); test("imports an agent by provider handle id", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.importAgent({ providerId: "custom-codex", providerHandleId: "thread-1", cwd: "/tmp/repo", }); expect(mock.sent).toHaveLength(1); const request = JSON.parse(String(mock.sent[0])) as { type: "session"; message: { type: "import_agent_request"; requestId: string; providerId?: string; providerHandleId?: string; sessionId?: string; cwd?: string; }; }; expect(request.message).toMatchObject({ type: "import_agent_request", providerId: "custom-codex", providerHandleId: "thread-1", cwd: "/tmp/repo", }); expect(request.message).not.toHaveProperty("sessionId"); mock.triggerMessage( wrapSessionMessage({ type: "status", payload: { status: "agent_resumed", requestId: request.message.requestId, agentId: "agent-1", timelineSize: 0, agent: { id: "agent-1", provider: "custom-codex", cwd: "/tmp/repo", model: null, features: [], thinkingOptionId: null, effectiveThinkingOptionId: null, createdAt: "2026-04-30T00:00:00.000Z", updatedAt: "2026-04-30T00:00:00.000Z", lastUserMessageAt: null, status: "idle", capabilities: { supportsStreaming: false, supportsSessionPersistence: false, supportsDynamicModes: false, supportsMcpServers: false, supportsReasoningStream: false, supportsToolInvocations: false, }, currentModeId: null, availableModes: [], pendingPermissions: [], persistence: { provider: "custom-codex", sessionId: "thread-1", nativeHandle: "thread-1", }, title: null, labels: {}, requiresAttention: false, attentionReason: null, }, }, }), ); await expect(promise).resolves.toMatchObject({ id: "agent-1", provider: "custom-codex", }); }); test("uses server-provided dictation finish timeout budget", async () => { vi.useFakeTimers(); const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const finishPromise = client.finishDictationStream("dict-1", 0); const finishError = finishPromise.then( () => null, (error) => error, ); expect(mock.sent).toHaveLength(1); mock.triggerMessage( wrapSessionMessage({ type: "dictation_stream_finish_accepted", payload: { dictationId: "dict-1", timeoutMs: 100, }, }), ); await vi.advanceTimersByTimeAsync(5_101); const error = await finishError; expect(error).toBeInstanceOf(Error); if (error instanceof Error) { expect(error.message).toContain("Timeout waiting for dictation finalization (5100ms)"); } vi.useRealTimers(); }); test("resolves dictation finish when final arrives after finish accepted", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const finishPromise = client.finishDictationStream("dict-2", 1); expect(mock.sent).toHaveLength(1); mock.triggerMessage( wrapSessionMessage({ type: "dictation_stream_finish_accepted", payload: { dictationId: "dict-2", timeoutMs: 1000, }, }), ); mock.triggerMessage( wrapSessionMessage({ type: "dictation_stream_final", payload: { dictationId: "dict-2", text: "hello", }, }), ); await expect(finishPromise).resolves.toEqual({ dictationId: "dict-2", text: "hello", }); }); test("cancels waiters when send fails (no leaked timeouts)", async () => { vi.useFakeTimers(); const logger = createMockLogger(); const mock = createMockTransport(); let sendCount = 0; const transportFactory = () => ({ ...mock.transport, send: () => { sendCount += 1; if (sendCount > 1) { throw new Error("boom"); } }, }); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.getCheckoutStatus("/tmp/project"); await expect(promise).rejects.toThrow("boom"); // Ensure we didn't leave a waiter behind that will reject later. const internal = client as unknown as { waiters: Set }; expect(internal.waiters.size).toBe(0); vi.runOnlyPendingTimers(); vi.useRealTimers(); }); test("lists available providers via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.listAvailableProviders(); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("list_available_providers_request"); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "list_available_providers_response", payload: { providers: [ { provider: "claude", available: true, error: null }, { provider: "codex", available: false, error: "Missing binary" }, ], error: null, fetchedAt: "2026-02-12T00:00:00.000Z", requestId: request.requestId, }, }, }), ); await expect(promise).resolves.toEqual({ providers: [ { provider: "claude", available: true, error: null }, { provider: "codex", available: false, error: "Missing binary" }, ], error: null, fetchedAt: "2026-02-12T00:00:00.000Z", requestId: request.requestId, }); }); test("lists commands with draft config via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.listCommands("__new_agent__", { draftConfig: { provider: "codex", cwd: "/tmp/project", modeId: "bypassPermissions", model: "gpt-5", thinkingOptionId: "off", }, }); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("list_commands_request"); expect(request.agentId).toBe("__new_agent__"); expect(request.draftConfig).toEqual({ provider: "codex", cwd: "/tmp/project", modeId: "bypassPermissions", model: "gpt-5", thinkingOptionId: "off", }); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "list_commands_response", payload: { agentId: "__new_agent__", commands: [{ name: "help", description: "Show help", argumentHint: "" }], error: null, requestId: request.requestId, }, }, }), ); await expect(promise).resolves.toEqual({ agentId: "__new_agent__", commands: [{ name: "help", description: "Show help", argumentHint: "" }], error: null, requestId: request.requestId, }); }); test("lists commands with legacy requestId signature via RPC", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const promise = client.listCommands("agent-1", "req-legacy"); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("list_commands_request"); expect(request.agentId).toBe("agent-1"); expect(request.requestId).toBe("req-legacy"); expect(request.draftConfig).toBeUndefined(); mock.triggerMessage( JSON.stringify({ type: "session", message: { type: "list_commands_response", payload: { agentId: "agent-1", commands: [], error: null, requestId: "req-legacy", }, }, }), ); await expect(promise).resolves.toEqual({ agentId: "agent-1", commands: [], error: null, requestId: "req-legacy", }); }); test("emits output events for the active terminal stream", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const seen: string[] = []; const unsubscribe = client.onTerminalStreamEvent((event) => { if (event.type !== "output") { return; } seen.push(new TextDecoder().decode(event.data)); }); const subscribePromise = client.subscribeTerminal("term-1", "sub-1"); mock.triggerMessage( wrapSessionMessage({ type: "subscribe_terminal_response", payload: { terminalId: "term-1", slot: 11, error: null, requestId: "sub-1", }, }), ); await subscribePromise; mock.triggerMessage( encodeTerminalStreamFrame({ opcode: TerminalStreamOpcode.Output, slot: 11, payload: new TextEncoder().encode("hello"), }), ); expect(seen).toEqual(["hello"]); expect(mock.sent).toHaveLength(1); unsubscribe(); }); test("emits snapshot events for the subscribed terminal stream", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const snapshots: unknown[] = []; client.onTerminalStreamEvent((event) => { if (event.type !== "snapshot") { return; } snapshots.push(event.state); }); const subscribePromise = client.subscribeTerminal("term-1", "sub-2"); mock.triggerMessage( wrapSessionMessage({ type: "subscribe_terminal_response", payload: { terminalId: "term-1", slot: 12, error: null, requestId: "sub-2", }, }), ); await subscribePromise; const state = { rows: 1, cols: 5, grid: [[{ char: "h" }, { char: "e" }, { char: "l" }, { char: "l" }, { char: "o" }]], scrollback: [], cursor: { row: 0, col: 5 }, }; mock.triggerMessage( encodeTerminalStreamFrame({ opcode: TerminalStreamOpcode.Snapshot, slot: 12, payload: encodeTerminalSnapshotPayload(state), }), ); expect(snapshots).toEqual([state]); }); test("sends input and resize frames for the subscribed terminal slot", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const subscribePromise = client.subscribeTerminal("term-1", "sub-3"); mock.triggerMessage( wrapSessionMessage({ type: "subscribe_terminal_response", payload: { terminalId: "term-1", slot: 13, error: null, requestId: "sub-3", }, }), ); await subscribePromise; mock.sent.length = 0; client.sendTerminalInput("term-1", { type: "input", data: "echo hello\r", }); client.sendTerminalInput("term-1", { type: "resize", rows: 24, cols: 80, }); const inputFrame = decodeTerminalStreamFrame(asUint8Array(mock.sent[0])!); const resizeFrame = decodeTerminalStreamFrame(asUint8Array(mock.sent[1])!); expect(inputFrame?.opcode).toBe(TerminalStreamOpcode.Input); expect(inputFrame?.slot).toBe(13); expect(new TextDecoder().decode(inputFrame?.payload ?? new Uint8Array())).toBe("echo hello\r"); expect(resizeFrame?.opcode).toBe(TerminalStreamOpcode.Resize); expect(resizeFrame?.slot).toBe(13); expect(decodeTerminalResizePayload(resizeFrame?.payload ?? new Uint8Array())).toEqual({ rows: 24, cols: 80, }); }); test("routes concurrent terminal stream frames by slot", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const seen: string[] = []; client.onTerminalStreamEvent((event) => { if (event.type !== "output") { return; } seen.push(`${event.terminalId}:${new TextDecoder().decode(event.data)}`); }); const subscribeFirstPromise = client.subscribeTerminal("term-1", "sub-multi-1"); mock.triggerMessage( wrapSessionMessage({ type: "subscribe_terminal_response", payload: { terminalId: "term-1", slot: 21, error: null, requestId: "sub-multi-1", }, }), ); await subscribeFirstPromise; const subscribeSecondPromise = client.subscribeTerminal("term-2", "sub-multi-2"); mock.triggerMessage( wrapSessionMessage({ type: "subscribe_terminal_response", payload: { terminalId: "term-2", slot: 22, error: null, requestId: "sub-multi-2", }, }), ); await subscribeSecondPromise; mock.sent.length = 0; mock.triggerMessage( encodeTerminalStreamFrame({ opcode: TerminalStreamOpcode.Output, slot: 22, payload: new TextEncoder().encode("beta"), }), ); mock.triggerMessage( encodeTerminalStreamFrame({ opcode: TerminalStreamOpcode.Output, slot: 21, payload: new TextEncoder().encode("alpha"), }), ); client.sendTerminalInput("term-2", { type: "input", data: "echo beta\r", }); client.sendTerminalInput("term-1", { type: "resize", rows: 10, cols: 20, }); const inputFrame = decodeTerminalStreamFrame(asUint8Array(mock.sent[0])!); const resizeFrame = decodeTerminalStreamFrame(asUint8Array(mock.sent[1])!); expect(seen).toEqual(["term-2:beta", "term-1:alpha"]); expect(inputFrame?.opcode).toBe(TerminalStreamOpcode.Input); expect(inputFrame?.slot).toBe(22); expect(resizeFrame?.opcode).toBe(TerminalStreamOpcode.Resize); expect(resizeFrame?.slot).toBe(21); }); test("ignores terminal stream frames after terminal_stream_exit", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const seen: string[] = []; const unsubscribe = client.onTerminalStreamEvent((event) => { if (event.type !== "output") { return; } seen.push(new TextDecoder().decode(event.data)); }); const subscribePromise = client.subscribeTerminal("term-1", "sub-4"); mock.triggerMessage( wrapSessionMessage({ type: "subscribe_terminal_response", payload: { terminalId: "term-1", slot: 14, error: null, requestId: "sub-4", }, }), ); await subscribePromise; mock.triggerMessage( encodeTerminalStreamFrame({ opcode: TerminalStreamOpcode.Output, slot: 14, payload: new TextEncoder().encode("before-exit"), }), ); expect(seen).toEqual(["before-exit"]); mock.triggerMessage( wrapSessionMessage({ type: "terminal_stream_exit", payload: { terminalId: "term-1", }, }), ); mock.triggerMessage( encodeTerminalStreamFrame({ opcode: TerminalStreamOpcode.Output, slot: 14, payload: new TextEncoder().encode("after-exit"), }), ); expect(seen).toEqual(["before-exit"]); unsubscribe(); }); test("parses canonical agent_stream tool_call payloads without crashing", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const received: unknown[] = []; const unsubscribe = client.on("agent_stream", (msg) => { received.push(msg); }); mock.triggerMessage( wrapSessionMessage({ type: "agent_stream", payload: { agentId: "agent_cli", timestamp: "2026-02-08T20:20:00.000Z", event: { type: "timeline", provider: "codex", item: { type: "tool_call", callId: "call_cli_stream", name: "shell", status: "running", detail: { type: "shell", command: "pwd", }, error: null, }, }, }, }), ); unsubscribe(); expect(received).toHaveLength(1); expect(received[0]).toMatchObject({ payload: { event: { item: { status: "running", error: null, detail: { type: "shell" }, }, }, }, }); expect(logger.warn).not.toHaveBeenCalled(); }); test("drops legacy agent_stream tool_call payloads and logs validation warning", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const received: unknown[] = []; const unsubscribe = client.on("agent_stream", (msg) => { received.push(msg); }); mock.triggerMessage( wrapSessionMessage({ type: "agent_stream", payload: { agentId: "agent_cli", timestamp: "2026-02-08T20:20:00.000Z", event: { type: "timeline", provider: "codex", item: { type: "tool_call", callId: "call_cli_stream_legacy", name: "shell", status: "inProgress", detail: { type: "unknown", input: { command: "pwd" }, output: null, }, }, }, }, }), ); unsubscribe(); expect(received).toHaveLength(0); expect(logger.warn).toHaveBeenCalled(); }); test("parses canonical fetch_agent_timeline_response payloads without crashing", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const received: unknown[] = []; const unsubscribe = client.on("fetch_agent_timeline_response", (msg) => { received.push(msg); }); mock.triggerMessage( wrapSessionMessage({ type: "fetch_agent_timeline_response", payload: { requestId: "req-1", agentId: "agent_cli", agent: null, direction: "tail", projection: "projected", epoch: "epoch-1", reset: false, staleCursor: false, gap: false, window: { minSeq: 1, maxSeq: 1, nextSeq: 2 }, startCursor: { epoch: "epoch-1", seq: 1 }, endCursor: { epoch: "epoch-1", seq: 1 }, hasOlder: false, hasNewer: false, entries: [ { timestamp: "2026-02-08T20:20:00.000Z", provider: "codex", seqStart: 1, seqEnd: 1, sourceSeqRanges: [{ startSeq: 1, endSeq: 1 }], collapsed: [], item: { type: "tool_call", callId: "call_cli_snapshot", name: "shell", status: "running", detail: { type: "shell", command: "pwd", }, error: null, }, }, ], error: null, }, }), ); unsubscribe(); expect(received).toHaveLength(1); expect(received[0]).toMatchObject({ payload: { entries: [ { item: { type: "tool_call", status: "running", error: null, detail: { type: "shell" }, }, }, ], }, }); expect(logger.warn).not.toHaveBeenCalled(); }); test("drops invalid fetch_agent_timeline_response tool_call payloads and logs validation warning", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const received: unknown[] = []; const unsubscribe = client.on("fetch_agent_timeline_response", (msg) => { received.push(msg); }); mock.triggerMessage( wrapSessionMessage({ type: "fetch_agent_timeline_response", payload: { requestId: "req-invalid", agentId: "agent_cli", agent: null, direction: "tail", projection: "projected", epoch: "epoch-1", reset: false, staleCursor: false, gap: false, window: { minSeq: 1, maxSeq: 1, nextSeq: 2 }, startCursor: { epoch: "epoch-1", seq: 1 }, endCursor: { epoch: "epoch-1", seq: 1 }, hasOlder: false, hasNewer: false, entries: [ { timestamp: "2026-02-08T20:20:00.000Z", provider: "codex", seqStart: 1, seqEnd: 1, sourceSeqRanges: [{ startSeq: 1, endSeq: 1 }], collapsed: [], item: { type: "tool_call", callId: "call_cli_invalid", name: "shell", status: "inProgress", detail: { type: "unknown", input: { command: "pwd" }, output: null, }, }, }, ], error: null, }, }), ); unsubscribe(); expect(received).toHaveLength(0); expect(logger.warn).toHaveBeenCalled(); }); test("sends subscribe/unsubscribe terminals messages", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; client.subscribeTerminals({ cwd: "/tmp/project" }); client.unsubscribeTerminals({ cwd: "/tmp/project" }); expect(mock.sent).toHaveLength(2); expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "subscribe_terminals_request", cwd: "/tmp/project", }, }); expect(JSON.parse(assertStr(mock.sent[1]))).toEqual({ type: "session", message: { type: "unsubscribe_terminals_request", cwd: "/tmp/project", }, }); }); test("dispatches terminals_changed events to typed listeners", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const received: Array<{ cwd: string; names: string[] }> = []; const unsubscribe = client.on("terminals_changed", (message) => { received.push({ cwd: message.payload.cwd, names: message.payload.terminals.map((terminal) => terminal.name), }); }); mock.triggerMessage( wrapSessionMessage({ type: "terminals_changed", payload: { cwd: "/tmp/project", terminals: [ { id: "term-1", name: "Dev Server", }, ], }, }), ); unsubscribe(); expect(received).toEqual([ { cwd: "/tmp/project", names: ["Dev Server"], }, ]); }); test("sends close_items_request and resolves close_items_response", async () => { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const responsePromise = client.closeItems( { agentIds: ["agent-1"], terminalIds: ["term-1"], }, "req-close-items", ); expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({ type: "session", message: { type: "close_items_request", agentIds: ["agent-1"], terminalIds: ["term-1"], requestId: "req-close-items", }, }); mock.triggerMessage( wrapSessionMessage({ type: "close_items_response", payload: { agents: [{ agentId: "agent-1", archivedAt: "2026-04-01T00:00:00.000Z" }], terminals: [{ terminalId: "term-1", success: true }], requestId: "req-close-items", }, }), ); await expect(responsePromise).resolves.toEqual({ agents: [{ agentId: "agent-1", archivedAt: "2026-04-01T00:00:00.000Z" }], terminals: [{ terminalId: "term-1", success: true }], requestId: "req-close-items", }); }); test("waitForFinish with timeout=0 omits timeoutMs and has no client deadline", async () => { vi.useFakeTimers(); try { const logger = createMockLogger(); const mock = createMockTransport(); const client = new DaemonClient({ url: "ws://test", clientId: "clsk_unit_test", logger, reconnect: { enabled: false }, transportFactory: () => mock.transport, }); clients.push(client); const connectPromise = client.connect(); mock.triggerOpen(); await connectPromise; const waitPromise = client.waitForFinish("agent-wait-zero-timeout", 0); expect(mock.sent).toHaveLength(1); const request = parseSentFrame(mock.sent[0]); expect(request.type).toBe("wait_for_finish_request"); expect(request.agentId).toBe("agent-wait-zero-timeout"); expect(request).not.toHaveProperty("timeoutMs"); const settled = vi.fn(); void waitPromise.then( () => settled("resolved"), () => settled("rejected"), ); await vi.advanceTimersByTimeAsync(5 * 60 * 1000); expect(settled).not.toHaveBeenCalled(); mock.triggerMessage( wrapSessionMessage({ type: "wait_for_finish_response", payload: { requestId: request.requestId, status: "idle", final: null, error: null, lastMessage: null, }, }), ); await expect(waitPromise).resolves.toEqual({ status: "idle", final: null, error: null, lastMessage: null, }); } finally { vi.useRealTimers(); } });