feat(companion): add platform createHeartbeatLoop helper

This commit is contained in:
William Valentin
2026-02-16 18:48:33 -08:00
parent a5c5a320ca
commit f67362bf3b
4 changed files with 47 additions and 0 deletions
+1
View File
@@ -1197,6 +1197,7 @@ Companion runtime helper:
- `AndroidCompanionClient` (`platform: "android"`, FCM push registration)
- shared `bootstrap()` helper (`register` + `getCapabilities`) for startup handshakes
- shared `publishHeartbeat()` helper for periodic `node.status.set` updates with safe defaults
- `createHeartbeatLoop()` convenience helper that returns a bound `CompanionHeartbeatLoop`
- optional `defaultSessionId` for canvas helper calls so `sessionId` can be omitted per call
- `src/companion/heartbeatLoop.ts` provides `CompanionHeartbeatLoop` for periodic heartbeat scheduling (`publishHeartbeat`) with start/stop safety, `tickNow()` for manual sends, error hooks, and optional auto-stop after repeated failures.
+13
View File
@@ -382,6 +382,19 @@
],
"test_status": "pnpm test:run src/companion/runtimeClient.test.ts src/companion/heartbeatLoop.test.ts src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing"
},
"companion-platform-heartbeat-loop-factory": {
"status": "completed",
"date": "2026-02-17",
"updated": "2026-02-17",
"summary": "Added `createHeartbeatLoop()` to macOS/iOS/Android platform clients, returning a client-bound `CompanionHeartbeatLoop` for minimal heartbeat wiring in companion app runtimes.",
"files_modified": [
"src/companion/platformClients.ts",
"src/companion/platformClients.test.ts",
"README.md",
"docs/plans/state.json"
],
"test_status": "pnpm test:run src/companion/platformClients.test.ts src/companion/heartbeatLoop.test.ts src/companion/runtimeClient.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing"
},
"browser-tools-activation-clarity": {
"status": "completed",
"date": "2026-02-17",
+15
View File
@@ -223,4 +223,19 @@ describe('platform companion clients', () => {
'sessionId is required (provide one or configure defaultSessionId)',
);
});
it('creates a bound heartbeat loop helper from platform clients', async () => {
const mock = createRuntimeMock();
const client = new IOSCompanionClient({ runtime: mock.runtime, nodeId: 'ios-node' });
const loop = client.createHeartbeatLoop();
await loop.tickNow();
expect(mock.setNodeStatus).toHaveBeenCalledWith(
expect.objectContaining({
platform: 'ios',
statusText: 'heartbeat',
}),
);
});
});
+18
View File
@@ -18,6 +18,12 @@ import type {
SystemCapabilitiesResult,
SystemNodesResult,
} from './runtimeClient.js';
import {
CompanionHeartbeatLoop,
} from './heartbeatLoop.js';
import type {
CompanionHeartbeatLoopOptions,
} from './heartbeatLoop.js';
export interface PlatformClientOptions {
runtime: CompanionRuntimeClient;
@@ -129,6 +135,10 @@ export class MacOSCompanionClient {
});
}
createHeartbeatLoop(options: CompanionHeartbeatLoopOptions = {}): CompanionHeartbeatLoop {
return new CompanionHeartbeatLoop(this, options);
}
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
return this.runtime.setNodeLocation(location);
}
@@ -261,6 +271,10 @@ export class IOSCompanionClient {
});
}
createHeartbeatLoop(options: CompanionHeartbeatLoopOptions = {}): CompanionHeartbeatLoop {
return new CompanionHeartbeatLoop(this, options);
}
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
return this.runtime.setNodeLocation(location);
}
@@ -393,6 +407,10 @@ export class AndroidCompanionClient {
});
}
createHeartbeatLoop(options: CompanionHeartbeatLoopOptions = {}): CompanionHeartbeatLoop {
return new CompanionHeartbeatLoop(this, options);
}
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
return this.runtime.setNodeLocation(location);
}