tycono 0.1.96-beta.41 → 0.1.96-beta.43

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.96-beta.41",
3
+ "version": "0.1.96-beta.43",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,40 @@
1
1
  import { COMPANY_ROOT } from './services/file-reader.js';
2
2
  import { applyConfig } from './services/company-config.js';
3
3
  import { createHttpServer } from './create-server.js';
4
+ import { listSessions, updateSession } from './services/session-store.js';
5
+ import { ActivityStream } from './services/activity-stream.js';
4
6
 
5
7
  // Load .tycono/config.json and apply to process.env
6
8
  const config = applyConfig(COMPANY_ROOT);
7
9
  console.log(`[STARTUP] Engine: ${config.engine}, API key: ${config.apiKey ? 'set' : 'none'}`);
8
10
 
11
+ // Startup: mark orphaned 'active' sessions as 'interrupted'
12
+ // These are sessions from a previous server that crashed or was killed
13
+ {
14
+ const allSessions = listSessions();
15
+ let orphaned = 0;
16
+ for (const ses of allSessions) {
17
+ if (ses.status !== 'active') continue;
18
+ // Check activity stream — if it has msg:done/msg:error, mark done
19
+ // If not, mark interrupted (previous server died mid-execution)
20
+ if (ActivityStream.exists(ses.id)) {
21
+ const events = ActivityStream.readFrom(ses.id, 0);
22
+ const tail = events.slice(-5);
23
+ const isDone = tail.some(e => e.type === 'msg:done' || e.type === 'msg:error');
24
+ if (isDone) {
25
+ updateSession(ses.id, { status: 'done' });
26
+ orphaned++;
27
+ continue;
28
+ }
29
+ }
30
+ updateSession(ses.id, { status: 'interrupted' as any });
31
+ orphaned++;
32
+ }
33
+ if (orphaned > 0) {
34
+ console.log(`[STARTUP] Cleaned ${orphaned} orphaned sessions (active → done/interrupted)`);
35
+ }
36
+ }
37
+
9
38
  const PORT = Number(process.env.PORT) || 3001;
10
39
  const server = createHttpServer();
11
40
 
@@ -13,3 +42,18 @@ server.listen(PORT, () => {
13
42
  console.log(`[API] Server running on http://localhost:${PORT}`);
14
43
  console.log(`[API] COMPANY_ROOT: ${COMPANY_ROOT}`);
15
44
  });
45
+
46
+ // Graceful shutdown: mark running sessions as interrupted
47
+ function gracefulShutdown(signal: string) {
48
+ console.log(`[SHUTDOWN] ${signal} received, marking active sessions as interrupted...`);
49
+ const sessions = listSessions();
50
+ for (const ses of sessions) {
51
+ if (ses.status === 'active') {
52
+ updateSession(ses.id, { status: 'interrupted' as any });
53
+ }
54
+ }
55
+ process.exit(0);
56
+ }
57
+
58
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
59
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
@@ -10,7 +10,7 @@ import { estimateCost } from './pricing.js';
10
10
  import { readConfig, getConversationLimits, resolveCodeRoot } from './company-config.js';
11
11
  import { postKnowledgingCheck, type KnowledgeDebtItem } from '../engine/knowledge-gate.js';
12
12
  import { earnCoinsInternal } from '../routes/coins.js';
13
- import { getSession, createSession, addMessage, updateMessage as updateSessionMessage, appendMessageEvent, type Message, type ImageAttachment } from './session-store.js';
13
+ import { getSession, createSession, addMessage, updateMessage as updateSessionMessage, updateSession, appendMessageEvent, type Message, type ImageAttachment } from './session-store.js';
14
14
  import { portRegistry, type PortAllocation } from './port-registry.js';
15
15
  import { type MessageStatus, isMessageActive, canTransition, messageStatusToRoleStatus } from '../../../shared/types.js';
16
16
 
@@ -603,6 +603,12 @@ class ExecutionManager {
603
603
  knowledgeDebt: execution.knowledgeDebt.map(d => ({ type: d.type, file: d.file, message: d.message })),
604
604
  }),
605
605
  });
606
+
607
+ // Mark session as done in session-store (persisted to file)
608
+ // Skip CEO supervisor sessions — they stay active for wave lifecycle
609
+ if (session.roleId !== 'ceo' || session.source !== 'wave') {
610
+ updateSession(execution.sessionId, { status: 'done' });
611
+ }
606
612
  }
607
613
 
608
614
  private cleanupOrphanedChildren(parentSessionId: string): void {
@@ -125,7 +125,7 @@ class WaveMultiplexer {
125
125
  console.log(`[WaveMux] Replayed ${replayEvents.length} events (${sessionList.length} total sessions, ${recentSessions.length} replayed)`);
126
126
 
127
127
  // Phase 2: Subscribe to live events for active sessions
128
- for (const [, exec] of sessionList) {
128
+ for (const exec of sessionList) {
129
129
  if (exec.status === 'running' || exec.status === 'awaiting_input') {
130
130
  this.subscribeSessionToClient(waveId, client, exec, true);
131
131
  }
@@ -145,14 +145,17 @@ export const PanelMode: React.FC<PanelModeProps> = ({
145
145
  return tree.map(scopeNode);
146
146
  }, [tree, waveScopedStatuses]);
147
147
 
148
- // Files created in this wave
149
- const waveFiles = useMemo(() => extractWaveFiles(events), [events]);
148
+ // Files created in this wave — only compute when Docs tab is active
149
+ const waveFiles = useMemo(() => {
150
+ if (rightTab !== 'docs' && rightTab !== 'info') return [];
151
+ return extractWaveFiles(events);
152
+ }, [rightTab === 'docs' || rightTab === 'info' ? events.length : 0, rightTab]);
150
153
 
151
- // File preview for selected doc
154
+ // File preview only when Docs tab + file selected
152
155
  const selectedFile = waveFiles[docsIndex] ?? null;
153
156
  const filePreview = useMemo(() => {
154
157
  if (!selectedFile || rightTab !== 'docs') return [];
155
- return readFilePreview(selectedFile, 100);
158
+ return readFilePreview(selectedFile, 60);
156
159
  }, [selectedFile, rightTab]);
157
160
 
158
161
  useInput((input, key) => {