diff --git a/apps/electron/src/main/kernelHost.test.ts b/apps/electron/src/main/kernelHost.test.ts index 0a5684e..7464970 100644 --- a/apps/electron/src/main/kernelHost.test.ts +++ b/apps/electron/src/main/kernelHost.test.ts @@ -6,6 +6,8 @@ let lastKernelOptions: Record | null = null; let lastMarketData: FakeMarketDataService | null = null; let lastAutomationContext: unknown = null; let forwardedEvents: unknown[] = []; +// Providers the mocked ProviderRouter reports as registered (default: none). +let routerProviderList: Array> = []; const routerFetchers = { getQuote: async () => ({ symbol: 'AAPL.US' }) }; class FakeMarketDataService { @@ -86,6 +88,8 @@ class FakeAgentKernel { mock.module('electron', () => ({ app: { getPath: () => '/tmp/finagent-test', + getVersion: () => '0.0.0-test', + isPackaged: false, }, safeStorage: { isEncryptionAvailable: () => true, @@ -177,7 +181,7 @@ mock.module('@finagent/shared', () => ({ register = () => undefined; setRouting = () => undefined; get = () => undefined; - list = () => []; + list = () => routerProviderList; coverage = () => []; }, ConnectionStore: class { @@ -352,6 +356,7 @@ beforeEach(() => { lastMarketData = null; lastAutomationContext = null; forwardedEvents = []; + routerProviderList = []; }); afterEach(() => { @@ -436,6 +441,36 @@ describe('AgentKernelHost', () => { host.dispose(); }); + it('reports the broker as disconnected when the broker has no signed-in account', async () => { + routerProviderList = [ + { + id: 'longbridge-broker', + kind: 'broker-account', + accounts: async () => ({ ok: false, error: { code: 'AUTH_EXPIRED' } }), + }, + ]; + const host = new AgentKernelHost(); + + const bundle = await host.collectDiagnostics(); + expect(bundle.providers.broker).toEqual({ connected: false, accountCount: 0 }); + host.dispose(); + }); + + it('reports the connected broker account count from the broker itself', async () => { + routerProviderList = [ + { + id: 'longbridge-broker', + kind: 'broker-account', + accounts: async () => ({ ok: true, data: [{ id: 'acct-1', name: 'Default (acct-1)' }] }), + }, + ]; + const host = new AgentKernelHost(); + + const bundle = await host.collectDiagnostics(); + expect(bundle.providers.broker).toEqual({ connected: true, accountCount: 1 }); + host.dispose(); + }); + it('forwards kernel agent events to the attached window', async () => { let subscriber: ((event: AgentEvent) => void) | null = null; fakeRuns.subscribe = (listener) => { diff --git a/apps/electron/src/main/kernelHost.ts b/apps/electron/src/main/kernelHost.ts index 63bea45..93c3f43 100644 --- a/apps/electron/src/main/kernelHost.ts +++ b/apps/electron/src/main/kernelHost.ts @@ -1579,6 +1579,12 @@ export class AgentKernelHost { } catch { // Provider wiring lands with the connector slice; empty until then. } + // `providerSummaries` lists every REGISTERED provider, and the broker is + // registered unconditionally in the constructor — so "is the broker in the + // list" is a constant, not a connection state. Ask the broker itself, the + // way `entryFor` does for the Connections surface. + const { connected: brokerConnected, accountCount: brokerAccountCount } = + await this.brokerAccountFacts(); return collectDiagnostics({ version: app.getVersion(), os: process.platform, @@ -1589,8 +1595,8 @@ export class AgentKernelHost { llmProviderId: llmState?.model?.provider ?? null, llmModel: llmState?.model?.id ?? null, financialProviders: providerSummaries, - brokerConnected: providerSummaries.some((p) => p.id === 'longbridge-broker'), - brokerAccountCount: providerSummaries.some((p) => p.id === 'longbridge-broker') ? 1 : 0, + brokerConnected, + brokerAccountCount, skillsLoadedCount: this.skillHub.listSkills().length, capabilities: this.registry, resources: { dev: !app.isPackaged, root: getRuntimeRoot() }, @@ -2461,6 +2467,28 @@ export class AgentKernelHost { } } + /** + * Broker facts for the diagnostics bundle. Registration is not connection: + * the broker provider is registered unconditionally at construction, so its + * presence in the provider list says nothing about whether an account is + * actually signed in. Ask the broker for its accounts — the same call + * `entryFor` makes for the Connections surface. + */ + private async brokerAccountFacts(): Promise<{ connected: boolean; accountCount: number }> { + const broker = this.providerRouter.list().find((provider) => provider.kind === 'broker-account'); + if (!broker || broker.kind !== 'broker-account') { + return { connected: false, accountCount: 0 }; + } + try { + const accounts = await broker.accounts(); + if (!accounts.ok) return { connected: false, accountCount: 0 }; + return { connected: accounts.data.length > 0, accountCount: accounts.data.length }; + } catch { + // A broker that cannot answer is reported as not connected. + return { connected: false, accountCount: 0 }; + } + } + private listFinancialProviders(): FinancialProviderSummary[] { return this.providerRouter.list().map((provider) => { const coverage = this.providerRouter.coverage().find((c) => c.providerId === provider.id);