@ddtcorex/dsh-maestro-supervisor 0.6.6 → 0.6.7

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.
@@ -217,20 +217,40 @@ function defaultWriteFile(p, c) {
217
217
  writeFileSync(p, c, 'utf-8');
218
218
  }
219
219
  async function defaultDryBoot() {
220
+ const { execSync } = await import('node:child_process');
221
+ const { resolveDeepseekHarnessDir } = await import('./paths.js');
222
+ const { buildKillStalePortsCommand } = await import('./restart-guards.js');
223
+ const port = Math.floor(19000 + Math.random() * 1000);
224
+ let tmp = '';
220
225
  try {
221
- const { execSync } = await import('node:child_process');
222
- const { resolveDeepseekHarnessDir } = await import('./paths.js');
223
226
  const deepseekDir = resolveDeepseekHarnessDir();
224
- const tmp = execSync('mktemp -d', { encoding: 'utf-8' }).trim();
225
- const port = Math.floor(19000 + Math.random() * 1000);
227
+ tmp = execSync('mktemp -d', { encoding: 'utf-8' }).trim();
226
228
  // Use spawn-based dry-boot to avoid nested quoting hell (prev \\$! / \\$(seq) caused syntax error)
227
- const out = execSync(`timeout 8 bash -c 'DSH_HOME=${tmp} pnpm --dir ${deepseekDir} dsh web --port ${port} --no-open >${tmp}/dsh.log 2>&1 & pid=$!; for i in $(seq 1 5); do sleep 1; if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${port}/ 2>&1 | grep -q 200; then kill $pid 2>/dev/null || true; wait $pid 2>/dev/null || true; echo ok; exit 0; fi; done; kill $pid 2>/dev/null || true; wait $pid 2>/dev/null || true; echo fail; exit 1'`, { encoding: 'utf-8', timeout: 12000 });
228
- execSync(`rm -rf ${tmp}`);
229
+ const out = execSync(`timeout 8 bash -c 'DSH_HOME=${tmp} pnpm --dir ${deepseekDir} dsh web --port ${port} --no-open >${tmp}/dsh.log 2>&1 & pid=$!; for i in $(seq 1 5); do sleep 1; if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${port}/ 2>&1 | grep -q 200; then echo ok; exit 0; fi; done; echo fail; exit 1'`, { encoding: 'utf-8', timeout: 12000 });
229
230
  return out.includes('ok');
230
231
  }
231
232
  catch {
232
233
  return false;
233
234
  }
235
+ finally {
236
+ // `pnpm dsh web` is a pnpm→sh→node tree (see AGENTS.md Known Issues); the
237
+ // backgrounded job's `$!` above is only the pnpm wrapper, so `kill $pid`
238
+ // never reached the real node process — it kept running detached,
239
+ // holding the ephemeral port. Confirmed live 2026-08-31: 3 failed
240
+ // attempts left 3 orphaned "MainThread" node processes eating 6.4G RAM
241
+ // for 7+ hours. Resolve the real pid by the port it is actually
242
+ // listening on instead (same technique as the live-restart kill step).
243
+ try {
244
+ execSync(buildKillStalePortsCommand([port]), { timeout: 5000, stdio: 'pipe' });
245
+ }
246
+ catch { }
247
+ if (tmp) {
248
+ try {
249
+ execSync(`rm -rf ${tmp}`);
250
+ }
251
+ catch { }
252
+ }
253
+ }
234
254
  }
235
255
  async function defaultFetchLLM(prompt) {
236
256
  const cfg = await resolveLLMConfig();
@@ -198,6 +198,30 @@ export async function pollHealth(opts = {}) {
198
198
  logTail: logContent.slice(-5000),
199
199
  };
200
200
  }
201
+ // Corroborate with a cheap port-liveness check before declaring a crash.
202
+ // The HTTP fetch shares dsh-web's own event loop, so a busy-but-alive
203
+ // process (e.g. heavy GitLab-webhook-triggered review work) times out
204
+ // the same way a genuinely dead one does — but a real crash always frees
205
+ // the port, while `ss` does not depend on the contended event loop to
206
+ // answer. Downgrade to DEGRADED (still visible, still escalates after
207
+ // repeated ticks) instead of forcing an immediate rollback + restartWeb
208
+ // that would kill an in-flight review for nothing.
209
+ let portAlive = false;
210
+ try {
211
+ portAlive = await psAliveFn();
212
+ }
213
+ catch {
214
+ portAlive = false;
215
+ }
216
+ if (portAlive) {
217
+ return {
218
+ up: true,
219
+ httpCode,
220
+ error: logError ? `${fetchError} + ${logError}` : fetchError,
221
+ degraded: true,
222
+ logTail: logContent.slice(-5000),
223
+ };
224
+ }
201
225
  return {
202
226
  up: false,
203
227
  httpCode,
package/lib/supervisor.js CHANGED
@@ -197,7 +197,7 @@ export class Supervisor {
197
197
  return cfg.downThreshold;
198
198
  }
199
199
  catch { }
200
- return 3;
200
+ return 5;
201
201
  }
202
202
  async findInterruptedRecent(withinMs) {
203
203
  const ms = withinMs ?? this.getResumeWithinMs();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddtcorex/dsh-maestro-supervisor",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "Supervisor daemon for DSH Web resilience — auto-detect crashes, rollback to LKG, report",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",