tycono 0.1.96-beta.40 → 0.1.96-beta.41

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.40",
3
+ "version": "0.1.96-beta.41",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,25 +51,21 @@ export function handleExecRequest(req: IncomingMessage, res: ServerResponse): vo
51
51
  // ── /api/waves/active — restore active waves after refresh ──
52
52
  if (method === 'GET' && url === '/api/waves/active') {
53
53
  // Recovery: rebuild wave→session mapping from session-store if lost
54
+ // Only recover ACTIVE sessions to prevent OOM on large datasets (140+ sessions)
54
55
  const waves = waveMultiplexer.getActiveWaves();
55
56
  if (waves.length === 0) {
56
57
  const allSessions = listSessions();
57
- const waveGroups = new Map<string, string[]>();
58
+ let recovered = 0;
58
59
  for (const ses of allSessions) {
59
- if (ses.waveId) {
60
- if (!waveGroups.has(ses.waveId)) waveGroups.set(ses.waveId, []);
61
- waveGroups.get(ses.waveId)!.push(ses.id);
60
+ if (!ses.waveId || ses.status !== 'active') continue;
61
+ const exec = executionManager.getActiveExecution(ses.id);
62
+ if (exec) {
63
+ waveMultiplexer.registerSession(ses.waveId, exec);
64
+ recovered++;
62
65
  }
63
66
  }
64
- // BUG-W03 fix: register ALL wave sessions (including completed) for tree display
65
- for (const [wid, sids] of waveGroups) {
66
- for (const sid of sids) {
67
- // getActiveExecution falls back to stream recovery for completed sessions
68
- const exec = executionManager.getActiveExecution(sid);
69
- if (exec) {
70
- waveMultiplexer.registerSession(wid, exec);
71
- }
72
- }
67
+ if (recovered > 0) {
68
+ console.log(`[WaveRecovery] Recovered ${recovered} active sessions`);
73
69
  }
74
70
  }
75
71
  jsonResponse(res, 200, { waves: waveMultiplexer.getActiveWaves() });
@@ -480,13 +476,11 @@ function handleWaveStream(waveId: string, url: string, res: ServerResponse, req:
480
476
 
481
477
  let sessionIds = waveMultiplexer.getWaveSessionIds(waveId);
482
478
 
483
- // Recovery: if wave→session mapping was lost (e.g. server restart),
484
- // rebuild from session-store (sessions have waveId) + executionManager
479
+ // Recovery: only recover ACTIVE sessions for this wave (not all 140)
485
480
  if (sessionIds.length === 0) {
486
481
  const allSessions = listSessions();
487
- const waveSessions = allSessions.filter(s => s.waveId === waveId);
482
+ const waveSessions = allSessions.filter(s => s.waveId === waveId && s.status === 'active');
488
483
  for (const ses of waveSessions) {
489
- // getActiveExecution recovers from stream files for completed sessions too
490
484
  const exec = executionManager.getActiveExecution(ses.id);
491
485
  if (exec) {
492
486
  waveMultiplexer.registerSession(waveId, exec);
@@ -763,25 +757,26 @@ function handleStatus(res: ServerResponse): void {
763
757
  );
764
758
 
765
759
  const recovered: typeof activeExecs = [];
766
- for (const ses of activeSessions) {
767
- // Check activity-stream for actual running state
760
+ // Limit recovery scan to prevent OOM on large session stores
761
+ const MAX_RECOVERY_SCAN = 20;
762
+ const recentActive = activeSessions.slice(-MAX_RECOVERY_SCAN);
763
+
764
+ for (const ses of recentActive) {
768
765
  if (!ActivityStream.exists(ses.id)) continue;
766
+ // Only read last few events to check done/error (not entire stream)
769
767
  const events = ActivityStream.readFrom(ses.id, 0);
770
768
  if (events.length === 0) continue;
771
769
 
772
- const startEvent = events.find(e => e.type === 'msg:start');
773
- if (!startEvent) continue;
774
-
775
- const doneEvent = events.find(e => e.type === 'msg:done');
776
- const errorEvent = events.find(e => e.type === 'msg:error');
777
-
778
- // Only include sessions that haven't finished yet
779
- if (doneEvent || errorEvent) continue;
770
+ // Check last 5 events for done/error (optimization: don't scan entire file)
771
+ const tail = events.slice(-5);
772
+ const isDone = tail.some(e => e.type === 'msg:done' || e.type === 'msg:error');
773
+ if (isDone) continue;
780
774
 
781
- const task = (startEvent.data?.task as string) ?? ses.title ?? '';
775
+ const startEvent = events.find(e => e.type === 'msg:start');
776
+ const task = (startEvent?.data?.task as string) ?? ses.title ?? '';
782
777
  recovered.push({
783
778
  id: `recovered-${ses.id}`,
784
- type: (startEvent.data?.type as string ?? 'assign') as 'assign' | 'wave' | 'consult',
779
+ type: (startEvent?.data?.type as string ?? 'assign') as 'assign' | 'wave' | 'consult',
785
780
  roleId: ses.roleId,
786
781
  task,
787
782
  status: 'running',