@delt/claude-alarm 1.4.0 → 1.4.1

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.
@@ -396,27 +396,48 @@ var defaultSpawn = (command, args) => {
396
396
  shellSpawns.add(child);
397
397
  return child;
398
398
  };
399
- var queryProcesses = () => new Promise((resolve, reject) => {
400
- const script = "@(Get-CimInstance Win32_Process | ForEach-Object { [pscustomobject]@{ pid = [int]$_.ProcessId; parentPid = [int]$_.ParentProcessId; creationTime = $_.CreationDate.ToUniversalTime().ToString('o') } }) | ConvertTo-Json -Compress";
401
- execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { windowsHide: true, timeout: 5e3, maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
402
- if (err) {
403
- reject(err);
404
- return;
405
- }
406
- try {
407
- const rows = JSON.parse(stdout);
408
- if (!Array.isArray(rows) || rows.some((row) => !Number.isInteger(row.pid) || !Number.isInteger(row.parentPid) || typeof row.creationTime !== "string")) {
409
- throw new Error("invalid process snapshot");
410
- }
411
- resolve(rows);
412
- } catch (error) {
413
- reject(error);
414
- }
399
+ var POWERSHELL_TIMEOUT_MS = 5e3;
400
+ var powershell = (script, env) => new Promise((resolve, reject) => {
401
+ execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { windowsHide: true, timeout: POWERSHELL_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024, env }, (err, stdout) => {
402
+ if (err) reject(err);
403
+ else resolve(stdout);
415
404
  });
416
405
  });
417
- var runFile = (file, args, options, callback) => {
418
- execFile(file, args, options, (err) => callback(err));
406
+ var QUERY_SCRIPT = "@(Get-CimInstance Win32_Process | Where-Object CreationDate | Select-Object @{ n = 'pid'; e = { [int]$_.ProcessId } }, @{ n = 'parentPid'; e = { [int]$_.ParentProcessId } }, @{ n = 'creationTime'; e = { $_.CreationDate.ToUniversalTime().ToString('o') } }) | ConvertTo-Json -Compress";
407
+ var processQuery = (prelude = "") => async () => {
408
+ const rows = JSON.parse(await powershell(prelude + QUERY_SCRIPT));
409
+ if (!Array.isArray(rows) || rows.some((row) => !Number.isInteger(row.pid) || !Number.isInteger(row.parentPid) || typeof row.creationTime !== "string")) {
410
+ throw new Error("invalid process snapshot");
411
+ }
412
+ return rows;
419
413
  };
414
+ var queryProcesses = processQuery();
415
+ var END_SCRIPT = [
416
+ "foreach ($t in ($env:CLAUDE_ALARM_END_TARGETS | ConvertFrom-Json)) {",
417
+ " $p = Get-Process -Id $t.pid -ErrorAction SilentlyContinue",
418
+ // Reading Handle opens the process once, and StartTime, Stop-Process and Wait-Process then use that handle; a getter that throws reads as $null.
419
+ ' if ($null -eq $p -or $null -eq $p.Handle) { "$($t.pid) is gone or cannot be opened"; continue }',
420
+ " $start = $p.StartTime",
421
+ ' if ($null -eq $start) { "$($t.pid) has no readable creation time"; continue }',
422
+ " $start = $start.ToUniversalTime()",
423
+ // Win32_Process creation dates stop at microseconds.
424
+ ` if ($start.AddTicks(-($start.Ticks % 10)).ToString('o') -ne $t.creationTime) { "$($t.pid) now belongs to another process"; continue }`,
425
+ ' try { Stop-Process -InputObject $p -Force -ErrorAction Stop } catch { "$($t.pid) not ended: $($_.FullyQualifiedErrorId)" }',
426
+ // A refused termination usually means the process is already exiting, so it is waited for all the same.
427
+ ' try { Wait-Process -InputObject $p -Timeout 1 -ErrorAction Stop } catch { "$($t.pid) has not exited yet" }',
428
+ "}"
429
+ ].join("\n");
430
+ var processEnder = (prelude = "") => async (targets) => {
431
+ await Promise.all(targets.map(async (target) => {
432
+ try {
433
+ const notes = await powershell(prelude + END_SCRIPT, { ...process.env, CLAUDE_ALARM_END_TARGETS: JSON.stringify([target]) });
434
+ for (const note of notes.split(/\r?\n/)) if (note) logger.debug(`codex process ${note}`);
435
+ } catch (err) {
436
+ logger.debug(`ending codex process ${target.pid} failed: ${err.killed ? "timed out" : String(err)}`);
437
+ }
438
+ }));
439
+ };
440
+ var endProcesses = processEnder();
420
441
  function descendantsOf(rootPid, rows) {
421
442
  const root = rows.find((row) => row.pid === rootPid);
422
443
  if (!root) return void 0;
@@ -438,16 +459,32 @@ function descendantsOf(rootPid, rows) {
438
459
  return found;
439
460
  }
440
461
  var exited = (child) => child.exitCode !== null || child.signalCode !== null;
441
- function treeKiller(platform = process.platform, run = runFile, query = queryProcesses, throughShell = spawnedThroughShell) {
442
- const recorded = /* @__PURE__ */ new WeakMap();
443
- const stop = (pid) => new Promise((resolve) => {
444
- run("taskkill", ["/PID", String(pid), "/F"], { windowsHide: true }, (err) => {
445
- if (err) logger.debug(`taskkill ${pid} failed: ${err.message}`);
446
- resolve();
447
- });
462
+ var SHELL_EXIT_WAIT_MS = 1e3;
463
+ var PROXY_TREE_CLOSE_BUDGET_MS = 2 * POWERSHELL_TIMEOUT_MS + SHELL_EXIT_WAIT_MS;
464
+ var exitOf = (child) => new Promise((resolve) => {
465
+ if (exited(child)) {
466
+ resolve();
467
+ return;
468
+ }
469
+ const timer = setTimeout(() => {
470
+ logger.debug(`codex proxy shell ${child.pid} has not exited yet`);
471
+ resolve();
472
+ }, SHELL_EXIT_WAIT_MS);
473
+ child.once("exit", () => {
474
+ clearTimeout(timer);
475
+ resolve();
448
476
  });
449
- const stopDeepestFirst = async (descendants) => {
450
- for (const descendant of [...descendants].sort((a, b) => b.depth - a.depth)) await stop(descendant.pid);
477
+ });
478
+ var merged = (before, found) => [...before, ...found.filter((d) => !before.some((b) => b.pid === d.pid && b.creationTime === d.creationTime))];
479
+ function treeKiller(platform = process.platform, end = endProcesses, query = queryProcesses, throughShell = spawnedThroughShell) {
480
+ const recorded = /* @__PURE__ */ new WeakMap();
481
+ const endByIdentity = async (descendants) => {
482
+ if (!descendants.length) return;
483
+ try {
484
+ await end([...descendants].sort((a, b) => b.depth - a.depth).map(({ depth, ...identity }) => identity));
485
+ } catch (err) {
486
+ logger.debug(`ending codex processes failed: ${String(err)}`);
487
+ }
451
488
  };
452
489
  const snapshot = async () => {
453
490
  try {
@@ -463,20 +500,12 @@ function treeKiller(platform = process.platform, run = runFile, query = queryPro
463
500
  if (!exited(child)) child.kill();
464
501
  return;
465
502
  }
466
- if (!exited(child)) {
467
- const rows = await snapshot();
468
- if (!exited(child)) {
469
- const descendants2 = rows && descendantsOf(child.pid, rows);
470
- if (descendants2) await stopDeepestFirst(descendants2);
471
- child.kill();
472
- return;
473
- }
474
- }
475
- const descendants = await recorded.get(child) ?? [];
476
- if (!descendants.length) return;
477
- const current = await snapshot();
478
- if (!current) return;
479
- await stopDeepestFirst(descendants.filter((d) => current.some((row) => row.pid === d.pid && row.creationTime === d.creationTime)));
503
+ const [rows, before] = await Promise.all([exited(child) ? void 0 : snapshot(), recorded.get(child)]);
504
+ const found = rows && !exited(child) ? descendantsOf(child.pid, rows) ?? [] : [];
505
+ await endByIdentity(merged(before ?? [], found));
506
+ if (exited(child)) return;
507
+ child.kill();
508
+ await exitOf(child);
480
509
  };
481
510
  killer.track = async (child) => {
482
511
  if (!isShellTree(child) || exited(child)) return;
@@ -484,8 +513,7 @@ function treeKiller(platform = process.platform, run = runFile, query = queryPro
484
513
  const latest = (async () => {
485
514
  const rows = await snapshot();
486
515
  const found = rows && !exited(child) ? descendantsOf(child.pid, rows) ?? [] : [];
487
- const before = await earlier ?? [];
488
- return [...before, ...found.filter((d) => !before.some((b) => b.pid === d.pid && b.creationTime === d.creationTime))];
516
+ return merged(await earlier ?? [], found);
489
517
  })();
490
518
  recorded.set(child, latest);
491
519
  await latest;
@@ -1296,6 +1324,31 @@ var CodexAdapter = class {
1296
1324
  }
1297
1325
  };
1298
1326
 
1327
+ // src/codex/shutdown.ts
1328
+ var ADAPTER_SHUTDOWN_MS = 25e3;
1329
+ function createAdapterShutdown(opts) {
1330
+ let exiting = false;
1331
+ const shutdown2 = () => {
1332
+ if (exiting) return;
1333
+ exiting = true;
1334
+ const timer = setTimeout(() => {
1335
+ logger.warn(`Codex adapter shutdown did not finish within ${ADAPTER_SHUTDOWN_MS}ms; exiting`);
1336
+ opts.exit();
1337
+ }, ADAPTER_SHUTDOWN_MS);
1338
+ const stopped = Promise.resolve().then(opts.stop);
1339
+ const released = Promise.resolve().then(opts.release);
1340
+ void Promise.allSettled([stopped, released]).then(() => {
1341
+ clearTimeout(timer);
1342
+ opts.exit();
1343
+ });
1344
+ };
1345
+ opts.signals.on("SIGINT", shutdown2);
1346
+ opts.signals.on("SIGTERM", shutdown2);
1347
+ opts.stdin?.on("end", shutdown2);
1348
+ opts.stdin?.on("close", shutdown2);
1349
+ return shutdown2;
1350
+ }
1351
+
1299
1352
  // src/shared/hub-url.ts
1300
1353
  import net from "net";
1301
1354
  function hubUrlHost(host) {
@@ -1518,22 +1571,14 @@ var supervised = process.argv.includes("--watch-stdin");
1518
1571
  var endpoint = controlEndpoint(CONFIG_DIR);
1519
1572
  var adapter;
1520
1573
  var release;
1521
- var exiting = false;
1522
- var shutdown = () => {
1523
- if (exiting) return;
1524
- exiting = true;
1525
- const stopped = adapter?.stop();
1526
- const exit = () => process.exit(0);
1527
- setTimeout(exit, 3e3).unref();
1528
- Promise.all([stopped, release?.()]).then(exit, exit);
1529
- };
1530
- process.on("SIGINT", shutdown);
1531
- process.on("SIGTERM", shutdown);
1532
- if (supervised) {
1533
- process.stdin.on("end", shutdown);
1534
- process.stdin.on("close", shutdown);
1535
- process.stdin.resume();
1536
- }
1574
+ var shutdown = createAdapterShutdown({
1575
+ signals: process,
1576
+ stdin: supervised ? process.stdin : void 0,
1577
+ stop: () => adapter?.stop(),
1578
+ release: () => release?.(),
1579
+ exit: () => process.exit(0)
1580
+ });
1581
+ if (supervised) process.stdin.resume();
1537
1582
  function send(message, then) {
1538
1583
  if (!process.send || !process.connected) {
1539
1584
  then?.();