tycono 0.1.107 → 0.1.108
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
|
@@ -283,18 +283,72 @@ class SupervisorHeartbeat {
|
|
|
283
283
|
* Spawn a lightweight conversation session (no dispatch tools).
|
|
284
284
|
* CEO reads files and answers directly.
|
|
285
285
|
*/
|
|
286
|
+
/**
|
|
287
|
+
* Load conversation history from activity-stream files for a wave.
|
|
288
|
+
* Used when supervisor restarts (e.g., TUI restarted) to restore context.
|
|
289
|
+
*/
|
|
290
|
+
private loadWaveHistory(waveId: string): string {
|
|
291
|
+
try {
|
|
292
|
+
// Find CEO sessions for this wave from session-store
|
|
293
|
+
const allSessions = listSessions();
|
|
294
|
+
const waveCeoSessions = allSessions
|
|
295
|
+
.filter(s => s.waveId === waveId && s.roleId === 'ceo')
|
|
296
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
297
|
+
|
|
298
|
+
if (waveCeoSessions.length === 0) return '';
|
|
299
|
+
|
|
300
|
+
const history: string[] = [];
|
|
301
|
+
for (const ses of waveCeoSessions) {
|
|
302
|
+
if (!ActivityStream.exists(ses.id)) continue;
|
|
303
|
+
const events = ActivityStream.readAll(ses.id);
|
|
304
|
+
|
|
305
|
+
// Extract CEO directives (from messages) and supervisor text responses
|
|
306
|
+
for (const e of events) {
|
|
307
|
+
if (e.type === 'msg:start' && e.data.task) {
|
|
308
|
+
// Extract directive from task (remove system prompt noise)
|
|
309
|
+
const task = String(e.data.task);
|
|
310
|
+
const directiveMatch = task.match(/\[CEO (?:Supervisor|Question)\]\s*(.*?)(?:\n|$)/);
|
|
311
|
+
if (directiveMatch) {
|
|
312
|
+
history.push(`CEO: "${directiveMatch[1].slice(0, 100)}"`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (e.type === 'text' && e.roleId === 'ceo') {
|
|
316
|
+
const text = String(e.data.text ?? '').trim();
|
|
317
|
+
if (text && text.length > 10 && !text.startsWith('#') && !text.startsWith('⛔')) {
|
|
318
|
+
history.push(`Supervisor: ${text.slice(0, 150)}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (history.length === 0) return '';
|
|
325
|
+
|
|
326
|
+
// Keep last 10 exchanges to fit in context
|
|
327
|
+
const recent = history.slice(-10);
|
|
328
|
+
return `\n[Previous Conversation in Wave ${waveId}]\n${recent.join('\n')}\n`;
|
|
329
|
+
} catch {
|
|
330
|
+
return '';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
286
334
|
private spawnConversation(state: SupervisorState, directive: string): void {
|
|
287
|
-
// Build conversation context:
|
|
335
|
+
// Build conversation context: in-memory directives + disk history
|
|
288
336
|
const deliveredDirectives = state.pendingDirectives.filter(d => d.delivered);
|
|
289
337
|
const directiveHistory = deliveredDirectives.length > 0
|
|
290
338
|
? deliveredDirectives.map(d => `- CEO: "${d.text}"`).join('\n')
|
|
291
339
|
: '';
|
|
292
340
|
|
|
341
|
+
// If no in-memory history, load from disk (restart case)
|
|
342
|
+
const diskHistory = !directiveHistory ? this.loadWaveHistory(state.waveId) : '';
|
|
343
|
+
|
|
293
344
|
// Extract last execution's output from activity stream (what "just happened")
|
|
294
345
|
let lastExecutionSummary = '';
|
|
295
|
-
|
|
346
|
+
// Try current supervisorSessionId first, then search by waveId
|
|
347
|
+
const sessionIdToCheck = state.supervisorSessionId
|
|
348
|
+
|| listSessions().find(s => s.waveId === state.waveId && s.roleId === 'ceo')?.id;
|
|
349
|
+
if (sessionIdToCheck) {
|
|
296
350
|
try {
|
|
297
|
-
const events = ActivityStream.readAll(
|
|
351
|
+
const events = ActivityStream.readAll(sessionIdToCheck);
|
|
298
352
|
// Get last text outputs (the supervisor's final response)
|
|
299
353
|
const textEvents = events.filter(e => e.type === 'text' && e.roleId === 'ceo');
|
|
300
354
|
const toolEvents = events.filter(e => e.type === 'tool:start' && e.roleId === 'ceo');
|
|
@@ -315,7 +369,7 @@ class SupervisorHeartbeat {
|
|
|
315
369
|
} catch { /* ignore */ }
|
|
316
370
|
}
|
|
317
371
|
|
|
318
|
-
const context = [directiveHistory, lastExecutionSummary].filter(Boolean).join('\n');
|
|
372
|
+
const context = [directiveHistory || diskHistory, lastExecutionSummary].filter(Boolean).join('\n');
|
|
319
373
|
|
|
320
374
|
const task = `${context ? context + '\n' : ''}[CEO Question] ${directive}
|
|
321
375
|
|
package/src/tui/app.tsx
CHANGED
|
@@ -286,28 +286,16 @@ export const App: React.FC = () => {
|
|
|
286
286
|
setFocusedWaveId(apiWaves[apiWaves.length - 1].waveId);
|
|
287
287
|
autoWaveCreated.current = true;
|
|
288
288
|
} else if (api.loaded && api.pastWaves.length > 0) {
|
|
289
|
-
// No active waves,
|
|
289
|
+
// No active waves, past waves exist — resume last wave (don't create new)
|
|
290
290
|
autoWaveCreated.current = true;
|
|
291
291
|
const pastEntries: WaveInfo[] = api.pastWaves.slice(0, 10).map(pw => ({
|
|
292
292
|
waveId: pw.id,
|
|
293
293
|
directive: pw.directive || '',
|
|
294
294
|
startedAt: pw.startedAt ? new Date(pw.startedAt).getTime() : 0,
|
|
295
295
|
}));
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
waveId: result.waveId,
|
|
300
|
-
directive: '',
|
|
301
|
-
startedAt: Date.now(),
|
|
302
|
-
};
|
|
303
|
-
setWaves([...pastEntries, newWave]);
|
|
304
|
-
setFocusedWaveId(result.waveId);
|
|
305
|
-
}).catch(() => {
|
|
306
|
-
// Even if new wave fails, show past waves
|
|
307
|
-
setWaves(pastEntries);
|
|
308
|
-
setFocusedWaveId(pastEntries[pastEntries.length - 1]?.waveId ?? null);
|
|
309
|
-
autoWaveCreated.current = true;
|
|
310
|
-
});
|
|
296
|
+
setWaves(pastEntries);
|
|
297
|
+
// Focus last wave — user can /new if they want a fresh one
|
|
298
|
+
setFocusedWaveId(pastEntries[pastEntries.length - 1]?.waveId ?? null);
|
|
311
299
|
} else if (api.loaded) {
|
|
312
300
|
// No active waves, no past waves — fresh start
|
|
313
301
|
autoWaveCreated.current = true;
|