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
|
@@ -371,10 +371,9 @@ elif cmd == 'amend':
|
|
|
371
371
|
log('Usage: supervision amend <sessionId> "<instruction>"')
|
|
372
372
|
sys.exit(1)
|
|
373
373
|
|
|
374
|
-
# Amend
|
|
374
|
+
# Amend sends a message to the session with amendment instructions
|
|
375
375
|
body = json.dumps({
|
|
376
|
-
'
|
|
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
|
|
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
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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:
|
|
394
|
-
duration
|
|
457
|
+
startedAt: startedAt.toISOString(),
|
|
458
|
+
duration,
|
|
395
459
|
roles: rolesData,
|
|
396
460
|
...(waveId && { waveId }),
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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`);
|