tycono 0.1.107 → 0.1.109
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
|
@@ -129,8 +129,35 @@ class SupervisorHeartbeat {
|
|
|
129
129
|
* Dispatch Protocol Principle 2: tick이 유일한 동기화 지점.
|
|
130
130
|
*/
|
|
131
131
|
addDirective(waveId: string, text: string): PendingDirective | null {
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
let state = this.supervisors.get(waveId);
|
|
133
|
+
|
|
134
|
+
// If wave not in memory (e.g., server restarted), restore from disk
|
|
135
|
+
if (!state) {
|
|
136
|
+
// Check if this wave existed before (has sessions in session-store)
|
|
137
|
+
const waveSessions = listSessions().filter(s => s.waveId === waveId);
|
|
138
|
+
if (waveSessions.length > 0) {
|
|
139
|
+
// Restore supervisor state for this wave
|
|
140
|
+
const ceoSession = waveSessions.find(s => s.roleId === 'ceo');
|
|
141
|
+
state = {
|
|
142
|
+
waveId,
|
|
143
|
+
directive: text,
|
|
144
|
+
continuous: false,
|
|
145
|
+
supervisorSessionId: ceoSession?.id ?? null,
|
|
146
|
+
executionId: null,
|
|
147
|
+
status: 'stopped',
|
|
148
|
+
crashCount: 0,
|
|
149
|
+
maxCrashRetries: 10,
|
|
150
|
+
restartTimer: null,
|
|
151
|
+
pendingDirectives: [],
|
|
152
|
+
pendingQuestions: [],
|
|
153
|
+
createdAt: ceoSession?.createdAt ?? new Date().toISOString(),
|
|
154
|
+
};
|
|
155
|
+
this.supervisors.set(waveId, state);
|
|
156
|
+
console.log(`[Supervisor] Restored wave ${waveId} from disk (${waveSessions.length} sessions)`);
|
|
157
|
+
} else {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
134
161
|
|
|
135
162
|
const directive: PendingDirective = {
|
|
136
163
|
id: `dir-${Date.now()}`,
|
|
@@ -283,18 +310,72 @@ class SupervisorHeartbeat {
|
|
|
283
310
|
* Spawn a lightweight conversation session (no dispatch tools).
|
|
284
311
|
* CEO reads files and answers directly.
|
|
285
312
|
*/
|
|
313
|
+
/**
|
|
314
|
+
* Load conversation history from activity-stream files for a wave.
|
|
315
|
+
* Used when supervisor restarts (e.g., TUI restarted) to restore context.
|
|
316
|
+
*/
|
|
317
|
+
private loadWaveHistory(waveId: string): string {
|
|
318
|
+
try {
|
|
319
|
+
// Find CEO sessions for this wave from session-store
|
|
320
|
+
const allSessions = listSessions();
|
|
321
|
+
const waveCeoSessions = allSessions
|
|
322
|
+
.filter(s => s.waveId === waveId && s.roleId === 'ceo')
|
|
323
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
324
|
+
|
|
325
|
+
if (waveCeoSessions.length === 0) return '';
|
|
326
|
+
|
|
327
|
+
const history: string[] = [];
|
|
328
|
+
for (const ses of waveCeoSessions) {
|
|
329
|
+
if (!ActivityStream.exists(ses.id)) continue;
|
|
330
|
+
const events = ActivityStream.readAll(ses.id);
|
|
331
|
+
|
|
332
|
+
// Extract CEO directives (from messages) and supervisor text responses
|
|
333
|
+
for (const e of events) {
|
|
334
|
+
if (e.type === 'msg:start' && e.data.task) {
|
|
335
|
+
// Extract directive from task (remove system prompt noise)
|
|
336
|
+
const task = String(e.data.task);
|
|
337
|
+
const directiveMatch = task.match(/\[CEO (?:Supervisor|Question)\]\s*(.*?)(?:\n|$)/);
|
|
338
|
+
if (directiveMatch) {
|
|
339
|
+
history.push(`CEO: "${directiveMatch[1].slice(0, 100)}"`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (e.type === 'text' && e.roleId === 'ceo') {
|
|
343
|
+
const text = String(e.data.text ?? '').trim();
|
|
344
|
+
if (text && text.length > 10 && !text.startsWith('#') && !text.startsWith('⛔')) {
|
|
345
|
+
history.push(`Supervisor: ${text.slice(0, 150)}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (history.length === 0) return '';
|
|
352
|
+
|
|
353
|
+
// Keep last 10 exchanges to fit in context
|
|
354
|
+
const recent = history.slice(-10);
|
|
355
|
+
return `\n[Previous Conversation in Wave ${waveId}]\n${recent.join('\n')}\n`;
|
|
356
|
+
} catch {
|
|
357
|
+
return '';
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
286
361
|
private spawnConversation(state: SupervisorState, directive: string): void {
|
|
287
|
-
// Build conversation context:
|
|
362
|
+
// Build conversation context: in-memory directives + disk history
|
|
288
363
|
const deliveredDirectives = state.pendingDirectives.filter(d => d.delivered);
|
|
289
364
|
const directiveHistory = deliveredDirectives.length > 0
|
|
290
365
|
? deliveredDirectives.map(d => `- CEO: "${d.text}"`).join('\n')
|
|
291
366
|
: '';
|
|
292
367
|
|
|
368
|
+
// If no in-memory history, load from disk (restart case)
|
|
369
|
+
const diskHistory = !directiveHistory ? this.loadWaveHistory(state.waveId) : '';
|
|
370
|
+
|
|
293
371
|
// Extract last execution's output from activity stream (what "just happened")
|
|
294
372
|
let lastExecutionSummary = '';
|
|
295
|
-
|
|
373
|
+
// Try current supervisorSessionId first, then search by waveId
|
|
374
|
+
const sessionIdToCheck = state.supervisorSessionId
|
|
375
|
+
|| listSessions().find(s => s.waveId === state.waveId && s.roleId === 'ceo')?.id;
|
|
376
|
+
if (sessionIdToCheck) {
|
|
296
377
|
try {
|
|
297
|
-
const events = ActivityStream.readAll(
|
|
378
|
+
const events = ActivityStream.readAll(sessionIdToCheck);
|
|
298
379
|
// Get last text outputs (the supervisor's final response)
|
|
299
380
|
const textEvents = events.filter(e => e.type === 'text' && e.roleId === 'ceo');
|
|
300
381
|
const toolEvents = events.filter(e => e.type === 'tool:start' && e.roleId === 'ceo');
|
|
@@ -315,7 +396,7 @@ class SupervisorHeartbeat {
|
|
|
315
396
|
} catch { /* ignore */ }
|
|
316
397
|
}
|
|
317
398
|
|
|
318
|
-
const context = [directiveHistory, lastExecutionSummary].filter(Boolean).join('\n');
|
|
399
|
+
const context = [directiveHistory || diskHistory, lastExecutionSummary].filter(Boolean).join('\n');
|
|
319
400
|
|
|
320
401
|
const task = `${context ? context + '\n' : ''}[CEO Question] ${directive}
|
|
321
402
|
|
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;
|