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
package/src/api/src/server.ts
CHANGED
|
@@ -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
|
|
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(() =>
|
|
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
|
|
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,
|
|
158
|
+
return readFilePreview(selectedFile, 60);
|
|
156
159
|
}, [selectedFile, rightTab]);
|
|
157
160
|
|
|
158
161
|
useInput((input, key) => {
|