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.
- package/bin/tycono.ts +62 -27
- package/package.json +1 -1
- package/src/api/src/create-server.ts +5 -1
- package/src/api/src/routes/active-sessions.ts +3 -0
- package/src/api/src/routes/execute.ts +30 -41
- package/src/api/src/server.ts +44 -0
- package/src/api/src/services/execution-manager.ts +7 -1
- package/src/api/src/services/supervisor-heartbeat.ts +186 -11
- package/src/api/src/services/wave-multiplexer.ts +21 -7
- package/src/tui/api.ts +61 -2
- package/src/tui/app.tsx +455 -101
- package/src/tui/components/CommandMode.tsx +206 -86
- package/src/tui/components/OrgTree.tsx +17 -18
- package/src/tui/components/PanelMode.tsx +477 -41
- package/src/tui/components/SetupWizard.tsx +22 -7
- package/src/tui/components/StatusBar.tsx +44 -25
- package/src/tui/components/StreamView.tsx +27 -16
- package/src/tui/hooks/useApi.ts +42 -7
- package/src/tui/hooks/useCommand.ts +143 -131
- package/src/tui/hooks/useSSE.ts +130 -27
- package/src/tui/store.ts +12 -0
- package/src/tui/utils/markdown.tsx +102 -0
|
@@ -83,19 +83,30 @@ class WaveMultiplexer {
|
|
|
83
83
|
|
|
84
84
|
const sessions = this.waveSessions.get(waveId);
|
|
85
85
|
if (sessions) {
|
|
86
|
-
// Phase 1: Replay
|
|
87
|
-
|
|
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
|
-
|
|
94
|
+
const allEvents: { event: ActivityEvent; sessionId: string }[] = [];
|
|
95
|
+
for (const exec of recentSessions) {
|
|
90
96
|
const events = ActivityStream.readFrom(exec.sessionId, 0);
|
|
91
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 {
|