tycono 0.1.91 → 0.1.92

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.91",
3
+ "version": "0.1.92",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -119,6 +119,13 @@ export function createHttpServer(): http.Server {
119
119
  const url = req.url ?? '';
120
120
  const method = req.method ?? '';
121
121
 
122
+ // GET /api/waves/active — restore active waves after refresh
123
+ if (url === '/api/waves/active' && method === 'GET') {
124
+ setExecCors(req, res);
125
+ handleExecRequest(req, res);
126
+ return;
127
+ }
128
+
122
129
  // SSE multiplexed wave stream (GET /api/waves/:waveId/stream)
123
130
  if (url.match(/^\/api\/waves\/[^/]+\/stream/) && method === 'GET') {
124
131
  setExecCors(req, res);
@@ -48,6 +48,12 @@ export function handleExecRequest(req: IncomingMessage, res: ServerResponse): vo
48
48
  return;
49
49
  }
50
50
 
51
+ // ── /api/waves/active — restore active waves after refresh ──
52
+ if (method === 'GET' && url === '/api/waves/active') {
53
+ jsonResponse(res, 200, { waves: waveMultiplexer.getActiveWaves() });
54
+ return;
55
+ }
56
+
51
57
  // ── /api/waves/save ──
52
58
  if (method === 'POST' && url === '/api/waves/save') {
53
59
  readBody(req).then((body) => handleSaveWave(body, res));
@@ -255,6 +255,60 @@ class WaveMultiplexer {
255
255
  const jobs = this.waveJobs.get(waveId);
256
256
  return jobs ? Array.from(jobs.keys()) : [];
257
257
  }
258
+
259
+ /**
260
+ * Return all waves that have at least one active (running/awaiting_input) job.
261
+ * Used to restore waveCenterWaves after page refresh.
262
+ */
263
+ getActiveWaves(): Array<{
264
+ id: string;
265
+ directive: string;
266
+ rootJobs: Array<{ sessionId: string; roleId: string; roleName: string; jobId: string }>;
267
+ startedAt: number;
268
+ sessionIds: string[];
269
+ }> {
270
+ const result: Array<{
271
+ id: string;
272
+ directive: string;
273
+ rootJobs: Array<{ sessionId: string; roleId: string; roleName: string; jobId: string }>;
274
+ startedAt: number;
275
+ sessionIds: string[];
276
+ }> = [];
277
+
278
+ for (const [waveId, jobs] of this.waveJobs) {
279
+ const hasActive = Array.from(jobs.values()).some(j => isJobActive(j.status));
280
+ if (!hasActive) continue;
281
+
282
+ // Extract wave info from root jobs (jobs without parentJobId in this wave)
283
+ const rootJobs = Array.from(jobs.values())
284
+ .filter(j => !j.parentJobId || !jobs.has(j.parentJobId))
285
+ .map(j => ({
286
+ sessionId: j.sessionId ?? '',
287
+ roleId: j.roleId,
288
+ roleName: j.roleId.toUpperCase(),
289
+ jobId: j.id,
290
+ }));
291
+
292
+ // Directive from first root job's task (strip "[CEO Wave] " prefix)
293
+ const firstJob = rootJobs.length > 0
294
+ ? Array.from(jobs.values()).find(j => j.id === rootJobs[0].jobId)
295
+ : undefined;
296
+ const directive = firstJob?.task.replace(/^\[CEO Wave\]\s*/, '') ?? '';
297
+
298
+ // StartedAt from earliest job
299
+ const startedAt = Math.min(
300
+ ...Array.from(jobs.values()).map(j => new Date(j.createdAt).getTime())
301
+ );
302
+
303
+ const sessionIds = Array.from(jobs.values())
304
+ .map(j => j.sessionId)
305
+ .filter((s): s is string => !!s);
306
+
307
+ result.push({ id: waveId, directive, rootJobs, startedAt, sessionIds });
308
+ }
309
+
310
+ return result;
311
+ }
258
312
  }
259
313
 
260
314
  /* ─── Helpers ────────────────────────────── */