tycono 0.1.96-beta.9 → 0.1.97

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.
@@ -83,19 +83,30 @@ class WaveMultiplexer {
83
83
 
84
84
  const sessions = this.waveSessions.get(waveId);
85
85
  if (sessions) {
86
- // Phase 1: Replay all historical events sorted by timestamp
87
- const allEvents: { event: ActivityEvent; sessionId: string }[] = [];
86
+ // Phase 1: Replay recent historical events (capped to prevent OOM)
87
+ // Only replay from recent sessions (last 5) to avoid 120-session waves killing memory
88
+ const MAX_REPLAY_SESSIONS = 5;
89
+ const MAX_REPLAY_EVENTS = 200;
90
+
91
+ const sessionList = Array.from(sessions.values());
92
+ const recentSessions = sessionList.slice(-MAX_REPLAY_SESSIONS);
88
93
 
89
- for (const [, exec] of sessions) {
94
+ const allEvents: { event: ActivityEvent; sessionId: string }[] = [];
95
+ for (const exec of recentSessions) {
90
96
  const events = ActivityStream.readFrom(exec.sessionId, 0);
91
- for (const event of events) {
97
+ // Take last N events per session
98
+ const recent = events.slice(-50);
99
+ for (const event of recent) {
92
100
  allEvents.push({ event, sessionId: exec.sessionId });
93
101
  }
94
102
  }
95
103
 
96
104
  allEvents.sort((a, b) => a.event.ts.localeCompare(b.event.ts));
97
105
 
98
- for (const item of allEvents) {
106
+ // Cap total replay events
107
+ const replayEvents = allEvents.slice(-MAX_REPLAY_EVENTS);
108
+
109
+ for (const item of replayEvents) {
99
110
  const waveSeq = client.waveSeq++;
100
111
  if (waveSeq < fromWaveSeq) continue;
101
112
 
@@ -111,8 +122,10 @@ class WaveMultiplexer {
111
122
  } as WaveStreamEnvelope);
112
123
  }
113
124
 
125
+ console.log(`[WaveMux] Replayed ${replayEvents.length} events (${sessionList.length} total sessions, ${recentSessions.length} replayed)`);
126
+
114
127
  // Phase 2: Subscribe to live events for active sessions
115
- for (const [, exec] of sessions) {
128
+ for (const exec of sessionList) {
116
129
  if (exec.status === 'running' || exec.status === 'awaiting_input') {
117
130
  this.subscribeSessionToClient(waveId, client, exec, true);
118
131
  }
@@ -137,7 +150,8 @@ class WaveMultiplexer {
137
150
  });
138
151
 
139
152
  const events = ActivityStream.readFrom(execution.sessionId, 0);
140
- for (const event of events) {
153
+ const recentEvents = events.slice(-50); // Cap replay per session
154
+ for (const event of recentEvents) {
141
155
  const key = `${event.roleId}:${event.seq}`;
142
156
  if (client.sentEvents.has(key)) continue;
143
157
  client.sentEvents.add(key);
package/src/tui/api.ts CHANGED
@@ -52,6 +52,9 @@ export async function fetchJson<T>(path: string, options?: { method?: string; bo
52
52
  },
53
53
  );
54
54
  req.on('error', reject);
55
+ req.setTimeout(10_000, () => {
56
+ req.destroy(new Error(`Timeout: ${path}`));
57
+ });
55
58
  if (bodyStr) req.write(bodyStr);
56
59
  req.end();
57
60
  });
@@ -83,6 +86,7 @@ export interface SessionInfo {
83
86
  status: string;
84
87
  source: string;
85
88
  waveId?: string;
89
+ parentSessionId?: string;
86
90
  createdAt: string;
87
91
  }
88
92
 
@@ -129,7 +133,7 @@ export async function fetchExecStatus(): Promise<ExecStatus> {
129
133
  return fetchJson<ExecStatus>('/api/exec/status');
130
134
  }
131
135
 
132
- export async function dispatchWave(directive: string, options?: {
136
+ export async function dispatchWave(directive?: string, options?: {
133
137
  targetRoles?: string[];
134
138
  continuous?: boolean;
135
139
  }): Promise<WaveResponse> {
@@ -137,7 +141,7 @@ export async function dispatchWave(directive: string, options?: {
137
141
  method: 'POST',
138
142
  body: {
139
143
  type: 'wave',
140
- directive,
144
+ directive: directive ?? '',
141
145
  targetRoles: options?.targetRoles,
142
146
  continuous: options?.continuous ?? false,
143
147
  },
@@ -155,6 +159,61 @@ export async function fetchActiveWaves(): Promise<{ waves: Array<{ waveId: strin
155
159
  return fetchJson('/api/waves/active');
156
160
  }
157
161
 
162
+ /* ─── Active Sessions (port/worktree visibility) ─── */
163
+
164
+ export interface ActiveSessionInfo {
165
+ sessionId: string;
166
+ roleId: string;
167
+ task: string;
168
+ ports: { api: number; vite: number; hmr?: number };
169
+ worktreePath?: string;
170
+ pid?: number;
171
+ startedAt: string;
172
+ status: 'active' | 'idle' | 'dead';
173
+ waveId?: string | null;
174
+ messageStatus?: string | null;
175
+ alive?: boolean | null;
176
+ }
177
+
178
+ export interface ActiveSessionsResponse {
179
+ sessions: ActiveSessionInfo[];
180
+ summary: { active: number; totalPorts: number };
181
+ }
182
+
183
+ export async function fetchActiveSessions(): Promise<ActiveSessionsResponse> {
184
+ return fetchJson<ActiveSessionsResponse>('/api/active-sessions');
185
+ }
186
+
187
+ export async function killSession(sessionId: string): Promise<{ ok: boolean }> {
188
+ return fetchJson<{ ok: boolean }>(`/api/active-sessions/${sessionId}`, { method: 'DELETE' });
189
+ }
190
+
191
+ export async function cleanupSessions(): Promise<{ cleaned: number; remaining: number }> {
192
+ return fetchJson<{ cleaned: number; remaining: number }>('/api/active-sessions/cleanup', { method: 'POST' });
193
+ }
194
+
195
+ /* ─── Knowledge docs ─── */
196
+
197
+ export interface KnowledgeDoc {
198
+ id: string;
199
+ title: string;
200
+ path: string;
201
+ type?: string;
202
+ domain?: string;
203
+ status?: string;
204
+ updatedAt?: string;
205
+ }
206
+
207
+ export async function fetchKnowledgeDocs(): Promise<KnowledgeDoc[]> {
208
+ return fetchJson<KnowledgeDoc[]>('/api/knowledge');
209
+ }
210
+
211
+ /** Scan COMPANY_ROOT for all .md files (not API-dependent) */
212
+ export async function fetchCompanyRoot(): Promise<string> {
213
+ const health = await fetchJson<{ companyRoot: string }>('/api/health');
214
+ return health.companyRoot;
215
+ }
216
+
158
217
  /* ─── Setup API calls ─── */
159
218
 
160
219
  export interface TeamTemplate {