From 4a347d0d47ca2427ea53ac2c27df8ee4d3b1e9dc Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 14 Sep 2026 14:17:02 -0700 Subject: [PATCH] test(runtime): pin delivery after late idle evidence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each case asserts delivery DOES happen once the evidence the edge was missing arrives: quiescence elapsing, a ready title after a name-only frame, and a done status after a vetoed idle title. Positive-after-the-edge is the assertion the earlier tests skipped — they only pinned the early negative. Refs #6011 --- .../tui-idle-delivery-and-quiescence.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/runtime/tui-idle-delivery-and-quiescence.test.ts b/src/main/runtime/tui-idle-delivery-and-quiescence.test.ts index 3d670e10028..97794c6200d 100644 --- a/src/main/runtime/tui-idle-delivery-and-quiescence.test.ts +++ b/src/main/runtime/tui-idle-delivery-and-quiescence.test.ts @@ -15,6 +15,8 @@ const PTY_ID = 'pty-followups' const ESC = String.fromCharCode(27) const BEL = String.fromCharCode(7) const osc = (title: string) => `${ESC}]0;${title}${BEL}` +const agentStatus = (state: string, agentType: string) => + `${ESC}]9999;{"state":"${state}","agentType":"${agentType}"}${BEL}` const GRAPH: RuntimeSyncWindowGraph = { tabs: [ @@ -115,6 +117,42 @@ describe('mailbox delivery honours the tui-idle evidence ranking', () => { expect(deliver).not.toHaveBeenCalled() }, 20_000) + // Case B, the mainline path: a hooked Codex emits a name-only frame BEFORE the hook's + // `Codex ready`. The name-only frame consumes the working->idle transition, leaving the + // ready title as an idle->idle step that delivery was never offered — so the strongest + // evidence the agent ever emits could not reach it. + it('delivers when the ready title arrives after a name-only frame', async () => { + const { runtime } = await makeRuntime('codex') + const deliver = watchDelivery(runtime) + runtime.onPtyData(PTY_ID, `${osc('\u280b Codex')}working\n`, Date.now()) + runtime.onPtyData(PTY_ID, `${osc('Codex')}out\n`, Date.now()) + expect(deliver).not.toHaveBeenCalled() + + await new Promise((resolve) => setTimeout(resolve, 100)) + runtime.onPtyData(PTY_ID, osc('Codex ready'), Date.now()) + // Promptly, on the ready title itself — not after waiting out a quiescence window. + expect(deliver).toHaveBeenCalled() + }, 20_000) + + // Case C: the agent's own status stream vetoes the idle title, then reports done with no + // edge behind it. `working` stays fresh for 30 minutes, so without a re-offer the veto + // outlives the turn it described. + it('delivers when a done status lands after the idle title was vetoed', async () => { + const { runtime } = await makeRuntime('claude') + const deliver = watchDelivery(runtime) + runtime.onPtyData( + PTY_ID, + `${agentStatus('working', 'claude')}${osc('\u280b Claude')}w\n`, + Date.now() + ) + runtime.onPtyData(PTY_ID, `${osc('claude')}out\n`, Date.now()) + expect(deliver).not.toHaveBeenCalled() + + runtime.onPtyData(PTY_ID, agentStatus('done', 'claude'), Date.now()) + await new Promise((resolve) => setTimeout(resolve, 4_500)) + expect(deliver).toHaveBeenCalled() + }, 20_000) + it('still delivers for an agent whose name is its only rest signal', async () => { const { runtime } = await makeRuntime('grok', 'grok') const deliver = watchDelivery(runtime)