myrlin-workbook 0.9.17 → 0.9.19
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 +18 -6
- package/src/supervisor.js +48 -16
- package/src/web/pty-manager.js +1 -1
package/logs/server.pid
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
49156
|
package/package.json
CHANGED
package/src/gui.js
CHANGED
|
@@ -191,10 +191,22 @@ if (!process.env.CWM_NO_OPEN) {
|
|
|
191
191
|
|
|
192
192
|
// ─── Memory Watchdog ──────────────────────────────────────
|
|
193
193
|
// Monitor RSS and kill idle PTY sessions when memory exceeds threshold.
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const
|
|
194
|
+
// ConPTY on Windows allocates significant native memory (outside V8 heap)
|
|
195
|
+
// so RSS can spike well beyond --max-old-space-size.
|
|
196
|
+
const MEMORY_CHECK_INTERVAL = 15000; // Check every 15 seconds
|
|
197
|
+
const MEMORY_WARN_MB = 200; // Start killing idle (zero-client) PTYs
|
|
198
|
+
const MEMORY_CRITICAL_MB = 350; // Aggressively kill PTYs to survive
|
|
199
|
+
|
|
200
|
+
// Periodic RSS logging so we can trace memory trajectory in server.log
|
|
201
|
+
const _rssLogger = setInterval(() => {
|
|
202
|
+
const mem = process.memoryUsage();
|
|
203
|
+
const ptyManager = getPtyManager();
|
|
204
|
+
const ptySessions = ptyManager ? ptyManager.listSessions().length : 0;
|
|
205
|
+
try {
|
|
206
|
+
console.log(`[RSS] ${Math.round(mem.rss / 1024 / 1024)}MB heap=${Math.round(mem.heapUsed / 1024 / 1024)}/${Math.round(mem.heapTotal / 1024 / 1024)}MB pty=${ptySessions}`);
|
|
207
|
+
} catch (_) {}
|
|
208
|
+
}, 60000);
|
|
209
|
+
_rssLogger.unref();
|
|
198
210
|
|
|
199
211
|
const _memoryWatchdog = setInterval(() => {
|
|
200
212
|
const rssMB = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
@@ -220,8 +232,8 @@ const _memoryWatchdog = setInterval(() => {
|
|
|
220
232
|
}
|
|
221
233
|
|
|
222
234
|
// In critical mode, also kill sessions that have been alive longest
|
|
223
|
-
if (isCritical && killed === 0 && sessions.length >
|
|
224
|
-
const oldest = sessions.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
235
|
+
if (isCritical && killed === 0 && sessions.length > 1) {
|
|
236
|
+
const oldest = [...sessions].sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
225
237
|
ptyManager.killSession(oldest[0].sessionId);
|
|
226
238
|
killed++;
|
|
227
239
|
}
|
package/src/supervisor.js
CHANGED
|
@@ -33,25 +33,57 @@ if (process.argv.includes('--daemon')) {
|
|
|
33
33
|
const logDir = path.join(__dirname, '..', 'logs');
|
|
34
34
|
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
35
35
|
const logFile = path.join(logDir, 'server.log');
|
|
36
|
-
const
|
|
37
|
-
const err = fs.openSync(logFile, 'a');
|
|
36
|
+
const pidFile = path.join(logDir, 'server.pid');
|
|
38
37
|
|
|
39
38
|
// Strip --daemon from args so the child runs in foreground (supervised) mode
|
|
40
39
|
const childArgs = process.argv.slice(2).filter(a => a !== '--daemon');
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
const nodeExe = process.execPath;
|
|
41
|
+
const scriptArgs = [__filename, ...childArgs].map(a => `"${a}"`).join(' ');
|
|
42
|
+
|
|
43
|
+
if (process.platform === 'win32') {
|
|
44
|
+
// On Windows, Node's detached:true still inherits the console session's
|
|
45
|
+
// Job Object. When the parent shell (Git Bash, cmd, Claude Code) exits,
|
|
46
|
+
// Windows kills the entire job group. Use cmd.exe /c start to create a
|
|
47
|
+
// process in a completely new console session, then redirect its output.
|
|
48
|
+
const { execSync } = require('child_process');
|
|
49
|
+
const cmd = `cmd.exe /c start /b "" "${nodeExe}" --max-old-space-size=1024 ${scriptArgs} >> "${logFile}" 2>&1`;
|
|
50
|
+
execSync(cmd, { stdio: 'ignore', windowsHide: true });
|
|
51
|
+
|
|
52
|
+
// The PID isn't directly available from start /b. Write a marker so we
|
|
53
|
+
// can find it via tasklist. Wait briefly for the process to appear.
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
try {
|
|
56
|
+
const { execSync: es } = require('child_process');
|
|
57
|
+
const psCmd = `powershell.exe -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*supervisor.js*' -and $_.CommandLine -notlike '*--daemon*' } | Select-Object -ExpandProperty ProcessId"`;
|
|
58
|
+
const out = es(psCmd, { encoding: 'utf8', timeout: 10000 });
|
|
59
|
+
const pids = out.trim().split('\n').map(l => l.trim()).filter(Boolean);
|
|
60
|
+
if (pids.length > 0) {
|
|
61
|
+
const pid = pids[pids.length - 1];
|
|
62
|
+
fs.writeFileSync(pidFile, pid, 'utf8');
|
|
63
|
+
console.log(`[supervisor] Daemonized server (PID ${pid}), logs at ${logFile}`);
|
|
64
|
+
} else {
|
|
65
|
+
console.log(`[supervisor] Daemonized server, logs at ${logFile}`);
|
|
66
|
+
}
|
|
67
|
+
} catch (_) {
|
|
68
|
+
console.log(`[supervisor] Daemonized server, logs at ${logFile}`);
|
|
69
|
+
}
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}, 2000);
|
|
72
|
+
} else {
|
|
73
|
+
// Unix: standard detach with file descriptors
|
|
74
|
+
const out = fs.openSync(logFile, 'a');
|
|
75
|
+
const err = fs.openSync(logFile, 'a');
|
|
76
|
+
const child = spawn(nodeExe, ['--max-old-space-size=1024', __filename, ...childArgs], {
|
|
77
|
+
stdio: ['ignore', out, err],
|
|
78
|
+
detached: true,
|
|
79
|
+
env: { ...process.env },
|
|
80
|
+
});
|
|
81
|
+
fs.writeFileSync(pidFile, String(child.pid), 'utf8');
|
|
82
|
+
child.unref();
|
|
83
|
+
console.log(`[supervisor] Daemonized server (PID ${child.pid}), logs at ${logFile}`);
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
return; // Guard: don't fall through to supervisor logic while waiting
|
|
55
87
|
}
|
|
56
88
|
|
|
57
89
|
// ─── EPIPE Protection ────────────────────────────────────
|
package/src/web/pty-manager.js
CHANGED
|
@@ -111,7 +111,7 @@ class PtySession {
|
|
|
111
111
|
// Maximum number of live PTY sessions. Each one is a ConPTY handle + a Claude
|
|
112
112
|
// process (100-200MB each). Beyond this limit, new spawns are rejected to
|
|
113
113
|
// prevent the OS from OOM-killing the server process tree.
|
|
114
|
-
const MAX_PTY_SESSIONS =
|
|
114
|
+
const MAX_PTY_SESSIONS = 5;
|
|
115
115
|
|
|
116
116
|
class PtySessionManager {
|
|
117
117
|
constructor() {
|