From 4f25994876b229e374765dc621822ec7d9c92366 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 16 Feb 2026 19:30:39 -0800 Subject: [PATCH] feat(companion): add known event name typing and listing --- README.md | 2 +- docs/plans/state.json | 14 ++++++++++++++ src/companion/index.ts | 1 + src/companion/runtimeClient.test.ts | 12 ++++++++++++ src/companion/runtimeClient.ts | 12 +++++++++--- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 00c0031..028bfbb 100644 --- a/README.md +++ b/README.md @@ -1190,7 +1190,7 @@ Methods: - `system.capabilities` returns gateway protocol and node policy snapshot. Companion runtime helper: -- `src/companion/runtimeClient.ts` provides a typed Node/WebSocket client for companion runtimes (macOS/iOS/Android workers) with wrappers for `node.register`, `node.capabilities.get`, `node.location.set/get`, `node.status.set`, `node.push_token.set`, `system.capabilities`, `system.nodes`, and canvas artifact RPCs (`canvas.put/get/list/delete/clear`), plus convenience helpers (`bootstrapNode`, optional `autoConnect`, `dispose()`) and event helpers (`subscribeEvents()`, `subscribeEvent()`, `subscribeAgentStream()`, `subscribeAgentTyping()`, `subscribeContextWarning()`, `waitForEvent()` with timeout/predicate/abort support and deterministic teardown cancellation, `waitForAnyEvent()`, `waitForAgentStream()`, `waitForAgentTyping()`, `waitForContextWarning()`, `clearEventSubscriptions()`). +- `src/companion/runtimeClient.ts` provides a typed Node/WebSocket client for companion runtimes (macOS/iOS/Android workers) with wrappers for `node.register`, `node.capabilities.get`, `node.location.set/get`, `node.status.set`, `node.push_token.set`, `system.capabilities`, `system.nodes`, and canvas artifact RPCs (`canvas.put/get/list/delete/clear`), plus convenience helpers (`bootstrapNode`, optional `autoConnect`, `dispose()`) and event helpers (`subscribeEvents()`, `subscribeEvent()`, `subscribeAgentStream()`, `subscribeAgentTyping()`, `subscribeContextWarning()`, `waitForEvent()` with timeout/predicate/abort support and deterministic teardown cancellation, `waitForAnyEvent()`, `waitForAgentStream()`, `waitForAgentTyping()`, `waitForContextWarning()`, `clearEventSubscriptions()`, `listKnownEventNames()`). - `src/companion/platformClients.ts` provides platform-focused wrappers: - `MacOSCompanionClient` (`platform: "macos"`, APNs push registration) - `IOSCompanionClient` (`platform: "ios"`, APNs push registration) diff --git a/docs/plans/state.json b/docs/plans/state.json index 4eb9d36..470440f 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -633,6 +633,20 @@ ], "test_status": "pnpm test:run src/companion/platformClients.test.ts src/companion/runtimeClient.test.ts src/companion/heartbeatLoop.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing" }, + "companion-runtime-known-event-names": { + "status": "completed", + "date": "2026-02-17", + "updated": "2026-02-17", + "summary": "Added typed companion event-name contract (`CompanionEventName`) and `listKnownEventNames()` helper for discoverable event surfaces in companion clients.", + "files_modified": [ + "src/companion/runtimeClient.ts", + "src/companion/runtimeClient.test.ts", + "src/companion/index.ts", + "README.md", + "docs/plans/state.json" + ], + "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" + }, "browser-tools-activation-clarity": { "status": "completed", "date": "2026-02-17", diff --git a/src/companion/index.ts b/src/companion/index.ts index 17bd81f..8c9b25a 100644 --- a/src/companion/index.ts +++ b/src/companion/index.ts @@ -14,6 +14,7 @@ export type { CompanionRuntimeClientOptions, CompanionEventHandler, CompanionTypedEventHandler, + CompanionEventName, CompanionEventPredicate, CompanionEventEnvelope, RegisterNodeInput, diff --git a/src/companion/runtimeClient.test.ts b/src/companion/runtimeClient.test.ts index de7ad60..44db720 100644 --- a/src/companion/runtimeClient.test.ts +++ b/src/companion/runtimeClient.test.ts @@ -224,6 +224,18 @@ describe('CompanionRuntimeClient', () => { expect(typingHandler).toHaveBeenCalledWith({ active: true }); }); + it('lists known companion event names', () => { + const client = new CompanionRuntimeClient({ + url: 'ws://127.0.0.1:1', + }); + + expect(client.listKnownEventNames()).toEqual([ + 'agent.stream', + 'agent.typing', + 'context_warning', + ]); + }); + it('supports subscribeContextWarning helper', () => { const client = new CompanionRuntimeClient({ url: 'ws://127.0.0.1:1', diff --git a/src/companion/runtimeClient.ts b/src/companion/runtimeClient.ts index 8d92a22..0ce245c 100644 --- a/src/companion/runtimeClient.ts +++ b/src/companion/runtimeClient.ts @@ -54,6 +54,8 @@ export const COMPANION_EVENT_NAMES = { agentTyping: 'agent.typing', contextWarning: 'context_warning', } as const; +export type CompanionEventName = + (typeof COMPANION_EVENT_NAMES)[keyof typeof COMPANION_EVENT_NAMES]; export interface RegisterNodeInput { nodeId: string; @@ -388,7 +390,7 @@ export class CompanionRuntimeClient { } subscribeEvent( - eventName: string, + eventName: CompanionEventName | string, handler: CompanionTypedEventHandler, ): () => void { return this.subscribeEvents((event, data) => { @@ -418,7 +420,7 @@ export class CompanionRuntimeClient { } waitForEvent( - eventName: string, + eventName: CompanionEventName | string, options?: { timeoutMs?: number; predicate?: CompanionEventPredicate; @@ -480,7 +482,7 @@ export class CompanionRuntimeClient { } waitForAnyEvent( - eventNames: readonly string[], + eventNames: readonly (CompanionEventName | string)[], options?: { timeoutMs?: number; predicate?: (event: string, data: TData) => boolean; @@ -573,6 +575,10 @@ export class CompanionRuntimeClient { return this.waitForEvent(COMPANION_EVENT_NAMES.contextWarning, options); } + listKnownEventNames(): CompanionEventName[] { + return Object.values(COMPANION_EVENT_NAMES); + } + async call(method: string, params?: Record): Promise { if (!this.connected) { if (!this.autoConnect) {