Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion apps/electron/src/main/kernelHost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ let lastKernelOptions: Record<string, unknown> | 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<Record<string, unknown>> = [];
const routerFetchers = { getQuote: async () => ({ symbol: 'AAPL.US' }) };

class FakeMarketDataService {
Expand Down Expand Up @@ -86,6 +88,8 @@ class FakeAgentKernel {
mock.module('electron', () => ({
app: {
getPath: () => '/tmp/finagent-test',
getVersion: () => '0.0.0-test',
isPackaged: false,
},
safeStorage: {
isEncryptionAvailable: () => true,
Expand Down Expand Up @@ -177,7 +181,7 @@ mock.module('@finagent/shared', () => ({
register = () => undefined;
setRouting = () => undefined;
get = () => undefined;
list = () => [];
list = () => routerProviderList;
coverage = () => [];
},
ConnectionStore: class {
Expand Down Expand Up @@ -352,6 +356,7 @@ beforeEach(() => {
lastMarketData = null;
lastAutomationContext = null;
forwardedEvents = [];
routerProviderList = [];
});

afterEach(() => {
Expand Down Expand Up @@ -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) => {
Expand Down
32 changes: 30 additions & 2 deletions apps/electron/src/main/kernelHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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() },
Expand Down Expand Up @@ -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);
Expand Down
Loading