From e45de3d1c51962b8c58a73b3fcb7844720f0842e Mon Sep 17 00:00:00 2001 From: Pleasurecruise <3196812536@qq.com> Date: Thu, 27 Aug 2026 19:23:34 +0800 Subject: [PATCH] fix(components): use registered local Machine RPC bridge Route local file preview, file-index, and session control requests through Electron's registered machineRpc.send service after the preload migration removed window.api. Model: GPT-5 --- .../providers/workspace-machine-rpc-facade.ts | 10 ++---- .../workspace-machine-rpc-facade.test.ts | 33 ++++++++++--------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/components/src/providers/workspace-machine-rpc-facade.ts b/packages/components/src/providers/workspace-machine-rpc-facade.ts index 4c4bd34ab..0dff61dd4 100644 --- a/packages/components/src/providers/workspace-machine-rpc-facade.ts +++ b/packages/components/src/providers/workspace-machine-rpc-facade.ts @@ -94,14 +94,8 @@ const toCodeCollabTransportError = (error: unknown): CodeCollabV2Error => ({ retryable: true, }); -const getLocalMachineRpcSender = (): LocalMachineRpcSender | null => { - if (typeof window === 'undefined') return null; - const api = (window as unknown as { readonly api?: { readonly sendLocalMachineRpc?: unknown } }) - .api; - return typeof api?.sendLocalMachineRpc === 'function' - ? (api.sendLocalMachineRpc as LocalMachineRpcSender) - : null; -}; +const getLocalMachineRpcSender = (): LocalMachineRpcSender | undefined => + getIpcServices()?.machineRpc.send; export function createWorkspaceMachineRpcFacade(deps: WorkspaceMachineRpcFacadeDeps) { const { workspaceId, targetRouter, getMachineRpcClient } = deps; diff --git a/packages/components/tests/workspace-machine-rpc-facade.test.ts b/packages/components/tests/workspace-machine-rpc-facade.test.ts index d9d8c2aeb..795b695a6 100644 --- a/packages/components/tests/workspace-machine-rpc-facade.test.ts +++ b/packages/components/tests/workspace-machine-rpc-facade.test.ts @@ -13,7 +13,7 @@ afterEach(() => { describe('createWorkspaceMachineRpcFacade', () => { it('uses the local-only IPC preview method without creating a cloud client', async () => { - const sendLocalMachineRpc = vi.fn(async () => ({ + const invoke = vi.fn(async () => ({ ok: true as const, result: { status: 'ok' as const, @@ -30,7 +30,7 @@ describe('createWorkspaceMachineRpcFacade', () => { })); vi.stubGlobal('window', { __LODY_ELECTRON__: true, - api: { sendLocalMachineRpc }, + ipc: { invoke }, }); const getMachineRpcClient = vi.fn(); const facade = createWorkspaceMachineRpcFacade({ @@ -48,7 +48,8 @@ describe('createWorkspaceMachineRpcFacade', () => { path: '/Users/me/Documents/notes.md', }) ).resolves.toMatchObject({ status: 'ok', external: true, readonly: true }); - expect(sendLocalMachineRpc).toHaveBeenCalledWith( + expect(invoke).toHaveBeenCalledWith( + 'machineRpc.send', expect.objectContaining({ machineId: localMachineId, workspaceId, @@ -60,10 +61,10 @@ describe('createWorkspaceMachineRpcFacade', () => { }); it('does not fall back to a cloud preview while Electron local routing is unresolved', async () => { - const sendLocalMachineRpc = vi.fn(); + const invoke = vi.fn(); vi.stubGlobal('window', { __LODY_ELECTRON__: true, - api: { sendLocalMachineRpc }, + ipc: { invoke }, }); const getMachineRpcClient = vi.fn(); const facade = createWorkspaceMachineRpcFacade({ @@ -80,12 +81,12 @@ describe('createWorkspaceMachineRpcFacade', () => { await expect( facade.requestFilePreview(localMachineId, { sessionId, path: '/tmp/local.txt' }) ).resolves.toMatchObject({ status: 'error', code: 'transient_io' }); - expect(sendLocalMachineRpc).not.toHaveBeenCalled(); + expect(invoke).not.toHaveBeenCalled(); expect(getMachineRpcClient).not.toHaveBeenCalled(); }); it('uses the local bridge for a file-index snapshot without creating a cloud client', async () => { - const sendLocalMachineRpc = vi.fn(async () => ({ + const invoke = vi.fn(async () => ({ ok: true as const, result: { status: 'ok' as const, @@ -96,7 +97,7 @@ describe('createWorkspaceMachineRpcFacade', () => { })); vi.stubGlobal('window', { __LODY_ELECTRON__: true, - api: { sendLocalMachineRpc }, + ipc: { invoke }, }); const getMachineRpcClient = vi.fn(); const facade = createWorkspaceMachineRpcFacade({ @@ -118,7 +119,8 @@ describe('createWorkspaceMachineRpcFacade', () => { status: 'ok', fileIndex: { 'src/local.ts': { kind: 'file' } }, }); - expect(sendLocalMachineRpc).toHaveBeenCalledWith( + expect(invoke).toHaveBeenCalledWith( + 'machineRpc.send', expect.objectContaining({ machineId: localMachineId, workspaceId, @@ -131,7 +133,7 @@ describe('createWorkspaceMachineRpcFacade', () => { }); it('uses the local bridge without creating a cloud client for the local machine', async () => { - const sendLocalMachineRpc = vi.fn(async () => ({ + const invoke = vi.fn(async () => ({ ok: true as const, result: { type: 'session/cancel_response' as const, @@ -141,7 +143,7 @@ describe('createWorkspaceMachineRpcFacade', () => { })); vi.stubGlobal('window', { __LODY_ELECTRON__: true, - api: { sendLocalMachineRpc }, + ipc: { invoke }, }); const getMachineRpcClient = vi.fn(); const facade = createWorkspaceMachineRpcFacade({ @@ -160,7 +162,8 @@ describe('createWorkspaceMachineRpcFacade', () => { success: true, } ); - expect(sendLocalMachineRpc).toHaveBeenCalledWith( + expect(invoke).toHaveBeenCalledWith( + 'machineRpc.send', expect.objectContaining({ machineId: localMachineId, workspaceId, @@ -171,10 +174,10 @@ describe('createWorkspaceMachineRpcFacade', () => { }); it('uses the cloud Machine RPC client for a remote machine', async () => { - const sendLocalMachineRpc = vi.fn(); + const invoke = vi.fn(); vi.stubGlobal('window', { __LODY_ELECTRON__: true, - api: { sendLocalMachineRpc }, + ipc: { invoke }, }); const requestSessionCancel = vi.fn(async () => ({ type: 'session/cancel_response' as const, @@ -204,6 +207,6 @@ describe('createWorkspaceMachineRpcFacade', () => { turnId: 'turn-1', timeoutMs: 2_000, }); - expect(sendLocalMachineRpc).not.toHaveBeenCalled(); + expect(invoke).not.toHaveBeenCalled(); }); });