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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.1.96-beta.20",
3
+ "version": "0.1.96-beta.21",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -495,12 +495,8 @@ function handleWaveStream(waveId: string, url: string, res: ServerResponse, req:
495
495
  sessionIds = waveMultiplexer.getWaveSessionIds(waveId);
496
496
  }
497
497
 
498
- if (sessionIds.length === 0) {
499
- res.writeHead(404, { 'Content-Type': 'application/json' });
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', () => {
@@ -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
- setStreamStatus('streaming');
40
- setEvents([]);
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
- const conn = subscribeToWaveStream(
43
- waveId,
44
- (event) => {
45
- setEvents((prev) => {
46
- const next = [...prev, event];
47
- return next.length > MAX_EVENTS ? next.slice(-MAX_EVENTS) : next;
48
- });
49
- },
50
- (reason) => {
51
- if (reason === 'done') {
52
- setStreamStatus('done');
53
- } else if (reason === 'error') {
54
- setStreamStatus('error');
55
- } else {
56
- setStreamStatus('done');
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
- conn.close();
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