From 1fb70ce0b180be44f7f763777d7a7418c4b4e687 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 16 Feb 2026 19:12:47 -0800 Subject: [PATCH] feat(companion): add heartbeat loop getState snapshot --- README.md | 2 +- docs/plans/state.json | 14 ++++++++++++++ src/companion/heartbeatLoop.test.ts | 5 +++++ src/companion/heartbeatLoop.ts | 14 ++++++++++++++ src/companion/index.ts | 1 + 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a46b3b..2cbebb2 100644 --- a/README.md +++ b/README.md @@ -1200,7 +1200,7 @@ Companion runtime helper: - `createHeartbeatLoop()` convenience helper that returns a bound `CompanionHeartbeatLoop` - optional `defaultSessionId` for canvas helper calls so `sessionId` can be omitted per call - `dispose()` lifecycle helper for unified runtime teardown -- `src/companion/heartbeatLoop.ts` provides `CompanionHeartbeatLoop` for periodic heartbeat scheduling (`publishHeartbeat`) with start/stop safety, optional interval jitter (`jitterRatio`) to spread load, `tickNow()` for manual sends, success/error hooks, failure observability (`failureCount`, `lastFailure`), and optional auto-stop after repeated failures. +- `src/companion/heartbeatLoop.ts` provides `CompanionHeartbeatLoop` for periodic heartbeat scheduling (`publishHeartbeat`) with start/stop safety, optional interval jitter (`jitterRatio`) to spread load, `tickNow()` for manual sends, success/error hooks, failure observability (`failureCount`, `lastFailure`, `getState()`), and optional auto-stop after repeated failures. ## Canvas / A2UI Foundation diff --git a/docs/plans/state.json b/docs/plans/state.json index 5c679d6..56543a6 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -540,6 +540,20 @@ ], "test_status": "pnpm test:run src/companion/runtimeClient.test.ts src/companion/platformClients.test.ts src/companion/heartbeatLoop.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing" }, + "companion-heartbeat-loop-state-snapshot": { + "status": "completed", + "date": "2026-02-17", + "updated": "2026-02-17", + "summary": "Added `getState()` snapshot helper on `CompanionHeartbeatLoop` to expose consolidated runtime state (`running`, `failureCount`, `lastFailure`) for companion observability surfaces.", + "files_modified": [ + "src/companion/heartbeatLoop.ts", + "src/companion/heartbeatLoop.test.ts", + "src/companion/index.ts", + "README.md", + "docs/plans/state.json" + ], + "test_status": "pnpm test:run src/companion/heartbeatLoop.test.ts src/companion/runtimeClient.test.ts 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/heartbeatLoop.test.ts b/src/companion/heartbeatLoop.test.ts index 883533a..43d8dcc 100644 --- a/src/companion/heartbeatLoop.test.ts +++ b/src/companion/heartbeatLoop.test.ts @@ -191,5 +191,10 @@ describe('CompanionHeartbeatLoop', () => { await loop.tickNow(); expect(publishHeartbeat).toHaveBeenCalledTimes(1); expect(loop.running).toBe(false); + expect(loop.getState()).toEqual({ + running: false, + failureCount: 0, + lastFailure: null, + }); }); }); diff --git a/src/companion/heartbeatLoop.ts b/src/companion/heartbeatLoop.ts index c897b21..54983ad 100644 --- a/src/companion/heartbeatLoop.ts +++ b/src/companion/heartbeatLoop.ts @@ -16,6 +16,12 @@ export interface CompanionHeartbeatLoopOptions { randomFn?: () => number; } +export interface CompanionHeartbeatLoopState { + running: boolean; + failureCount: number; + lastFailure: Error | null; +} + /** * Lightweight periodic heartbeat loop for companion runtimes. * Calls `publishHeartbeat` on a fixed interval with optional dynamic payload generation. @@ -77,6 +83,14 @@ export class CompanionHeartbeatLoop { return this.lastError; } + getState(): CompanionHeartbeatLoopState { + return { + running: this.running, + failureCount: this.failureCount, + lastFailure: this.lastFailure, + }; + } + start(runImmediately = true): void { if (this.started) { return; diff --git a/src/companion/index.ts b/src/companion/index.ts index 3193c24..a61722d 100644 --- a/src/companion/index.ts +++ b/src/companion/index.ts @@ -57,4 +57,5 @@ export type { export type { HeartbeatPublisher, CompanionHeartbeatLoopOptions, + CompanionHeartbeatLoopState, } from './heartbeatLoop.js';