tycono 0.1.103 → 0.1.105
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
CHANGED
|
@@ -270,6 +270,12 @@ async function startServerForTui(): Promise<void> {
|
|
|
270
270
|
origLog(` API server started on port ${port}`);
|
|
271
271
|
origLog(` Logs: ${logFile}`);
|
|
272
272
|
|
|
273
|
+
// Memory monitor — log heap usage every 30s to detect leaks
|
|
274
|
+
setInterval(() => {
|
|
275
|
+
const mem = process.memoryUsage();
|
|
276
|
+
logStream.write(`[MEM] heap=${Math.round(mem.heapUsed / 1024 / 1024)}MB/${Math.round(mem.heapTotal / 1024 / 1024)}MB rss=${Math.round(mem.rss / 1024 / 1024)}MB\n`);
|
|
277
|
+
}, 30_000).unref();
|
|
278
|
+
|
|
273
279
|
// Graceful shutdown — mark active sessions as interrupted
|
|
274
280
|
const shutdown = () => {
|
|
275
281
|
try {
|
package/package.json
CHANGED
|
@@ -58,6 +58,27 @@ export function createApp() {
|
|
|
58
58
|
res.json({ status: 'ok', companyRoot: COMPANY_ROOT });
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
// Debug: memory stats for leak detection
|
|
62
|
+
app.get('/api/debug/memory', (_req, res) => {
|
|
63
|
+
const mem = process.memoryUsage();
|
|
64
|
+
// Import dynamically to avoid circular deps
|
|
65
|
+
import('./services/execution-manager.js').then(({ executionManager }) => {
|
|
66
|
+
import('./services/session-store.js').then(({ listSessions }) => {
|
|
67
|
+
res.json({
|
|
68
|
+
heap: { used: Math.round(mem.heapUsed / 1024 / 1024), total: Math.round(mem.heapTotal / 1024 / 1024) },
|
|
69
|
+
rss: Math.round(mem.rss / 1024 / 1024),
|
|
70
|
+
execManager: executionManager.getMemoryStats(),
|
|
71
|
+
sessions: listSessions().length,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}).catch(() => {
|
|
75
|
+
res.json({
|
|
76
|
+
heap: { used: Math.round(mem.heapUsed / 1024 / 1024), total: Math.round(mem.heapTotal / 1024 / 1024) },
|
|
77
|
+
rss: Math.round(mem.rss / 1024 / 1024),
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
61
82
|
app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
|
62
83
|
const status = err.name === 'FileNotFoundError' ? 404 : 500;
|
|
63
84
|
res.status(status).json({ error: err.message });
|
|
@@ -540,22 +540,30 @@ class ExecutionManager {
|
|
|
540
540
|
}
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (execution.sessionId) {
|
|
549
|
-
for (const key of this.sessionMsgContent.keys()) {
|
|
550
|
-
if (key.startsWith(execution.sessionId + ':')) {
|
|
551
|
-
this.sessionMsgContent.delete(key);
|
|
552
|
-
}
|
|
543
|
+
// Clean up sessionMsgContent immediately (no longer needed after finalize)
|
|
544
|
+
if (execution.sessionId) {
|
|
545
|
+
for (const key of this.sessionMsgContent.keys()) {
|
|
546
|
+
if (key.startsWith(execution.sessionId + ':')) {
|
|
547
|
+
this.sessionMsgContent.delete(key);
|
|
553
548
|
}
|
|
554
549
|
}
|
|
555
|
-
}
|
|
550
|
+
}
|
|
556
551
|
});
|
|
557
552
|
}
|
|
558
553
|
|
|
554
|
+
/** Debug: return memory stats for monitoring */
|
|
555
|
+
getMemoryStats(): { executions: number; msgContentKeys: number; msgContentSize: number } {
|
|
556
|
+
let msgContentSize = 0;
|
|
557
|
+
for (const v of this.sessionMsgContent.values()) {
|
|
558
|
+
msgContentSize += v.length;
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
executions: this.executions.size,
|
|
562
|
+
msgContentKeys: this.sessionMsgContent.size,
|
|
563
|
+
msgContentSize,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
559
567
|
/* ─── Session ↔ Execution bridge ───────── */
|
|
560
568
|
|
|
561
569
|
private sessionMsgContent = new Map<string, string>();
|
|
@@ -252,12 +252,6 @@ export function updateSession(id: string, updates: Partial<Pick<Session, 'title'
|
|
|
252
252
|
session.updatedAt = new Date().toISOString();
|
|
253
253
|
writeImmediate(session);
|
|
254
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
|
-
|
|
261
255
|
return session;
|
|
262
256
|
}
|
|
263
257
|
|