Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Enforce root-cause investigation before any fix—structured debugging protocol for bugs and test failures
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
condition-based-waiting-example.ts
1// Complete implementation of condition-based waiting utilities2// From: Lace test infrastructure improvements (2025-10-03)3// Context: Fixed 15 flaky tests by replacing arbitrary timeouts45import type { ThreadManager } from '~/threads/thread-manager';6import type { LaceEvent, LaceEventType } from '~/threads/types';78/**9* Wait for a specific event type to appear in thread10*11* @param threadManager - The thread manager to query12* @param threadId - Thread to check for events13* @param eventType - Type of event to wait for14* @param timeoutMs - Maximum time to wait (default 5000ms)15* @returns Promise resolving to the first matching event16*17* Example:18* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');19*/20export function waitForEvent(21threadManager: ThreadManager,22threadId: string,23eventType: LaceEventType,24timeoutMs = 500025): Promise<LaceEvent> {26return new Promise((resolve, reject) => {27const startTime = Date.now();2829const check = () => {30const events = threadManager.getEvents(threadId);31const event = events.find((e) => e.type === eventType);3233if (event) {34resolve(event);35} else if (Date.now() - startTime > timeoutMs) {36reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));37} else {38setTimeout(check, 10); // Poll every 10ms for efficiency39}40};4142check();43});44}4546/**47* Wait for a specific number of events of a given type48*49* @param threadManager - The thread manager to query50* @param threadId - Thread to check for events51* @param eventType - Type of event to wait for52* @param count - Number of events to wait for53* @param timeoutMs - Maximum time to wait (default 5000ms)54* @returns Promise resolving to all matching events once count is reached55*56* Example:57* // Wait for 2 AGENT_MESSAGE events (initial response + continuation)58* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);59*/60export function waitForEventCount(61threadManager: ThreadManager,62threadId: string,63eventType: LaceEventType,64count: number,65timeoutMs = 500066): Promise<LaceEvent[]> {67return new Promise((resolve, reject) => {68const startTime = Date.now();6970const check = () => {71const events = threadManager.getEvents(threadId);72const matchingEvents = events.filter((e) => e.type === eventType);7374if (matchingEvents.length >= count) {75resolve(matchingEvents);76} else if (Date.now() - startTime > timeoutMs) {77reject(78new Error(79`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`80)81);82} else {83setTimeout(check, 10);84}85};8687check();88});89}9091/**92* Wait for an event matching a custom predicate93* Useful when you need to check event data, not just type94*95* @param threadManager - The thread manager to query96* @param threadId - Thread to check for events97* @param predicate - Function that returns true when event matches98* @param description - Human-readable description for error messages99* @param timeoutMs - Maximum time to wait (default 5000ms)100* @returns Promise resolving to the first matching event101*102* Example:103* // Wait for TOOL_RESULT with specific ID104* await waitForEventMatch(105* threadManager,106* agentThreadId,107* (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123',108* 'TOOL_RESULT with id=call_123'109* );110*/111export function waitForEventMatch(112threadManager: ThreadManager,113threadId: string,114predicate: (event: LaceEvent) => boolean,115description: string,116timeoutMs = 5000117): Promise<LaceEvent> {118return new Promise((resolve, reject) => {119const startTime = Date.now();120121const check = () => {122const events = threadManager.getEvents(threadId);123const event = events.find(predicate);124125if (event) {126resolve(event);127} else if (Date.now() - startTime > timeoutMs) {128reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));129} else {130setTimeout(check, 10);131}132};133134check();135});136}137138// Usage example from actual debugging session:139//140// BEFORE (flaky):141// ---------------142// const messagePromise = agent.sendMessage('Execute tools');143// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms144// agent.abort();145// await messagePromise;146// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms147// expect(toolResults.length).toBe(2); // Fails randomly148//149// AFTER (reliable):150// ----------------151// const messagePromise = agent.sendMessage('Execute tools');152// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start153// agent.abort();154// await messagePromise;155// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results156// expect(toolResults.length).toBe(2); // Always succeeds157//158// Result: 60% pass rate → 100%, 40% faster execution159