tycono 0.1.107-beta.2 → 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.1.107-beta.2",
3
+ "version": "0.1.108",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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: previous directives + last execution summary
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
- if (state.supervisorSessionId) {
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(state.supervisorSessionId);
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, but past waves exist — load them + create a new one
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
- dispatchWave().then(result => {
298
- const newWave: WaveInfo = {
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;
@@ -396,18 +396,17 @@ export const CommandMode: React.FC<CommandModeProps> = ({
396
396
  {/* Quick action bar — shown when arrow down from input */}
397
397
  {quickBarActive && (
398
398
  <Box paddingX={0} marginTop={0}>
399
- {QUICK_ACTIONS.map((action, i) => (
400
- <Box key={action} marginRight={1}>
401
- <Text
402
- color={i === quickBarIndex ? 'cyan' : 'gray'}
403
- bold={i === quickBarIndex}
404
- inverse={i === quickBarIndex}
405
- >
406
- {` ${action} `}
407
- </Text>
408
- </Box>
409
- ))}
410
- <Text color="gray" dimColor> \u2190\u2192 select Enter open \u2191 back</Text>
399
+ {QUICK_ACTIONS.map((action, i) => {
400
+ const selected = i === quickBarIndex;
401
+ return (
402
+ <Box key={action} marginRight={1}>
403
+ <Text color={selected ? 'cyan' : 'gray'} bold={selected}>
404
+ {selected ? `[ ${action} ]` : ` ${action} `}
405
+ </Text>
406
+ </Box>
407
+ );
408
+ })}
409
+ <Text color="gray" dimColor> \u2190\u2192 Enter \u2191back</Text>
411
410
  </Box>
412
411
  )}
413
412
  </Box>