diff --git a/packages/main/src/plugin/openshell-cli/openshell-gateway.spec.ts b/packages/main/src/plugin/openshell-cli/openshell-gateway.spec.ts index f1c037425a..e2771c3a7f 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-gateway.spec.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-gateway.spec.ts @@ -82,6 +82,7 @@ const openshellCli = { selectGateway: vi.fn(), checkEndpointStatus: vi.fn(), addGateway: vi.fn(), + removeGateway: vi.fn(), } as unknown as OpenshellCli; const directories = { @@ -105,6 +106,8 @@ beforeEach(() => { ] as unknown as CliToolInfo[]); vi.mocked(createWriteStream).mockReturnValue(gatewayLogStream); vi.mocked(exec.exec).mockResolvedValue({ command: '', stdout: '', stderr: '' }); + vi.mocked(openshellCli.removeGateway).mockResolvedValue(); + vi.mocked(openshellCli.listGateways).mockResolvedValue([]); gateway = new OpenshellGateway(cliToolRegistry, openshellCli, directories, exec, notificationRegistry); }); @@ -423,6 +426,84 @@ describe('start', () => { }); }); + test('skips re-registration when kaiden-local already exists with same endpoint', async () => { + vi.spyOn(console, 'log').mockImplementation(() => undefined); + const proc = createMockChildProcess(); + vi.mocked(spawn).mockReturnValue(proc); + vi.mocked(exec.exec).mockResolvedValue(mockExecResult('openshell-gateway 0.0.69')); + vi.mocked(openshellCli.checkEndpointStatus).mockResolvedValue(true); + vi.mocked(openshellCli.listGateways).mockResolvedValue([ + { name: 'kaiden-local', endpoint: 'http://127.0.0.1:17670' } as GatewayInfo, + ]); + + await gateway.start(); + + expect(openshellCli.removeGateway).not.toHaveBeenCalled(); + expect(openshellCli.addGateway).not.toHaveBeenCalled(); + }); + + test('removes stale gateway and re-registers when endpoint differs', async () => { + vi.spyOn(console, 'log').mockImplementation(() => undefined); + const proc = createMockChildProcess(); + vi.mocked(spawn).mockReturnValue(proc); + vi.mocked(exec.exec).mockResolvedValue(mockExecResult('openshell-gateway 0.0.69')); + vi.mocked(openshellCli.checkEndpointStatus).mockResolvedValue(true); + vi.mocked(openshellCli.listGateways).mockResolvedValue([ + { name: 'kaiden-local', endpoint: 'http://127.0.0.1:9999' } as GatewayInfo, + ]); + + await gateway.start(); + + expect(openshellCli.removeGateway).toHaveBeenCalledWith('kaiden-local'); + expect(openshellCli.addGateway).toHaveBeenCalledWith({ + endpoint: 'http://127.0.0.1:17670', + local: true, + name: 'kaiden-local', + }); + + const removeOrder = vi.mocked(openshellCli.removeGateway).mock.invocationCallOrder[0]!; + const addOrder = vi.mocked(openshellCli.addGateway).mock.invocationCallOrder[0]!; + expect(removeOrder).toBeLessThan(addOrder); + }); + + test('registers fresh when kaiden-local does not exist', async () => { + vi.spyOn(console, 'log').mockImplementation(() => undefined); + const proc = createMockChildProcess(); + vi.mocked(spawn).mockReturnValue(proc); + vi.mocked(exec.exec).mockResolvedValue(mockExecResult('openshell-gateway 0.0.69')); + vi.mocked(openshellCli.checkEndpointStatus).mockResolvedValue(true); + vi.mocked(openshellCli.listGateways).mockResolvedValue([]); + + await gateway.start(); + + expect(openshellCli.removeGateway).not.toHaveBeenCalled(); + expect(openshellCli.addGateway).toHaveBeenCalledWith({ + endpoint: 'http://127.0.0.1:17670', + local: true, + name: 'kaiden-local', + }); + }); + + test('registers successfully even when removeGateway fails', async () => { + vi.spyOn(console, 'log').mockImplementation(() => undefined); + const proc = createMockChildProcess(); + vi.mocked(spawn).mockReturnValue(proc); + vi.mocked(exec.exec).mockResolvedValue(mockExecResult('openshell-gateway 0.0.69')); + vi.mocked(openshellCli.checkEndpointStatus).mockResolvedValue(true); + vi.mocked(openshellCli.listGateways).mockResolvedValue([ + { name: 'kaiden-local', endpoint: 'http://127.0.0.1:9999' } as GatewayInfo, + ]); + vi.mocked(openshellCli.removeGateway).mockRejectedValue(new Error('no such gateway')); + + await gateway.start(); + + expect(openshellCli.addGateway).toHaveBeenCalledWith({ + endpoint: 'http://127.0.0.1:17670', + local: true, + name: 'kaiden-local', + }); + }); + test('skips registerWithCli when skipRegistration is true', async () => { vi.spyOn(console, 'log').mockImplementation(() => undefined); const proc = createMockChildProcess(); diff --git a/packages/main/src/plugin/openshell-cli/openshell-gateway.ts b/packages/main/src/plugin/openshell-cli/openshell-gateway.ts index 7028f8470a..3fcc72c628 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-gateway.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-gateway.ts @@ -366,6 +366,15 @@ export class OpenshellGateway implements Disposable { private async registerWithCli(): Promise { const endpoint = `http://${this.#bindAddress}:${this.#port}`; try { + const gateways = await this.openshellCli.listGateways(); + const existing = gateways.find(gw => gw.name === 'kaiden-local'); + if (existing) { + if (existing.endpoint === endpoint) { + console.log(`[openshell-gateway] kaiden-local already registered at ${endpoint}`); + return; + } + await this.openshellCli.removeGateway('kaiden-local').catch(() => {}); + } await this.openshellCli.addGateway({ endpoint, local: true, name: 'kaiden-local' }); console.log(`[openshell-gateway] registered with CLI as kaiden-local at ${endpoint}`); } catch (err: unknown) {