|
8 | 8 | acceptSnapshot, |
9 | 9 | applyProjectionEvent, |
10 | 10 | beginInteraction, |
| 11 | + beginSnapshot, |
11 | 12 | createChatProjection, |
12 | 13 | deriveStatus, |
13 | 14 | failInteraction, |
@@ -90,6 +91,50 @@ describe('chat projection', () => { |
90 | 91 | }).cursor, |
91 | 92 | ).toBe(2); |
92 | 93 | }); |
| 94 | + it('does not let a stale snapshot cut roll back sequenced timeline state', () => { |
| 95 | + const base = acceptSnapshot(createChatProjection('s'), { |
| 96 | + ...snapshot(2), |
| 97 | + activeTurnId: 'turn-1', |
| 98 | + items: [], |
| 99 | + interactions: [], |
| 100 | + }); |
| 101 | + const final = applyProjectionEvent(base, { |
| 102 | + sequence: 3, |
| 103 | + type: 'agentMessageDelta', |
| 104 | + payload: { text: 'recovered final', phase: 'final_answer' }, |
| 105 | + }); |
| 106 | + const interaction = applyProjectionEvent(final, { |
| 107 | + sequence: 4, |
| 108 | + type: 'interaction.requested', |
| 109 | + payload: { requestId: 'request-1', kind: 'commandApproval', turnId: 'turn-1', payload: {} }, |
| 110 | + }); |
| 111 | + const activity = applyProjectionEvent(interaction, { |
| 112 | + sequence: 5, |
| 113 | + type: 'activity.updated', |
| 114 | + payload: { id: 'activity-1', label: 'Tool', detail: 'Finished' }, |
| 115 | + }); |
| 116 | + |
| 117 | + const retained = acceptSnapshot(beginSnapshot(activity), { |
| 118 | + ...snapshot(2), |
| 119 | + activeTurnId: null, |
| 120 | + items: [], |
| 121 | + interactions: [], |
| 122 | + }); |
| 123 | + |
| 124 | + expect(retained).toMatchObject({ |
| 125 | + cursor: 5, |
| 126 | + snapshotting: false, |
| 127 | + activeTurnId: 'turn-1', |
| 128 | + lifecycle: 'working', |
| 129 | + }); |
| 130 | + expect(retained.messages).toEqual([ |
| 131 | + expect.objectContaining({ text: 'recovered final', phase: 'final_answer', complete: true }), |
| 132 | + ]); |
| 133 | + expect(retained.interactions).toEqual([ |
| 134 | + expect.objectContaining({ requestId: 'request-1', state: 'pending' }), |
| 135 | + ]); |
| 136 | + expect(retained.activities).toEqual([{ id: 'activity-1', label: 'Tool', detail: 'Finished' }]); |
| 137 | + }); |
93 | 138 | it('does not promote duplicate identical optimistic prompts by ambiguous text', () => { |
94 | 139 | const projection = queuePrompt( |
95 | 140 | queuePrompt(createChatProjection('s'), 'one', 'same'), |
|
0 commit comments