From 1463e8a4bcd136ef94b46bcff05d65d4237bcc2a Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Mon, 14 Sep 2026 18:37:48 -0400 Subject: [PATCH] fix(session-search): report a paired server without session search as host-too-old on status reads --- src/main/ipc/ai-vault-search.test.ts | 2 ++ src/main/ipc/ai-vault-search.ts | 30 ++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/ipc/ai-vault-search.test.ts b/src/main/ipc/ai-vault-search.test.ts index d1d1211020d..be4028460a0 100644 --- a/src/main/ipc/ai-vault-search.test.ts +++ b/src/main/ipc/ai-vault-search.test.ts @@ -124,6 +124,8 @@ describe('desktop IPC and preload search boundary', () => { kind: 'unavailable', reason: 'no-service' }) + // Status is the one read where "no such method" must not collapse into "off". + await expect(aiVaultApi.searchStatus('runtime:env-1')).rejects.toThrow('host-too-old') runtimeSearch.mockRejectedValue( Object.assign(new Error('runtime disconnected'), { code: 'connection_lost' }) ) diff --git a/src/main/ipc/ai-vault-search.ts b/src/main/ipc/ai-vault-search.ts index c54406d5462..75ce9fd8725 100644 --- a/src/main/ipc/ai-vault-search.ts +++ b/src/main/ipc/ai-vault-search.ts @@ -26,6 +26,7 @@ import { toSshExecutionHostId, type ParsedExecutionHost } from '../../shared/execution-host' +import { redactStatusForTransport } from '../../shared/ai-vault-search-transport' import { requestActiveSshSessionSearch } from './ssh' import { clearSessionSearchInService } from '../ai-vault/session-scanner-service-spawn' import { searchAllExecutionHosts, type SessionSearchHostLeg } from './ai-vault-search-all-hosts' @@ -184,12 +185,37 @@ function remoteHostLeg(host: ParsedExecutionHost): SessionSearchHostLeg { } } -function statusByExecutionHost(scope: ParsedExecutionHost): Promise { +async function statusByExecutionHost(scope: ParsedExecutionHost): Promise { if (scope.kind === 'local') { return sessionSearchServiceStatus({}, 'ipc') } + if (scope.kind === 'runtime') { + return runtimeHostStatus(scope.environmentId) + } const client = remoteSearchClient(scope, handlerOptions.callRuntimeSearch) - return client ? client.searchStatus() : Promise.resolve(unavailableSessionSearchStatus()) + return client ? client.searchStatus() : unavailableSessionSearchStatus() +} + +/** + * Not through the shared client: it answers an unknown method with `unavailable`, which + * the settings pane cannot tell from a current server that is switched off. + */ +async function runtimeHostStatus(environmentId: string): Promise { + const call = handlerOptions.callRuntimeSearch + if (!call) { + return unavailableSessionSearchStatus() + } + try { + return redactStatusForTransport( + AiVaultSearchStatusSchema.parse(await call(environmentId, 'aiVault.searchStatus', {})), + 'relay' + ) + } catch (error) { + if (isUnknownSessionSearchMethod(error)) { + throw new Error(HOST_TOO_OLD_MESSAGE) + } + throw error + } } // Null for the local host and for a runtime environment with no injected transport.