myrlin-workbook 0.9.15 → 0.9.16
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/logs/server.pid +1 -1
- package/package.json +1 -1
- package/src/gui.js +46 -0
- package/src/supervisor.js +1 -1
- package/src/web/pty-manager.js +12 -0
package/logs/server.pid
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7280
|
package/package.json
CHANGED
package/src/gui.js
CHANGED
|
@@ -189,6 +189,52 @@ if (!process.env.CWM_NO_OPEN) {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
// ─── Memory Watchdog ──────────────────────────────────────
|
|
193
|
+
// Monitor RSS and kill idle PTY sessions when memory exceeds threshold.
|
|
194
|
+
// This prevents the OS from silently OOM-killing the entire process tree.
|
|
195
|
+
const MEMORY_CHECK_INTERVAL = 30000; // Check every 30 seconds
|
|
196
|
+
const MEMORY_WARN_MB = 350; // Warn and kill idle PTYs
|
|
197
|
+
const MEMORY_CRITICAL_MB = 450; // Force-kill all clientless PTYs
|
|
198
|
+
|
|
199
|
+
const _memoryWatchdog = setInterval(() => {
|
|
200
|
+
const rssMB = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
201
|
+
if (rssMB < MEMORY_WARN_MB) return;
|
|
202
|
+
|
|
203
|
+
const { logWarning } = require('./crash-logger');
|
|
204
|
+
const ptyManager = getPtyManager();
|
|
205
|
+
if (!ptyManager) return;
|
|
206
|
+
|
|
207
|
+
const sessions = ptyManager.listSessions();
|
|
208
|
+
const isCritical = rssMB >= MEMORY_CRITICAL_MB;
|
|
209
|
+
const label = isCritical ? 'CRITICAL' : 'WARNING';
|
|
210
|
+
try { console.log(`[Memory ${label}] RSS=${rssMB}MB, ${sessions.length} PTY sessions`); } catch (_) {}
|
|
211
|
+
logWarning('server', `Memory ${label}: RSS=${rssMB}MB, ${sessions.length} PTY sessions`);
|
|
212
|
+
|
|
213
|
+
// Kill PTY sessions with zero connected WebSocket clients first
|
|
214
|
+
let killed = 0;
|
|
215
|
+
for (const s of sessions) {
|
|
216
|
+
if (s.clientCount === 0) {
|
|
217
|
+
ptyManager.killSession(s.sessionId);
|
|
218
|
+
killed++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// In critical mode, also kill sessions that have been alive longest
|
|
223
|
+
if (isCritical && killed === 0 && sessions.length > 2) {
|
|
224
|
+
const oldest = sessions.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
225
|
+
ptyManager.killSession(oldest[0].sessionId);
|
|
226
|
+
killed++;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (killed > 0) {
|
|
230
|
+
try { console.log(`[Memory] Killed ${killed} PTY session(s) to free memory`); } catch (_) {}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Force GC if available (node --expose-gc)
|
|
234
|
+
if (global.gc) global.gc();
|
|
235
|
+
}, MEMORY_CHECK_INTERVAL);
|
|
236
|
+
_memoryWatchdog.unref();
|
|
237
|
+
|
|
192
238
|
// ─── Graceful Shutdown ─────────────────────────────────────
|
|
193
239
|
|
|
194
240
|
process.on('SIGINT', () => {
|
package/src/supervisor.js
CHANGED
|
@@ -83,7 +83,7 @@ function startChild() {
|
|
|
83
83
|
lastStartTime = Date.now();
|
|
84
84
|
console.log(`[supervisor] Starting GUI server (attempt ${consecutiveRestarts + 1})...`);
|
|
85
85
|
|
|
86
|
-
child = spawn(process.execPath, [guiScript, ...guiArgs], {
|
|
86
|
+
child = spawn(process.execPath, ['--max-old-space-size=512', guiScript, ...guiArgs], {
|
|
87
87
|
stdio: 'inherit',
|
|
88
88
|
env: { ...process.env, CWM_NO_OPEN: consecutiveRestarts > 0 ? '1' : '' },
|
|
89
89
|
});
|
package/src/web/pty-manager.js
CHANGED
|
@@ -108,6 +108,11 @@ class PtySession {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// Maximum number of live PTY sessions. Each one is a ConPTY handle + a Claude
|
|
112
|
+
// process (100-200MB each). Beyond this limit, new spawns are rejected to
|
|
113
|
+
// prevent the OS from OOM-killing the server process tree.
|
|
114
|
+
const MAX_PTY_SESSIONS = 10;
|
|
115
|
+
|
|
111
116
|
class PtySessionManager {
|
|
112
117
|
constructor() {
|
|
113
118
|
this.sessions = new Map(); // sessionId -> PtySession
|
|
@@ -132,6 +137,13 @@ class PtySessionManager {
|
|
|
132
137
|
return existing;
|
|
133
138
|
}
|
|
134
139
|
|
|
140
|
+
// Enforce max live sessions to prevent OOM from too many ConPTY handles
|
|
141
|
+
const aliveCount = [...this.sessions.values()].filter(s => s.alive).length;
|
|
142
|
+
if (aliveCount >= MAX_PTY_SESSIONS) {
|
|
143
|
+
console.log(`[PTY] Rejected spawn for ${sessionId}: ${aliveCount} live sessions (max ${MAX_PTY_SESSIONS})`);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
// ── Defense-in-depth: validate all user-controlled inputs ──
|
|
136
148
|
// Primary validation happens at the API/WebSocket boundary (server.js, pty-server.js).
|
|
137
149
|
// This is a secondary gate to catch any bypass or future code path that skips validation.
|