diff --git a/src/services/AgentSummary/__tests__/agentSummary.test.ts b/src/services/AgentSummary/__tests__/agentSummary.test.ts index 4c8abe185..e27b36593 100644 --- a/src/services/AgentSummary/__tests__/agentSummary.test.ts +++ b/src/services/AgentSummary/__tests__/agentSummary.test.ts @@ -161,7 +161,9 @@ describe('startAgentSummarization', () => { expect(forkCalls).toEqual([]) expect(updateCalls).toEqual([]) - expectDebugLogContaining('no bounded context available') + expectDebugLogContaining( + '[AgentSummary] Skipping summary for task-1: no bounded context available', + ) }) test('skips summarization before building context when transcript is too short', async () => { @@ -173,7 +175,9 @@ describe('startAgentSummarization', () => { expect(forkCalls).toEqual([]) expect(updateCalls).toEqual([]) - expectDebugLogContaining('not enough messages (2)') + expectDebugLogContaining( + '[AgentSummary] Skipping summary for task-1: not enough messages (2)', + ) }) test('skips and reschedules while poor mode is active', async () => { @@ -188,7 +192,7 @@ describe('startAgentSummarization', () => { expect(forkCalls).toEqual([]) expect(updateCalls).toEqual([]) - expectDebugLogContaining('poor mode active') + expectDebugLogContaining('[AgentSummary] Skipping summary — poor mode active') expect(scheduledCount).toBe(initialScheduledCount + 1) expect(lastTimerHandle).not.toBe(initialTimerHandle) }) @@ -218,7 +222,7 @@ describe('startAgentSummarization', () => { handle.stop() - expectDebugLogContaining('Stopping summarization for task-1') + expectDebugLogContaining('[AgentSummary] Stopping summarization for task-1') expect(clearedHandles).toEqual([pendingHandle]) }) }) diff --git a/src/utils/__tests__/udsMessaging.test.ts b/src/utils/__tests__/udsMessaging.test.ts index a4c49cee3..5a81916c6 100644 --- a/src/utils/__tests__/udsMessaging.test.ts +++ b/src/utils/__tests__/udsMessaging.test.ts @@ -307,7 +307,9 @@ describe('UDS inbox retention', () => { '../udsClient.js' ) - const error = await connectToPeer(path).then( + const error = await connectToPeer(path, () => { + throw new Error('Unexpected post-connect socket error') + }).then( () => undefined, err => err, ) @@ -338,13 +340,25 @@ describe('UDS inbox retention', () => { }) let client: Socket | undefined + const socketErrors: Error[] = [] try { const { connectToPeer } = await import('../udsClient.js') - client = await connectToPeer(path, 1000) + client = await connectToPeer( + path, + error => { + socketErrors.push(error) + }, + 1000, + ) await new Promise(resolve => setTimeout(resolve, 100)) expect(client.destroyed).toBe(false) - expect(client.listenerCount('error')).toBe(0) + expect(client.listenerCount('error')).toBe(1) + expect(client.listenerCount('timeout')).toBe(0) + + const socketError = new Error('post-connect failure') + client.emit('error', socketError) + expect(socketErrors).toEqual([socketError]) } finally { client?.destroy() for (const socket of sockets) { diff --git a/src/utils/udsClient.ts b/src/utils/udsClient.ts index 1f1a6eee8..c282c2326 100644 --- a/src/utils/udsClient.ts +++ b/src/utils/udsClient.ts @@ -266,36 +266,49 @@ export async function sendToUdsSocket( /** * Connect to a peer and return the raw socket for bidirectional communication. - * The caller is responsible for managing the connection lifecycle, including - * attaching an 'error' listener immediately after the Promise resolves. This - * function detaches its internal error listener on successful connect so - * caller-owned socket errors are not silently swallowed. + * The caller owns the post-connect lifecycle through onSocketError, which is + * attached before the Promise resolves so peer socket errors cannot be + * swallowed or surface through a listener handoff window. + * Pre-connect failures reject with UdsPeerConnectionError. + * This only opens the transport; callers still own any capability handshake. */ export function connectToPeer( socketPath: string, + onSocketError: (error: Error) => void, timeoutMs = 5000, ): Promise { return new Promise((resolve, reject) => { const conn = createConnection(socketPath) let settled = false - const fail = (cause: unknown) => { + const onTimeout = () => { + fail(new Error('Connection timed out')) + } + function cleanupListeners(): void { + conn.setTimeout(0) + conn.off('error', fail) + conn.off('timeout', onTimeout) + } + function fail(cause: unknown): void { if (settled) { return } settled = true + cleanupListeners() conn.destroy() reject(new UdsPeerConnectionError(socketPath, cause)) } conn.once('connect', () => { + if (settled) { + return + } settled = true - conn.setTimeout(0) - conn.off('error', fail) + cleanupListeners() + conn.on('error', onSocketError) resolve(conn) }) conn.on('error', fail) - conn.setTimeout(timeoutMs, () => { - fail(new Error('Connection timed out')) - }) + conn.once('timeout', onTimeout) + conn.setTimeout(timeoutMs) }) }