fix(companion): type-guard event wait name validation

This commit is contained in:
William Valentin
2026-02-16 19:45:01 -08:00
parent 7304a4b08f
commit 36ad56a6c6
3 changed files with 24 additions and 2 deletions
+12
View File
@@ -820,6 +820,18 @@
], ],
"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" "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-runtime-event-name-type-guard": {
"status": "completed",
"date": "2026-02-17",
"updated": "2026-02-17",
"summary": "Hardened runtime event-name validation with explicit string type guards so `waitForEvent()`/`waitForAnyEvent()` reject non-string inputs deterministically.",
"files_modified": [
"src/companion/runtimeClient.ts",
"src/companion/runtimeClient.test.ts",
"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": { "browser-tools-activation-clarity": {
"status": "completed", "status": "completed",
"date": "2026-02-17", "date": "2026-02-17",
+6
View File
@@ -350,6 +350,9 @@ describe('CompanionRuntimeClient', () => {
expect(() => client.waitForEvent('')).toThrow('eventName must be a non-empty string'); expect(() => client.waitForEvent('')).toThrow('eventName must be a non-empty string');
expect(() => client.waitForEvent(' ')).toThrow('eventName must be a non-empty string'); expect(() => client.waitForEvent(' ')).toThrow('eventName must be a non-empty string');
expect(() => client.waitForEvent(123 as unknown as string)).toThrow(
'eventName must be a non-empty string',
);
}); });
it('waitForEvent supports AbortSignal cancellation', async () => { it('waitForEvent supports AbortSignal cancellation', async () => {
@@ -585,6 +588,9 @@ describe('CompanionRuntimeClient', () => {
expect(() => client.waitForAnyEvent(['agent.stream', ' '])).toThrow( expect(() => client.waitForAnyEvent(['agent.stream', ' '])).toThrow(
'eventNames must not contain empty values', 'eventNames must not contain empty values',
); );
expect(() => client.waitForAnyEvent(['agent.stream', 123 as unknown as string])).toThrow(
'eventNames must not contain empty values',
);
}); });
it('connects and performs node registration + capability discovery', async () => { it('connects and performs node registration + capability discovery', async () => {
+6 -2
View File
@@ -437,7 +437,7 @@ export class CompanionRuntimeClient {
signal?: AbortSignal; signal?: AbortSignal;
}, },
): Promise<TData> { ): Promise<TData> {
if (eventName.trim().length === 0) { if (typeof eventName !== 'string' || eventName.trim().length === 0) {
throw new Error('eventName must be a non-empty string'); throw new Error('eventName must be a non-empty string');
} }
const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs; const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs;
@@ -505,7 +505,11 @@ export class CompanionRuntimeClient {
if (eventNames.length === 0) { if (eventNames.length === 0) {
throw new Error('eventNames must contain at least one event name'); throw new Error('eventNames must contain at least one event name');
} }
if (eventNames.some((eventName) => eventName.trim().length === 0)) { if (
eventNames.some(
(eventName) => typeof eventName !== 'string' || eventName.trim().length === 0,
)
) {
throw new Error('eventNames must not contain empty values'); throw new Error('eventNames must not contain empty values');
} }
const eventNameSet = new Set(eventNames); const eventNameSet = new Set(eventNames);