tycono 0.1.94-beta.4 → 0.1.94-beta.5

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.94-beta.4",
3
+ "version": "0.1.94-beta.5",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -371,10 +371,9 @@ elif cmd == 'amend':
371
371
  log('Usage: supervision amend <sessionId> "<instruction>"')
372
372
  sys.exit(1)
373
373
 
374
- # Amend uses continue-session with amended context
374
+ # Amend sends a message to the session with amendment instructions
375
375
  body = json.dumps({
376
- 'response': f'[SUPERVISION AMENDMENT] {instruction}',
377
- 'responderRole': os.environ.get('DISPATCH_SOURCE_ROLE', 'ceo'),
376
+ 'content': f'[SUPERVISION AMENDMENT] {instruction}',
378
377
  }).encode()
379
378
 
380
379
  try:
@@ -310,12 +310,45 @@ function handleSaveWave(body: Record<string, unknown>, res: ServerResponse): voi
310
310
  let sessionIds = (body.sessionIds ?? body.jobIds) as string[] | undefined;
311
311
  const waveId = body.waveId as string | undefined;
312
312
 
313
- // BUG-W01 fix: auto-collect sessionIds from session-store when waveId is present
313
+ // BUG-W01 + BUG-009 fix: auto-collect sessionIds from session-store AND activity-streams
314
314
  if (waveId && (!sessionIds || sessionIds.length === 0)) {
315
- const allSessions = listSessions();
316
- sessionIds = allSessions
317
- .filter(s => s.waveId === waveId)
318
- .map(s => s.id);
315
+ const sessionIdSet = new Set(
316
+ listSessions().filter(s => s.waveId === waveId).map(s => s.id)
317
+ );
318
+
319
+ // Scan activity-streams for sessions belonging to this wave
320
+ const streamsDir = path.join(COMPANY_ROOT, 'operations', 'activity-streams');
321
+ if (fs.existsSync(streamsDir)) {
322
+ const waveTimestamp = waveId.replace('wave-', '');
323
+ for (const file of fs.readdirSync(streamsDir)) {
324
+ if (!file.endsWith('.jsonl')) continue;
325
+ const sid = file.replace('.jsonl', '');
326
+ if (sessionIdSet.has(sid)) continue;
327
+ if (sid.includes(waveTimestamp)) {
328
+ sessionIdSet.add(sid);
329
+ }
330
+ }
331
+
332
+ // Recursively find all child sessions via dispatch:start events
333
+ let foundNew = true;
334
+ while (foundNew) {
335
+ foundNew = false;
336
+ for (const sid of Array.from(sessionIdSet)) {
337
+ try {
338
+ const events = ActivityStream.readAll(sid);
339
+ for (const e of events) {
340
+ const childSessionId = e.data.childSessionId as string | undefined;
341
+ if (e.type === 'dispatch:start' && childSessionId && !sessionIdSet.has(childSessionId)) {
342
+ sessionIdSet.add(childSessionId);
343
+ foundNew = true;
344
+ }
345
+ }
346
+ } catch { /* skip */ }
347
+ }
348
+ }
349
+ }
350
+
351
+ sessionIds = Array.from(sessionIdSet);
319
352
  console.log(`[WaveSave] Auto-collected ${sessionIds.length} sessionIds for wave ${waveId}`);
320
353
  }
321
354
 
@@ -387,14 +420,45 @@ function handleSaveWave(body: Record<string, unknown>, res: ServerResponse): voi
387
420
  }
388
421
  const jsonPath = path.join(wavesDir, `${baseName}.json`);
389
422
 
423
+ // Calculate actual duration from activity stream timestamps
424
+ let startedAt = now;
425
+ let endedAt = now;
426
+ for (const role of rolesData) {
427
+ if (role.events.length > 0) {
428
+ const firstTs = new Date(role.events[0].ts);
429
+ const lastTs = new Date(role.events[role.events.length - 1].ts);
430
+ if (firstTs < startedAt) startedAt = firstTs;
431
+ if (lastTs > endedAt) endedAt = lastTs;
432
+ }
433
+ for (const child of role.childSessions) {
434
+ if (child.events.length > 0) {
435
+ const firstTs = new Date(child.events[0].ts);
436
+ const lastTs = new Date(child.events[child.events.length - 1].ts);
437
+ if (firstTs < startedAt) startedAt = firstTs;
438
+ if (lastTs > endedAt) endedAt = lastTs;
439
+ }
440
+ }
441
+ }
442
+ const duration = Math.round((endedAt.getTime() - startedAt.getTime()) / 1000);
443
+
444
+ // Collect ALL session IDs including child sessions
445
+ const allSessionIds = [...sessionIds];
446
+ for (const role of rolesData) {
447
+ for (const child of role.childSessions) {
448
+ if (!allSessionIds.includes(child.sessionId)) {
449
+ allSessionIds.push(child.sessionId);
450
+ }
451
+ }
452
+ }
453
+
390
454
  const waveJson = {
391
455
  id: baseName,
392
456
  directive,
393
- startedAt: now.toISOString(),
394
- duration: 0,
457
+ startedAt: startedAt.toISOString(),
458
+ duration,
395
459
  roles: rolesData,
396
460
  ...(waveId && { waveId }),
397
- ...(sessionIds.length > 0 && { sessionIds }),
461
+ sessionIds: allSessionIds,
398
462
  };
