gm-skill 2.0.1557 → 2.0.1559
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/gm-plugkit/package.json +1 -1
- package/gm-plugkit/supervisor.js +5 -1
- package/gm.json +1 -1
- package/lib/skill-bootstrap.js +13 -4
- package/package.json +1 -1
- package/bin/plugkit-supervisor.js +0 -360
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1559",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/gm-plugkit/supervisor.js
CHANGED
|
@@ -264,7 +264,11 @@ function spawnWatcher(bootReason) {
|
|
|
264
264
|
try { fs.unlinkSync(SUPERVISOR_PATH); } catch (_) {}
|
|
265
265
|
process.exit(0);
|
|
266
266
|
}
|
|
267
|
-
const respawnReason = reason === 'version-change'
|
|
267
|
+
const respawnReason = reason === 'version-change'
|
|
268
|
+
? 'planned-restart-version-change'
|
|
269
|
+
: isPlanned
|
|
270
|
+
? `planned-restart-after-${reason || (cleanExit ? 'clean-exit' : 'exit')}`
|
|
271
|
+
: 'unplanned-restart-after-exit';
|
|
268
272
|
writeSupervisorStatus('restarting', {
|
|
269
273
|
prior_watcher_pid: currentChildPid,
|
|
270
274
|
prior_exit_code: code,
|
package/gm.json
CHANGED
package/lib/skill-bootstrap.js
CHANGED
|
@@ -503,10 +503,19 @@ function openWatcherLog(projectDir) {
|
|
|
503
503
|
|
|
504
504
|
function ensureSupervisorInstalled() {
|
|
505
505
|
try {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
let src = null;
|
|
507
|
+
try {
|
|
508
|
+
const gmPlugkit = require('gm-plugkit');
|
|
509
|
+
const base = path.dirname(gmPlugkit.getPath ? gmPlugkit.getPath() : require.resolve('gm-plugkit'));
|
|
510
|
+
const cand = path.join(base, 'supervisor.js');
|
|
511
|
+
if (fs.existsSync(cand)) src = cand;
|
|
512
|
+
} catch (_) {}
|
|
513
|
+
if (!src) {
|
|
514
|
+
src = resolveFromCandidates([
|
|
515
|
+
path.join(__dirname, '..', 'gm-plugkit', 'supervisor.js'),
|
|
516
|
+
path.join(__dirname, '..', '..', 'gm-plugkit', 'supervisor.js'),
|
|
517
|
+
], 'gm-skill/gm-plugkit/supervisor.js');
|
|
518
|
+
}
|
|
510
519
|
if (!src || !fs.existsSync(src)) {
|
|
511
520
|
emitBootstrapEvent('warn', 'bundled plugkit-supervisor.js not found; supervisor unavailable');
|
|
512
521
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1559",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const { spawn, spawnSync } = require('child_process');
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
|
|
10
|
-
function wrapperSha12OnDisk() {
|
|
11
|
-
try {
|
|
12
|
-
return crypto.createHash('sha256').update(fs.readFileSync(resolveWrapper())).digest('hex').slice(0, 12);
|
|
13
|
-
} catch (_) { return null; }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
|
-
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
18
|
-
fs.mkdirSync(spoolDir, { recursive: true });
|
|
19
|
-
|
|
20
|
-
const STATUS_PATH = path.join(spoolDir, '.status.json');
|
|
21
|
-
const SHUTDOWN_REASON_PATH = path.join(spoolDir, '.shutdown-reason.json');
|
|
22
|
-
const SUPERVISOR_STATUS_PATH = path.join(spoolDir, '.supervisor-status.json');
|
|
23
|
-
const SUPERVISOR_PID_PATH = path.join(spoolDir, '.supervisor.pid');
|
|
24
|
-
const LOG_PATH = path.join(spoolDir, '.watcher.log');
|
|
25
|
-
const GM_LOG_ROOT = process.env.GM_LOG_DIR || path.join(os.homedir(), '.claude', 'gm-log');
|
|
26
|
-
|
|
27
|
-
const HEARTBEAT_STALE_MS = 60_000;
|
|
28
|
-
const HEALTH_POLL_MS = 5_000;
|
|
29
|
-
const SUPERVISOR_HEARTBEAT_MS = 5_000;
|
|
30
|
-
const SIGTERM_GRACE_MS = 5_000;
|
|
31
|
-
const BACKOFF_BASE_MS = 2_000;
|
|
32
|
-
const BACKOFF_CAP_MS = 30_000;
|
|
33
|
-
|
|
34
|
-
function logEvent(event, fields) {
|
|
35
|
-
try {
|
|
36
|
-
const day = new Date().toISOString().slice(0, 10);
|
|
37
|
-
const dir = path.join(GM_LOG_ROOT, day);
|
|
38
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
-
const line = JSON.stringify({
|
|
40
|
-
ts: new Date().toISOString(),
|
|
41
|
-
sub: 'plugkit',
|
|
42
|
-
event,
|
|
43
|
-
pid: process.pid,
|
|
44
|
-
sess: process.env.CLAUDE_SESSION_ID || '',
|
|
45
|
-
cwd: projectDir,
|
|
46
|
-
role: 'supervisor',
|
|
47
|
-
...fields,
|
|
48
|
-
}) + '\n';
|
|
49
|
-
fs.appendFileSync(path.join(dir, 'plugkit.jsonl'), line);
|
|
50
|
-
} catch (_) {}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function writeSupervisorStatus(state, extra) {
|
|
54
|
-
try {
|
|
55
|
-
fs.writeFileSync(SUPERVISOR_STATUS_PATH, JSON.stringify({
|
|
56
|
-
pid: process.pid,
|
|
57
|
-
ts: Date.now(),
|
|
58
|
-
iso: new Date().toISOString(),
|
|
59
|
-
state,
|
|
60
|
-
watcher_pid: currentChildPid,
|
|
61
|
-
...(extra || {}),
|
|
62
|
-
}));
|
|
63
|
-
} catch (_) {}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function writeShutdownReason(reason, extra) {
|
|
67
|
-
try {
|
|
68
|
-
fs.writeFileSync(SHUTDOWN_REASON_PATH, JSON.stringify({
|
|
69
|
-
ts: new Date().toISOString(),
|
|
70
|
-
reason,
|
|
71
|
-
written_by: 'supervisor',
|
|
72
|
-
supervisor_pid: process.pid,
|
|
73
|
-
watcher_pid: currentChildPid,
|
|
74
|
-
...(extra || {}),
|
|
75
|
-
}));
|
|
76
|
-
} catch (_) {}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function pidAlive(pid) {
|
|
80
|
-
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
81
|
-
try { process.kill(pid, 0); return true; } catch (_) { return false; }
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function readStatus() {
|
|
85
|
-
try { return JSON.parse(fs.readFileSync(STATUS_PATH, 'utf-8')); } catch (_) { return null; }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function statusMtime() {
|
|
89
|
-
try { return fs.statSync(STATUS_PATH).mtimeMs; } catch (_) { return 0; }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function acquireSingleInstance() {
|
|
93
|
-
// Atomic via O_EXCL ('wx'): exclusive-create fails if the file exists, so when N supervisors
|
|
94
|
-
// race to start in the same instant exactly one wins. A plain existsSync->write is TOCTOU and
|
|
95
|
-
// lets a concurrent burst all pass, which is the duplicate-supervisor churn this guards against.
|
|
96
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
97
|
-
try {
|
|
98
|
-
const fd = fs.openSync(SUPERVISOR_PID_PATH, 'wx');
|
|
99
|
-
try { fs.writeSync(fd, String(process.pid)); } finally { fs.closeSync(fd); }
|
|
100
|
-
return true;
|
|
101
|
-
} catch (e) {
|
|
102
|
-
if (e && e.code === 'EEXIST') {
|
|
103
|
-
let other = NaN;
|
|
104
|
-
try { other = parseInt(fs.readFileSync(SUPERVISOR_PID_PATH, 'utf-8').trim(), 10); } catch (_) {}
|
|
105
|
-
if (Number.isFinite(other) && other !== process.pid && pidAlive(other)) {
|
|
106
|
-
logEvent('supervisor.refused-duplicate', { existing_pid: other, severity: 'warn' });
|
|
107
|
-
process.stderr.write(`[plugkit-supervisor] another supervisor is alive (pid=${other}); exiting\n`);
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
try { fs.unlinkSync(SUPERVISOR_PID_PATH); } catch (_) {}
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
logEvent('supervisor.pid-write-failed', { error: e && e.message, severity: 'warn' });
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function releaseSingleInstance() {
|
|
121
|
-
try {
|
|
122
|
-
if (fs.existsSync(SUPERVISOR_PID_PATH)) {
|
|
123
|
-
const raw = fs.readFileSync(SUPERVISOR_PID_PATH, 'utf-8').trim();
|
|
124
|
-
if (parseInt(raw, 10) === process.pid) fs.unlinkSync(SUPERVISOR_PID_PATH);
|
|
125
|
-
}
|
|
126
|
-
} catch (_) {}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
let currentChildPid = null;
|
|
130
|
-
let currentChild = null;
|
|
131
|
-
let restartCount = 0;
|
|
132
|
-
let lastSpawnedAt = 0;
|
|
133
|
-
let shuttingDown = false;
|
|
134
|
-
let killingForHeartbeat = false;
|
|
135
|
-
|
|
136
|
-
function nextBackoffMs() {
|
|
137
|
-
const ms = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * Math.pow(2, restartCount));
|
|
138
|
-
return ms;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function resolveWrapper() {
|
|
142
|
-
const primary = path.join(os.homedir(), '.gm-tools', 'plugkit-wasm-wrapper.js');
|
|
143
|
-
const fallback = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit-wasm-wrapper.js');
|
|
144
|
-
if (fs.existsSync(primary)) return primary;
|
|
145
|
-
if (fs.existsSync(fallback)) return fallback;
|
|
146
|
-
return primary;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function resolveRuntime() {
|
|
150
|
-
const preferred = process.env.PLUGKIT_RUNTIME || 'bun';
|
|
151
|
-
try {
|
|
152
|
-
const r = spawnSync(preferred, ['--version'], { stdio: 'ignore', windowsHide: true, timeout: 1500 });
|
|
153
|
-
if (r.status === 0) return preferred;
|
|
154
|
-
} catch (_) {}
|
|
155
|
-
return process.execPath;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function spawnWatcher(bootReason) {
|
|
159
|
-
if (shuttingDown) return;
|
|
160
|
-
const wrapper = resolveWrapper();
|
|
161
|
-
if (!fs.existsSync(wrapper)) {
|
|
162
|
-
logEvent('supervisor.wrapper-missing', { wrapper, severity: 'critical' });
|
|
163
|
-
writeSupervisorStatus('error', { error: 'wrapper-missing', wrapper });
|
|
164
|
-
setTimeout(() => spawnWatcher(bootReason), Math.min(BACKOFF_CAP_MS, nextBackoffMs()));
|
|
165
|
-
restartCount += 1;
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const runtime = resolveRuntime();
|
|
169
|
-
let logFd = null;
|
|
170
|
-
try { logFd = fs.openSync(LOG_PATH, 'a'); } catch (_) {}
|
|
171
|
-
try {
|
|
172
|
-
if (logFd !== null) fs.writeSync(logFd, `\n--- watcher spawn ${new Date().toISOString()} supervisor=${process.pid} reason=${bootReason} ---\n`);
|
|
173
|
-
} catch (_) {}
|
|
174
|
-
|
|
175
|
-
const child = spawn(runtime, [wrapper, 'spool'], {
|
|
176
|
-
detached: false,
|
|
177
|
-
stdio: ['ignore', logFd || 'ignore', logFd || 'ignore'],
|
|
178
|
-
windowsHide: true,
|
|
179
|
-
env: {
|
|
180
|
-
...process.env,
|
|
181
|
-
CLAUDE_PROJECT_DIR: projectDir,
|
|
182
|
-
PLUGKIT_BOOT_REASON: bootReason,
|
|
183
|
-
PLUGKIT_SUPERVISOR_PID: String(process.pid),
|
|
184
|
-
},
|
|
185
|
-
...(process.platform === 'win32' ? { creationFlags: 0x08000000 | 0x00000008 } : {}),
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
try { if (logFd !== null) fs.closeSync(logFd); } catch (_) {}
|
|
189
|
-
currentChild = child;
|
|
190
|
-
currentChildPid = child.pid;
|
|
191
|
-
lastSpawnedAt = Date.now();
|
|
192
|
-
writeSupervisorStatus('watching', { boot_reason: bootReason, runtime });
|
|
193
|
-
logEvent('supervisor.spawned-watcher', { watcher_pid: child.pid, boot_reason: bootReason, runtime });
|
|
194
|
-
|
|
195
|
-
child.on('exit', (code, signal) => {
|
|
196
|
-
const wasKilled = killingForHeartbeat;
|
|
197
|
-
killingForHeartbeat = false;
|
|
198
|
-
const exitedPid = currentChildPid;
|
|
199
|
-
currentChild = null;
|
|
200
|
-
currentChildPid = null;
|
|
201
|
-
if (shuttingDown) return;
|
|
202
|
-
const uptimeMs = Date.now() - lastSpawnedAt;
|
|
203
|
-
const respawnReason = wasKilled ? 'supervisor-killed-stale-heartbeat' : (signal ? `signal-${signal}` : `exit-${code}`);
|
|
204
|
-
logEvent('supervisor.watcher-exited', {
|
|
205
|
-
watcher_pid: exitedPid,
|
|
206
|
-
exit_code: code,
|
|
207
|
-
signal,
|
|
208
|
-
uptime_ms: uptimeMs,
|
|
209
|
-
respawn_reason: respawnReason,
|
|
210
|
-
severity: code === 0 && !signal && !wasKilled ? 'info' : 'critical',
|
|
211
|
-
});
|
|
212
|
-
if (code === 0 && !signal && !wasKilled) {
|
|
213
|
-
restartCount = 0;
|
|
214
|
-
} else {
|
|
215
|
-
restartCount += 1;
|
|
216
|
-
}
|
|
217
|
-
const delay = nextBackoffMs();
|
|
218
|
-
writeSupervisorStatus('restarting', { prior_watcher_pid: exitedPid, prior_exit_code: code, prior_signal: signal, respawn_reason: respawnReason, backoff_ms: delay });
|
|
219
|
-
setTimeout(() => spawnWatcher(respawnReason), delay);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
child.on('error', (err) => {
|
|
223
|
-
logEvent('supervisor.spawn-error', { error: err.message, severity: 'critical' });
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function killChild(reason) {
|
|
228
|
-
if (!currentChildPid || !pidAlive(currentChildPid)) return;
|
|
229
|
-
killingForHeartbeat = true;
|
|
230
|
-
writeShutdownReason(reason, { uptime_ms: Date.now() - lastSpawnedAt });
|
|
231
|
-
try { process.kill(currentChildPid, 'SIGTERM'); } catch (_) {}
|
|
232
|
-
const pidAtKill = currentChildPid;
|
|
233
|
-
setTimeout(() => {
|
|
234
|
-
if (pidAtKill && pidAlive(pidAtKill)) {
|
|
235
|
-
logEvent('supervisor.sigkill-after-grace', { watcher_pid: pidAtKill, grace_ms: SIGTERM_GRACE_MS, severity: 'warn' });
|
|
236
|
-
if (process.platform === 'win32') {
|
|
237
|
-
try { spawnSync('taskkill', ['/F', '/T', '/PID', String(pidAtKill)], { stdio: 'ignore', windowsHide: true, timeout: 3000 }); } catch (_) {}
|
|
238
|
-
} else {
|
|
239
|
-
try { process.kill(pidAtKill, 'SIGKILL'); } catch (_) {}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}, SIGTERM_GRACE_MS);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function checkWatcherHealth() {
|
|
246
|
-
if (shuttingDown) return;
|
|
247
|
-
if (!currentChildPid) return;
|
|
248
|
-
if (!pidAlive(currentChildPid)) return;
|
|
249
|
-
const mtime = statusMtime();
|
|
250
|
-
if (mtime === 0) {
|
|
251
|
-
const age = Date.now() - lastSpawnedAt;
|
|
252
|
-
if (age > HEARTBEAT_STALE_MS) {
|
|
253
|
-
logEvent('supervisor.no-heartbeat-file', { watcher_pid: currentChildPid, age_since_spawn_ms: age, severity: 'critical' });
|
|
254
|
-
killChild('supervisor-killed-no-heartbeat');
|
|
255
|
-
}
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
const age = Date.now() - mtime;
|
|
259
|
-
if (age > HEARTBEAT_STALE_MS) {
|
|
260
|
-
logEvent('supervisor.heartbeat-stale', {
|
|
261
|
-
watcher_pid: currentChildPid,
|
|
262
|
-
status_age_ms: age,
|
|
263
|
-
stale_limit_ms: HEARTBEAT_STALE_MS,
|
|
264
|
-
severity: 'critical',
|
|
265
|
-
});
|
|
266
|
-
killChild('supervisor-killed-stale-heartbeat');
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
// A published wrapper-only fix (no wasm version bump) is copied to ~/.gm-tools by the next
|
|
270
|
-
// bootstrap's ensureWrapperFresh, but a healthy running watcher keeps the old wrapper until it
|
|
271
|
-
// restarts. Compare the watcher's reported wrapper_sha against the on-disk wrapper; on drift,
|
|
272
|
-
// recycle so the fix goes live without a manual kill. Skip while busy (a long verb is running).
|
|
273
|
-
const status = readStatus();
|
|
274
|
-
if (status && !(status.busy_until && status.busy_until > Date.now())) {
|
|
275
|
-
const reported = status.wrapper_sha || null;
|
|
276
|
-
const onDisk = wrapperSha12OnDisk();
|
|
277
|
-
if (reported && onDisk && reported !== onDisk) {
|
|
278
|
-
logEvent('supervisor.wrapper-sha-drift', {
|
|
279
|
-
watcher_pid: currentChildPid,
|
|
280
|
-
reported_sha: reported,
|
|
281
|
-
on_disk_sha: onDisk,
|
|
282
|
-
severity: 'info',
|
|
283
|
-
});
|
|
284
|
-
killChild('supervisor-killed-wrapper-sha-drift');
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
// The watcher reads the wasm's embedded instance_version at load and compares it to the
|
|
288
|
-
// plugkit.version text file (file_version), exposing version_drifted when they disagree.
|
|
289
|
-
// This catches a bumped version text sitting next to a stale wasm build (text claims 635
|
|
290
|
-
// while the binary embeds 634), which ensureReady's text-only drift check never re-downloads.
|
|
291
|
-
// Evict the stale cached wasm so the next bootstrap fails isReady() and redownloads, then recycle.
|
|
292
|
-
if (status.version_drifted === true) {
|
|
293
|
-
logEvent('supervisor.version-drift', {
|
|
294
|
-
watcher_pid: currentChildPid,
|
|
295
|
-
instance_version: status.instance_version || null,
|
|
296
|
-
file_version: status.file_version || null,
|
|
297
|
-
severity: 'critical',
|
|
298
|
-
});
|
|
299
|
-
try {
|
|
300
|
-
const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
301
|
-
const gmTools = fs.existsSync(path.join(home, '.gm-tools'))
|
|
302
|
-
? path.join(home, '.gm-tools')
|
|
303
|
-
: path.join(home, '.claude', 'gm-tools');
|
|
304
|
-
for (const f of ['plugkit.wasm', 'plugkit.version', 'plugkit.wasm.sha256']) {
|
|
305
|
-
try { fs.unlinkSync(path.join(gmTools, f)); } catch (_) {}
|
|
306
|
-
}
|
|
307
|
-
} catch (_) {}
|
|
308
|
-
killChild('supervisor-killed-version-drift');
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function shutdown(reason) {
|
|
314
|
-
if (shuttingDown) return;
|
|
315
|
-
shuttingDown = true;
|
|
316
|
-
logEvent('supervisor.shutdown', { reason });
|
|
317
|
-
writeSupervisorStatus('shutdown', { reason });
|
|
318
|
-
if (currentChildPid && pidAlive(currentChildPid)) {
|
|
319
|
-
writeShutdownReason('supervisor-graceful-shutdown', { trigger: reason, uptime_ms: Date.now() - lastSpawnedAt });
|
|
320
|
-
try { process.kill(currentChildPid, 'SIGTERM'); } catch (_) {}
|
|
321
|
-
const pidAtKill = currentChildPid;
|
|
322
|
-
const start = Date.now();
|
|
323
|
-
const waitInterval = setInterval(() => {
|
|
324
|
-
if (!pidAlive(pidAtKill)) {
|
|
325
|
-
clearInterval(waitInterval);
|
|
326
|
-
releaseSingleInstance();
|
|
327
|
-
process.exit(0);
|
|
328
|
-
} else if (Date.now() - start > SIGTERM_GRACE_MS) {
|
|
329
|
-
clearInterval(waitInterval);
|
|
330
|
-
if (process.platform === 'win32') {
|
|
331
|
-
try { spawnSync('taskkill', ['/F', '/T', '/PID', String(pidAtKill)], { stdio: 'ignore', windowsHide: true, timeout: 3000 }); } catch (_) {}
|
|
332
|
-
} else {
|
|
333
|
-
try { process.kill(pidAtKill, 'SIGKILL'); } catch (_) {}
|
|
334
|
-
}
|
|
335
|
-
releaseSingleInstance();
|
|
336
|
-
process.exit(0);
|
|
337
|
-
}
|
|
338
|
-
}, 200);
|
|
339
|
-
} else {
|
|
340
|
-
releaseSingleInstance();
|
|
341
|
-
process.exit(0);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
process.on('SIGINT', () => shutdown('sigint'));
|
|
346
|
-
process.on('SIGTERM', () => shutdown('sigterm'));
|
|
347
|
-
process.on('uncaughtException', (err) => {
|
|
348
|
-
logEvent('supervisor.uncaught', { error: err.message, stack: err.stack, severity: 'critical' });
|
|
349
|
-
shutdown('uncaught-exception');
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
if (!acquireSingleInstance()) {
|
|
353
|
-
process.exit(0);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
writeSupervisorStatus('starting', {});
|
|
357
|
-
logEvent('supervisor.starting', { spool_dir: spoolDir, heartbeat_stale_ms: HEARTBEAT_STALE_MS });
|
|
358
|
-
spawnWatcher('initial');
|
|
359
|
-
setInterval(checkWatcherHealth, HEALTH_POLL_MS);
|
|
360
|
-
setInterval(() => writeSupervisorStatus('watching', {}), SUPERVISOR_HEARTBEAT_MS);
|