tycono 0.1.96-beta.20 → 0.1.96-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/src/routes/execute.ts +2 -6
- package/src/tui/hooks/useSSE.ts +68 -24
package/package.json
CHANGED
|
@@ -495,12 +495,8 @@ function handleWaveStream(waveId: string, url: string, res: ServerResponse, req:
|
|
|
495
495
|
sessionIds = waveMultiplexer.getWaveSessionIds(waveId);
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
res.end(JSON.stringify({ error: `No sessions found for wave: ${waveId}` }));
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
|
|
498
|
+
// Don't 404 on empty waves — keep SSE alive, sessions will appear later
|
|
499
|
+
// (e.g. idle wave waiting for first directive, or supervisor restarting)
|
|
504
500
|
const client = waveMultiplexer.attach(waveId, res as any, fromWaveSeq);
|
|
505
501
|
|
|
506
502
|
req.on('close', () => {
|
package/src/tui/hooks/useSSE.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* useSSE — subscribe to wave SSE stream
|
|
2
|
+
* useSSE — subscribe to wave SSE stream with auto-reconnect
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
6
6
|
import { subscribeToWaveStream, type SSEEvent, type SSEConnection } from '../api';
|
|
7
7
|
|
|
8
8
|
const MAX_EVENTS = 500;
|
|
9
|
+
const RECONNECT_DELAY_MS = 3000;
|
|
10
|
+
const MAX_RECONNECT_DELAY_MS = 15000;
|
|
9
11
|
|
|
10
12
|
export interface SSEState {
|
|
11
13
|
events: SSEEvent[];
|
|
@@ -18,9 +20,13 @@ export function useSSE(waveId: string | null): SSEState {
|
|
|
18
20
|
const [streamStatus, setStreamStatus] = useState<'idle' | 'streaming' | 'done' | 'error'>('idle');
|
|
19
21
|
const connRef = useRef<SSEConnection | null>(null);
|
|
20
22
|
const waveIdRef = useRef<string | null>(null);
|
|
23
|
+
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
24
|
+
const reconnectAttemptRef = useRef(0);
|
|
25
|
+
const maxSeqRef = useRef(0);
|
|
21
26
|
|
|
22
27
|
const clearEvents = useCallback(() => {
|
|
23
28
|
setEvents([]);
|
|
29
|
+
maxSeqRef.current = 0;
|
|
24
30
|
}, []);
|
|
25
31
|
|
|
26
32
|
useEffect(() => {
|
|
@@ -29,6 +35,11 @@ export function useSSE(waveId: string | null): SSEState {
|
|
|
29
35
|
connRef.current?.close();
|
|
30
36
|
connRef.current = null;
|
|
31
37
|
waveIdRef.current = waveId;
|
|
38
|
+
reconnectAttemptRef.current = 0;
|
|
39
|
+
if (reconnectTimerRef.current) {
|
|
40
|
+
clearTimeout(reconnectTimerRef.current);
|
|
41
|
+
reconnectTimerRef.current = null;
|
|
42
|
+
}
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
if (!waveId) {
|
|
@@ -36,32 +47,65 @@ export function useSSE(waveId: string | null): SSEState {
|
|
|
36
47
|
return;
|
|
37
48
|
}
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
const connect = (fromSeq: number) => {
|
|
51
|
+
setStreamStatus('streaming');
|
|
52
|
+
|
|
53
|
+
// Only clear events on first connect (not reconnects)
|
|
54
|
+
if (fromSeq === 0) {
|
|
55
|
+
setEvents([]);
|
|
56
|
+
maxSeqRef.current = 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const conn = subscribeToWaveStream(
|
|
60
|
+
waveId,
|
|
61
|
+
(event) => {
|
|
62
|
+
// Track max seq for reconnect resume
|
|
63
|
+
if (event.seq !== undefined && event.seq > maxSeqRef.current) {
|
|
64
|
+
maxSeqRef.current = event.seq;
|
|
65
|
+
}
|
|
66
|
+
reconnectAttemptRef.current = 0; // Reset on successful event
|
|
67
|
+
|
|
68
|
+
setEvents((prev) => {
|
|
69
|
+
const next = [...prev, event];
|
|
70
|
+
return next.length > MAX_EVENTS ? next.slice(-MAX_EVENTS) : next;
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
(reason) => {
|
|
74
|
+
if (reason === 'done') {
|
|
75
|
+
setStreamStatus('done');
|
|
76
|
+
} else {
|
|
77
|
+
// Disconnected or error → auto-reconnect
|
|
78
|
+
const attempt = reconnectAttemptRef.current++;
|
|
79
|
+
const delay = Math.min(
|
|
80
|
+
RECONNECT_DELAY_MS * Math.pow(1.5, attempt),
|
|
81
|
+
MAX_RECONNECT_DELAY_MS,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
setStreamStatus('streaming'); // Keep showing streaming during reconnect
|
|
41
85
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
connRef.current = conn;
|
|
86
|
+
reconnectTimerRef.current = setTimeout(() => {
|
|
87
|
+
reconnectTimerRef.current = null;
|
|
88
|
+
if (waveIdRef.current === waveId) {
|
|
89
|
+
connect(maxSeqRef.current);
|
|
90
|
+
}
|
|
91
|
+
}, delay);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
fromSeq,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
connRef.current = conn;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
connect(0);
|
|
62
101
|
|
|
63
102
|
return () => {
|
|
64
|
-
|
|
103
|
+
connRef.current?.close();
|
|
104
|
+
connRef.current = null;
|
|
105
|
+
if (reconnectTimerRef.current) {
|
|
106
|
+
clearTimeout(reconnectTimerRef.current);
|
|
107
|
+
reconnectTimerRef.current = null;
|
|
108
|
+
}
|
|
65
109
|
};
|
|
66
110
|
}, [waveId]);
|
|
67
111
|
|