399
463
  fs.writeFileSync(jsonPath, JSON.stringify(waveJson, null, 2), 'utf-8');
400
464
 
@@ -101,7 +101,11 @@ function writeImmediate(session: Session): void {
101
101
  clearTimeout(timer);
102
102
  writeTimers.delete(session.id);
103
103
  }
104
- fs.writeFileSync(sessionPath(session.id), JSON.stringify(session, null, 2));
104
+ try {
105
+ fs.writeFileSync(sessionPath(session.id), JSON.stringify(session, null, 2));
106
+ } catch (err) {
107
+ console.error(`[SessionStore] WRITE FAILED for ${session.id}:`, err);
108
+ }
105
109
  }
106
110
 
107
111
  /* ─── In-memory cache ───────────────────── */
@@ -254,6 +258,7 @@ export function deleteSession(id: string): boolean {
254
258
  const session = cache.get(id);
255
259
  if (!session) return false;
256
260
 
261
+ console.log(`[SessionStore] Deleting session ${id} (roleId=${session.roleId}, waveId=${session.waveId ?? 'none'}, messages=${session.messages.length})`);
257
262
  cache.delete(id);
258
263
  const p = sessionPath(id);
259
264
  if (fs.existsSync(p)) fs.unlinkSync(p);
@@ -168,11 +168,51 @@ export function updateFollowUpInWave(waveId: string, sessionId: string, roleId:
168
168
  */
169
169
  export function saveCompletedWave(waveId: string, directive: string): { ok: boolean; path?: string } {
170
170
  try {
171
- // Collect all sessionIds for this wave from session-store
172
- const allSessions = listSessions();
173
- const sessionIds = allSessions
174
- .filter(s => s.waveId === waveId)
175
- .map(s => s.id);
171
+ // BUG-009 fix: collect sessions from BOTH session-store AND activity-streams.
172
+ // Session-store cache may miss the CEO supervisor session (BUG-008).
173
+ // Activity-streams on disk are the source of truth for what actually ran.
174
+ const sessionIdSet = new Set(
175
+ listSessions().filter(s => s.waveId === waveId).map(s => s.id)
176
+ );
177
+
178
+ // Scan activity-streams for ALL sessions belonging to this wave.
179
+ // Wave sessions share a traceId chain: CEO → C-Level → subordinates.
180
+ // We find the CEO session (waveId timestamp embedded in its ID), then follow dispatch:start events.
181
+ const streamsDir = path.join(COMPANY_ROOT, 'operations', 'activity-streams');
182
+ if (fs.existsSync(streamsDir)) {
183
+ // Find all activity stream files and check if they belong to this wave
184
+ const waveTimestamp = waveId.replace('wave-', '');
185
+ for (const file of fs.readdirSync(streamsDir)) {
186
+ if (!file.endsWith('.jsonl')) continue;
187
+ const sid = file.replace('.jsonl', '');
188
+ if (sessionIdSet.has(sid)) continue;
189
+ // Check if session ID contains the wave timestamp (CEO session)
190
+ // or if the session was dispatched from a known wave session
191
+ if (sid.includes(waveTimestamp)) {
192
+ sessionIdSet.add(sid);
193
+ }
194
+ }
195
+
196
+ // Now recursively find all child sessions via dispatch:start events
197
+ let foundNew = true;
198
+ while (foundNew) {
199
+ foundNew = false;
200
+ for (const sid of Array.from(sessionIdSet)) {
201
+ try {
202
+ const events = ActivityStream.readAll(sid);
203
+ for (const e of events) {
204
+ const childSessionId = e.data.childSessionId as string | undefined;
205
+ if (e.type === 'dispatch:start' && childSessionId && !sessionIdSet.has(childSessionId)) {
206
+ sessionIdSet.add(childSessionId);
207
+ foundNew = true;
208
+ }
209
+ }
210
+ } catch { /* skip */ }
211
+ }
212
+ }
213
+ }
214
+
215
+ const sessionIds = Array.from(sessionIdSet);
176
216
 
177
217
  if (sessionIds.length === 0) {
178
218
  console.warn(`[WaveTracker] No sessions found for wave ${waveId}, skipping save`);