From 4d29c381f77736373e19adc360fb3b0d88a16b34 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 16 Feb 2026 18:34:57 -0800 Subject: [PATCH] feat(companion): add platform bootstrap helper --- README.md | 1 + docs/plans/state.json | 15 ++++++++++++ src/companion/index.ts | 1 + .../platformClients.integration.test.ts | 4 +++- src/companion/platformClients.test.ts | 17 ++++++++++++++ src/companion/platformClients.ts | 23 +++++++++++++++++++ 6 files changed, 60 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fde7875..1977225 100644 --- a/README.md +++ b/README.md @@ -1195,6 +1195,7 @@ Companion runtime helper: - `MacOSCompanionClient` (`platform: "macos"`, APNs push registration) - `IOSCompanionClient` (`platform: "ios"`, APNs push registration) - `AndroidCompanionClient` (`platform: "android"`, FCM push registration) + - shared `bootstrap()` helper (`register` + `getCapabilities`) for startup handshakes ## Canvas / A2UI Foundation diff --git a/docs/plans/state.json b/docs/plans/state.json index 3d32b27..5b4bc19 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -202,6 +202,21 @@ ], "test_status": "pnpm test:run src/companion/runtimeClient.test.ts src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing" }, + "companion-platform-bootstrap-helper": { + "status": "completed", + "date": "2026-02-17", + "updated": "2026-02-17", + "summary": "Added a `bootstrap()` helper to macOS/iOS/Android companion platform clients to standardize startup handshake (node registration followed by capability fetch), with unit and integration coverage.", + "files_modified": [ + "src/companion/platformClients.ts", + "src/companion/platformClients.test.ts", + "src/companion/platformClients.integration.test.ts", + "src/companion/index.ts", + "README.md", + "docs/plans/state.json" + ], + "test_status": "pnpm test:run src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing" + }, "browser-tools-activation-clarity": { "status": "completed", "date": "2026-02-17", diff --git a/src/companion/index.ts b/src/companion/index.ts index be535e1..c6affd0 100644 --- a/src/companion/index.ts +++ b/src/companion/index.ts @@ -41,4 +41,5 @@ export type { PlatformClientOptions, RegisterPushTokenInput, SharedStatusInput, + PlatformBootstrapResult, } from './platformClients.js'; diff --git a/src/companion/platformClients.integration.test.ts b/src/companion/platformClients.integration.test.ts index 61fab2c..9a4be72 100644 --- a/src/companion/platformClients.integration.test.ts +++ b/src/companion/platformClients.integration.test.ts @@ -118,7 +118,9 @@ describe('platform clients integration', () => { await client.connect(); try { - await client.register(); + const boot = await client.bootstrap(); + expect(boot.register.registered).toBe(true); + expect(boot.capabilities.node.id).toBe('macos-e2e'); const status = await client.setStatus({ appVersion: '1.0.0', statusText: 'menu-bar-active', diff --git a/src/companion/platformClients.test.ts b/src/companion/platformClients.test.ts index 066dd49..6e4c33a 100644 --- a/src/companion/platformClients.test.ts +++ b/src/companion/platformClients.test.ts @@ -151,4 +151,21 @@ describe('platform companion clients', () => { expect(mock.deleteCanvasArtifact).toHaveBeenCalledWith({ sessionId: 'ws:test-canvas', artifactId: 'a1' }); expect(mock.clearCanvasArtifacts).toHaveBeenCalledWith('ws:test-canvas'); }); + + it('bootstrap registers node and then fetches capabilities', async () => { + const mock = createRuntimeMock(); + const client = new IOSCompanionClient({ runtime: mock.runtime, nodeId: 'ios-node' }); + + const result = await client.bootstrap(); + + expect(mock.registerNode).toHaveBeenCalledOnce(); + expect(mock.getNodeCapabilities).toHaveBeenCalledOnce(); + expect(mock.registerNode.mock.invocationCallOrder[0]).toBeLessThan( + mock.getNodeCapabilities.mock.invocationCallOrder[0], + ); + expect(result).toEqual({ + register: { registered: true }, + capabilities: expect.any(Object), + }); + }); }); diff --git a/src/companion/platformClients.ts b/src/companion/platformClients.ts index 3ab8d0c..a814fa8 100644 --- a/src/companion/platformClients.ts +++ b/src/companion/platformClients.ts @@ -38,6 +38,11 @@ export type SharedStatusInput = Omit< 'platform' >; +export interface PlatformBootstrapResult { + register: NodeRegisterResult; + capabilities: NodeCapabilitiesResult; +} + export class MacOSCompanionClient { private readonly runtime: CompanionRuntimeClient; private readonly nodeId: string; @@ -70,6 +75,12 @@ export class MacOSCompanionClient { }); } + async bootstrap(): Promise { + const register = await this.register(); + const capabilities = await this.getCapabilities(); + return { register, capabilities }; + } + getCapabilities(): Promise { return this.runtime.getNodeCapabilities(); } @@ -163,6 +174,12 @@ export class IOSCompanionClient { }); } + async bootstrap(): Promise { + const register = await this.register(); + const capabilities = await this.getCapabilities(); + return { register, capabilities }; + } + getCapabilities(): Promise { return this.runtime.getNodeCapabilities(); } @@ -256,6 +273,12 @@ export class AndroidCompanionClient { }); } + async bootstrap(): Promise { + const register = await this.register(); + const capabilities = await this.getCapabilities(); + return { register, capabilities }; + } + getCapabilities(): Promise { return this.runtime.getNodeCapabilities(); }