tycono 0.1.99 → 0.1.101
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/bin/tycono.ts +32 -1
- package/package.json +1 -1
- package/src/api/src/services/activity-stream.ts +2 -0
- package/src/api/src/services/execution-manager.ts +14 -0
- package/src/api/src/services/session-store.ts +7 -0
- package/src/api/src/services/wave-multiplexer.ts +6 -0
- package/src/tui/components/CommandMode.tsx +11 -10
package/bin/tycono.ts
CHANGED
|
@@ -237,6 +237,30 @@ async function startServerForTui(): Promise<void> {
|
|
|
237
237
|
}) as any;
|
|
238
238
|
|
|
239
239
|
const { createHttpServer } = await import('../src/api/src/create-server.js');
|
|
240
|
+
|
|
241
|
+
// Startup orphan scan — mark stale 'active' sessions as interrupted
|
|
242
|
+
// (server.ts does this for standalone mode, but TUI mode uses create-server directly)
|
|
243
|
+
try {
|
|
244
|
+
const { listSessions, updateSession } = await import('../src/api/src/services/session-store.js');
|
|
245
|
+
const { ActivityStream } = await import('../src/api/src/services/activity-stream.js');
|
|
246
|
+
let orphaned = 0;
|
|
247
|
+
for (const ses of listSessions()) {
|
|
248
|
+
if (ses.status !== 'active') continue;
|
|
249
|
+
if (ActivityStream.exists(ses.id)) {
|
|
250
|
+
const events = ActivityStream.readFrom(ses.id, 0);
|
|
251
|
+
const tail = events.slice(-5);
|
|
252
|
+
if (tail.some((e: any) => e.type === 'msg:done' || e.type === 'msg:error')) {
|
|
253
|
+
updateSession(ses.id, { status: 'done' });
|
|
254
|
+
orphaned++;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
updateSession(ses.id, { status: 'interrupted' as any });
|
|
259
|
+
orphaned++;
|
|
260
|
+
}
|
|
261
|
+
if (orphaned > 0) logStream.write(`[STARTUP] Cleaned ${orphaned} orphaned sessions\n`);
|
|
262
|
+
} catch { /* ignore if session-store not ready */ }
|
|
263
|
+
|
|
240
264
|
const server = createHttpServer();
|
|
241
265
|
|
|
242
266
|
await new Promise<void>((resolve) => {
|
|
@@ -246,8 +270,15 @@ async function startServerForTui(): Promise<void> {
|
|
|
246
270
|
origLog(` API server started on port ${port}`);
|
|
247
271
|
origLog(` Logs: ${logFile}`);
|
|
248
272
|
|
|
249
|
-
// Graceful shutdown
|
|
273
|
+
// Graceful shutdown — mark active sessions as interrupted
|
|
250
274
|
const shutdown = () => {
|
|
275
|
+
try {
|
|
276
|
+
import('../src/api/src/services/session-store.js').then(({ listSessions, updateSession }) => {
|
|
277
|
+
for (const ses of listSessions()) {
|
|
278
|
+
if (ses.status === 'active') updateSession(ses.id, { status: 'interrupted' as any });
|
|
279
|
+
}
|
|
280
|
+
}).catch(() => {});
|
|
281
|
+
} catch {}
|
|
251
282
|
server.close(() => process.exit(0));
|
|
252
283
|
setTimeout(() => process.exit(1), 5000);
|
|
253
284
|
};
|
package/package.json
CHANGED
|
@@ -539,6 +539,20 @@ class ExecutionManager {
|
|
|
539
539
|
console.log(`[ExecMgr] Released ports for ${execution.id}: API :${execution.ports.api}, Vite :${execution.ports.vite}`);
|
|
540
540
|
}
|
|
541
541
|
}
|
|
542
|
+
|
|
543
|
+
// Memory cleanup: remove completed execution after 60s
|
|
544
|
+
// Keep briefly for status queries, then GC
|
|
545
|
+
setTimeout(() => {
|
|
546
|
+
this.executions.delete(execution.id);
|
|
547
|
+
// Clean up any leaked sessionMsgContent
|
|
548
|
+
if (execution.sessionId) {
|
|
549
|
+
for (const key of this.sessionMsgContent.keys()) {
|
|
550
|
+
if (key.startsWith(execution.sessionId + ':')) {
|
|
551
|
+
this.sessionMsgContent.delete(key);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}, 60_000);
|
|
542
556
|
});
|
|
543
557
|
}
|
|
544
558
|
|
|
@@ -251,6 +251,13 @@ export function updateSession(id: string, updates: Partial<Pick<Session, 'title'
|
|
|
251
251
|
if (updates.waveId !== undefined) session.waveId = updates.waveId;
|
|
252
252
|
session.updatedAt = new Date().toISOString();
|
|
253
253
|
writeImmediate(session);
|
|
254
|
+
|
|
255
|
+
// Memory: evict done/interrupted sessions from cache after write
|
|
256
|
+
// They're persisted to disk and can be re-loaded on demand
|
|
257
|
+
if (session.status === 'done' || session.status === 'interrupted') {
|
|
258
|
+
setTimeout(() => { cache.delete(id); }, 30_000);
|
|
259
|
+
}
|
|
260
|
+
|
|
254
261
|
return session;
|
|
255
262
|
}
|
|
256
263
|
|
|
@@ -222,6 +222,12 @@ class WaveMultiplexer {
|
|
|
222
222
|
this.registerSession(waveId, execution);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/** Remove completed wave sessions from memory */
|
|
226
|
+
cleanupWave(waveId: string): void {
|
|
227
|
+
this.waveSessions.delete(waveId);
|
|
228
|
+
this.clients.delete(waveId);
|
|
229
|
+
}
|
|
230
|
+
|
|
225
231
|
detach(waveId: string, client: WaveStreamClient): void {
|
|
226
232
|
client.closed = true;
|
|
227
233
|
clearInterval(client.heartbeat);
|
|
@@ -33,13 +33,19 @@ interface CommandModeProps {
|
|
|
33
33
|
|
|
34
34
|
let lineCounter = 0;
|
|
35
35
|
|
|
36
|
-
/** Filter out
|
|
36
|
+
/** Filter out internal system prompt fragments and context leakage */
|
|
37
37
|
function isSystemNoise(text: string): boolean {
|
|
38
38
|
const t = text.trim();
|
|
39
39
|
if (!t) return true;
|
|
40
|
-
//
|
|
40
|
+
// Injected supervisor system prompt header
|
|
41
41
|
if (t.startsWith('[CEO Supervisor]') && t.includes('Your Role')) return true;
|
|
42
42
|
if (t.startsWith('\u26D4 AKB Rule:')) return true;
|
|
43
|
+
// System prompt fragments
|
|
44
|
+
if (/^##\s*Your Role/i.test(t)) return true;
|
|
45
|
+
if (t.includes('무엇을 도와드릴까요')) return true;
|
|
46
|
+
// Conversation context leakage
|
|
47
|
+
if (t.startsWith('[Previous execution]')) return true;
|
|
48
|
+
if (/^Tools used:/i.test(t)) return true;
|
|
43
49
|
return false;
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -157,13 +163,8 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
|
|
|
157
163
|
case 'msg:start': {
|
|
158
164
|
const task = ((event.data.task as string) ?? '');
|
|
159
165
|
const cleanTask = task.replace(/\u26D4[^\u26D4]*\u26D4[^"]*/g, '').trim().slice(0, 60);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
id: ++lineCounter,
|
|
163
|
-
text: `\u25B6 Supervisor started${cleanTask ? ': ' + cleanTask : ''}`,
|
|
164
|
-
color: 'cyan',
|
|
165
|
-
};
|
|
166
|
-
}
|
|
166
|
+
// Hide supervisor started message (internal noise)
|
|
167
|
+
if (isSupervisor) return null;
|
|
167
168
|
return {
|
|
168
169
|
id: ++lineCounter,
|
|
169
170
|
prefix: event.roleId,
|
|
@@ -313,7 +314,7 @@ export const CommandMode: React.FC<CommandModeProps> = ({
|
|
|
313
314
|
setUserInputs(prev => [...prev.slice(-10), {
|
|
314
315
|
id: ++lineCounter,
|
|
315
316
|
text: `> ${trimmed}`,
|
|
316
|
-
color: '
|
|
317
|
+
color: 'green',
|
|
317
318
|
}]);
|
|
318
319
|
onSubmit(trimmed);
|
|
319
320
|
}
|