|
1 | 1 | import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; |
2 | | -import { parseStreamChunk } from '@/streaming/stream-parser'; |
3 | 2 |
|
4 | 3 | interface PipelineExecution { |
5 | 4 | id: string; |
@@ -82,60 +81,37 @@ export function usePipelineExecution(runId: string | null): UsePipelineExecution |
82 | 81 | if (!reader) return; |
83 | 82 | const decoder = new TextDecoder(); |
84 | 83 | let buffer = ''; |
| 84 | + let liveSeq = 0; |
85 | 85 | for (;;) { |
86 | 86 | const { done, value } = await reader.read(); |
87 | 87 | if (done) break; |
88 | 88 | buffer += decoder.decode(value, { stream: true }); |
89 | 89 | const parts = buffer.split('\n\n'); |
90 | 90 | buffer = parts.pop() || ''; |
91 | 91 | for (const chunk of parts) { |
92 | | - const parsed = parseStreamChunk(chunk + '\n'); |
93 | | - if (parsed.type === 'work-update' && parsed.update) { |
94 | | - recordWorkUpdate(parsed); |
95 | | - setEvents(prev => prev.concat([{ id: `${Date.now()}:work-update`, event: parsed, created_at: new Date().toISOString() }])); |
96 | | - continue; |
97 | | - } |
98 | | - if (parsed.status) { |
99 | | - const ev = { type: 'status', status: parsed.status }; |
100 | | - setEvents(prev => prev.concat([{ id: `${Date.now()}:status`, event: ev, created_at: new Date().toISOString() }])); |
101 | | - } |
102 | | - if (parsed.text && !parsed.status && !parsed.error && !parsed.completion) { |
103 | | - const ev = { type: 'message', message: parsed.text } as any; |
104 | | - setEvents(prev => prev.concat([{ id: `${Date.now()}:msg`, event: ev, created_at: new Date().toISOString() }])); |
105 | | - } |
106 | | - if (parsed.error) { |
107 | | - const ev = { type: 'error', message: parsed.error } as any; |
108 | | - setEvents(prev => prev.concat([{ id: `${Date.now()}:err`, event: ev, created_at: new Date().toISOString() }])); |
109 | | - } |
110 | | - if ((parsed as any).type) { |
111 | | - const ev = (parsed as any).event || parsed; |
112 | | - if (ev?.type === 'work-update') { |
113 | | - recordWorkUpdate(ev); |
114 | | - setEvents(prev => prev.concat([{ id: `${Date.now()}:work-update`, event: ev, created_at: new Date().toISOString() }])); |
115 | | - continue; |
116 | | - } |
117 | | - if (ev) { |
118 | | - setEvents(prev => prev.concat([{ id: `${Date.now()}:ev`, event: ev, created_at: new Date().toISOString() }])); |
119 | | - } |
120 | | - } |
| 92 | + // The live SSE tail relays raw `event_data` — the structured onStore |
| 93 | + // event with namespace/key/executionState intact (already source-safe |
| 94 | + // filtered server-side). Push it verbatim, identical in shape to the |
| 95 | + // history path, so the activity builder classifies live events the |
| 96 | + // same way and the formal log-line contract (F19: only LLM calls + |
| 97 | + // Tool uses become rows) holds during streaming, not just on reload. |
| 98 | + // Re-parsing through parseStreamChunk here used to flatten events into |
| 99 | + // namespace-less `status`/`message` shapes, which leaked every |
| 100 | + // intermediate store as a fragment row. |
121 | 101 | const lines = chunk.split('\n').filter((l) => l.startsWith('data: ')).map((l) => l.substring(6)); |
122 | 102 | for (const line of lines) { |
| 103 | + let payload: any; |
123 | 104 | try { |
124 | | - const payload = JSON.parse(line); |
125 | | - if (payload?.type === 'work-update') { |
126 | | - recordWorkUpdate(payload); |
127 | | - } |
128 | | - if ( |
129 | | - payload && |
130 | | - (payload.type === 'pipeline' || |
131 | | - payload.type === 'phase' || |
132 | | - payload.type === 'agent' || |
133 | | - payload.type === 'completion' || |
134 | | - payload.type === 'error') |
135 | | - ) { |
136 | | - setEvents(prev => prev.concat([{ id: `${Date.now()}:ev`, event: payload, created_at: new Date().toISOString() }])); |
137 | | - } |
138 | | - } catch {} |
| 105 | + payload = JSON.parse(line); |
| 106 | + } catch { |
| 107 | + continue; |
| 108 | + } |
| 109 | + if (!payload || typeof payload !== 'object') continue; |
| 110 | + if (payload.type === 'work-update') { |
| 111 | + recordWorkUpdate(payload); |
| 112 | + } |
| 113 | + const createdAt = typeof payload.timestamp === 'string' ? payload.timestamp : new Date().toISOString(); |
| 114 | + setEvents(prev => prev.concat([{ id: `live:${Date.now()}:${liveSeq++}`, event: payload, created_at: createdAt }])); |
139 | 115 | } |
140 | 116 | } |
141 | 117 | } |
|
0 commit comments