diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 8ca75be382e..b20bdfa034a 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -123,6 +123,7 @@ "file": "src/shared/agent-status-osc.test.ts", "assertions": [ "uses the earliest mixed terminator and counts only parsed payload offsets", + "keeps a distant ST usable after many intervening BEL frames", "preserves every split of prefixes, JSON, and both terminators across independent streams", "applies the pending cap only to incomplete frames" ] diff --git a/src/shared/agent-status-osc.test.ts b/src/shared/agent-status-osc.test.ts index dc143cedae7..065721fbce7 100644 --- a/src/shared/agent-status-osc.test.ts +++ b/src/shared/agent-status-osc.test.ts @@ -134,6 +134,26 @@ describe('createAgentStatusOscProcessor', () => { } }) + it('keeps a distant ST usable after many intervening BEL frames', () => { + const count = 200 + const bel = Array.from( + { length: count }, + (_, index) => `\x1b]9999;{"state":"working","prompt":"${index}"}\x07` + ).join('') + const result = createAgentStatusOscProcessor()( + `${bel}\x1b]9999;{"state":"done","prompt":"last"}\x1b\\tail` + ) + + expect(result.payloads).toEqual([ + ...Array.from({ length: count }, (_, index) => ({ + state: 'working', + prompt: String(index) + })), + { state: 'done', prompt: 'last' } + ]) + expect(result.cleanData).toBe('tail') + }) + it('applies the pending cap only to incomplete frames', () => { const marker = '\x1b]9999;{"state":"working"}' const atCap = marker + ' '.repeat(64 * 1024 - marker.length) diff --git a/src/shared/agent-status-osc.ts b/src/shared/agent-status-osc.ts index fdb183f442c..da1610fc1c4 100644 --- a/src/shared/agent-status-osc.ts +++ b/src/shared/agent-status-osc.ts @@ -31,6 +31,8 @@ function findAgentStatusTerminator( next: { belIndex: number; stIndex: number } ): { index: number; length: 1 | 2 } | null { // Reuse forward matches, including absence, for this chunk's remaining frames. + // Requires `searchFrom` to increase on every call for one `data`; a rewind would + // reuse a match that is no longer the earliest. if (next.belIndex !== -1 && next.belIndex < searchFrom) { next.belIndex = data.indexOf('\x07', searchFrom) }