feat(companion): add platform bootstrap helper

This commit is contained in:
William Valentin
2026-02-16 18:34:57 -08:00
parent 97afc39e01
commit 4d29c381f7
6 changed files with 60 additions and 1 deletions
+1
View File
@@ -1195,6 +1195,7 @@ Companion runtime helper:
- `MacOSCompanionClient` (`platform: "macos"`, APNs push registration) - `MacOSCompanionClient` (`platform: "macos"`, APNs push registration)
- `IOSCompanionClient` (`platform: "ios"`, APNs push registration) - `IOSCompanionClient` (`platform: "ios"`, APNs push registration)
- `AndroidCompanionClient` (`platform: "android"`, FCM push registration) - `AndroidCompanionClient` (`platform: "android"`, FCM push registration)
- shared `bootstrap()` helper (`register` + `getCapabilities`) for startup handshakes
## Canvas / A2UI Foundation ## Canvas / A2UI Foundation
+15
View File
@@ -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" "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": { "browser-tools-activation-clarity": {
"status": "completed", "status": "completed",
"date": "2026-02-17", "date": "2026-02-17",
+1
View File
@@ -41,4 +41,5 @@ export type {
PlatformClientOptions, PlatformClientOptions,
RegisterPushTokenInput, RegisterPushTokenInput,
SharedStatusInput, SharedStatusInput,
PlatformBootstrapResult,
} from './platformClients.js'; } from './platformClients.js';
@@ -118,7 +118,9 @@ describe('platform clients integration', () => {
await client.connect(); await client.connect();
try { 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({ const status = await client.setStatus({
appVersion: '1.0.0', appVersion: '1.0.0',
statusText: 'menu-bar-active', statusText: 'menu-bar-active',
+17
View File
@@ -151,4 +151,21 @@ describe('platform companion clients', () => {
expect(mock.deleteCanvasArtifact).toHaveBeenCalledWith({ sessionId: 'ws:test-canvas', artifactId: 'a1' }); expect(mock.deleteCanvasArtifact).toHaveBeenCalledWith({ sessionId: 'ws:test-canvas', artifactId: 'a1' });
expect(mock.clearCanvasArtifacts).toHaveBeenCalledWith('ws:test-canvas'); 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),
});
});
}); });
+23
View File
@@ -38,6 +38,11 @@ export type SharedStatusInput = Omit<
'platform' 'platform'
>; >;
export interface PlatformBootstrapResult {
register: NodeRegisterResult;
capabilities: NodeCapabilitiesResult;
}
export class MacOSCompanionClient { export class MacOSCompanionClient {
private readonly runtime: CompanionRuntimeClient; private readonly runtime: CompanionRuntimeClient;
private readonly nodeId: string; private readonly nodeId: string;
@@ -70,6 +75,12 @@ export class MacOSCompanionClient {
}); });
} }
async bootstrap(): Promise<PlatformBootstrapResult> {
const register = await this.register();
const capabilities = await this.getCapabilities();
return { register, capabilities };
}
getCapabilities(): Promise<NodeCapabilitiesResult> { getCapabilities(): Promise<NodeCapabilitiesResult> {
return this.runtime.getNodeCapabilities(); return this.runtime.getNodeCapabilities();
} }
@@ -163,6 +174,12 @@ export class IOSCompanionClient {
}); });
} }
async bootstrap(): Promise<PlatformBootstrapResult> {
const register = await this.register();
const capabilities = await this.getCapabilities();
return { register, capabilities };
}
getCapabilities(): Promise<NodeCapabilitiesResult> { getCapabilities(): Promise<NodeCapabilitiesResult> {
return this.runtime.getNodeCapabilities(); return this.runtime.getNodeCapabilities();
} }
@@ -256,6 +273,12 @@ export class AndroidCompanionClient {
}); });
} }
async bootstrap(): Promise<PlatformBootstrapResult> {
const register = await this.register();
const capabilities = await this.getCapabilities();
return { register, capabilities };
}
getCapabilities(): Promise<NodeCapabilitiesResult> { getCapabilities(): Promise<NodeCapabilitiesResult> {
return this.runtime.getNodeCapabilities(); return this.runtime.getNodeCapabilities();
} }