@camstack/system 1.2.310 → 1.2.312

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.
Files changed (68) hide show
  1. package/dist/addon-runner.js +6 -8
  2. package/dist/addon-runner.mjs +6 -8
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/autotrack/index.js +1 -1
  10. package/dist/builtins/autotrack/index.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/camera-grid/index.js +1 -1
  14. package/dist/builtins/camera-grid/index.mjs +1 -1
  15. package/dist/builtins/composer/composer.addon.js +1 -1
  16. package/dist/builtins/composer/composer.addon.mjs +1 -1
  17. package/dist/builtins/console-logging/index.js +1 -1
  18. package/dist/builtins/console-logging/index.mjs +1 -1
  19. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  20. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  21. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  22. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  23. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  24. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  25. package/dist/builtins/hub-forwarder/index.js +1 -1
  26. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  27. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  28. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  29. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  30. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  31. package/dist/builtins/local-network/local-network.addon.js +1 -1
  32. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  33. package/dist/builtins/loki-logging/index.js +1 -1
  34. package/dist/builtins/loki-logging/index.mjs +1 -1
  35. package/dist/builtins/native-metrics/native-metrics.addon.js +10 -102
  36. package/dist/builtins/native-metrics/native-metrics.addon.mjs +4 -92
  37. package/dist/builtins/platform-probe/index.js +1 -1
  38. package/dist/builtins/platform-probe/index.mjs +1 -1
  39. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  40. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  41. package/dist/builtins/snapshot/index.js +1 -1
  42. package/dist/builtins/snapshot/index.mjs +1 -1
  43. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  44. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  45. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  46. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  47. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  48. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  49. package/dist/builtins/system-config/system-config.addon.js +1 -1
  50. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  51. package/dist/builtins/winston-logging/index.js +1 -1
  52. package/dist/builtins/winston-logging/index.mjs +1 -1
  53. package/dist/{child-cap-dispatch-BCJjUtXV.mjs → child-cap-dispatch-CHTiHbsv.mjs} +294 -10
  54. package/dist/{child-cap-dispatch-Be8e2cBP.js → child-cap-dispatch-Dyn_7ezH.js} +347 -9
  55. package/dist/{dist-B78pkrps.mjs → dist-Cj7pJmXq.mjs} +35 -4
  56. package/dist/{dist-Cy6DFFjs.js → dist-CkRwnhbo.js} +35 -4
  57. package/dist/index.js +88 -18
  58. package/dist/index.mjs +89 -20
  59. package/dist/kernel/index.d.ts +1 -0
  60. package/dist/kernel/moleculer/process-service.d.ts +9 -0
  61. package/dist/kernel/moleculer/runner-process-group.d.ts +92 -0
  62. package/dist/proc-stat-CbC55Kqi.mjs +92 -0
  63. package/dist/proc-stat-DL-WUOhl.js +127 -0
  64. package/dist/{resource-monitor-Vp0bPV3U.mjs → resource-monitor-B1v6E07R.mjs} +1 -1
  65. package/dist/{resource-monitor-LVE1BoLs.js → resource-monitor-DzNn_IZK.js} +5 -5
  66. package/dist/{retired-settings-keys-B4_hiWsm.mjs → retired-settings-keys-C1duPikd.mjs} +1 -1
  67. package/dist/{retired-settings-keys-D9sgD6Yg.js → retired-settings-keys-CGSuXhP8.js} +1 -1
  68. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
+ const require_proc_stat = require("./proc-stat-DL-WUOhl.js");
2
3
  require("./hub-cap-forward-DmHNjbB0.js");
3
4
  let node_crypto = require("node:crypto");
4
5
  node_crypto = require_chunk.__toESM(node_crypto);
@@ -3210,7 +3211,7 @@ function createAddonPeerBytesFacility(args) {
3210
3211
  * Failures THROW with an actionable message — a caller must never proceed as
3211
3212
  * if dependencies were installed when they were not.
3212
3213
  */
3213
- var execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
3214
+ var execFileAsync$3 = (0, node_util.promisify)(node_child_process.execFile);
3214
3215
  var DEFAULT_REGISTRY = "https://registry.npmjs.org";
3215
3216
  /** Where the bootstrap lands inside `cacheDir` (npm tgz carries a `package/` prefix). */
3216
3217
  function bootstrappedCliPath(cacheDir) {
@@ -3218,7 +3219,7 @@ function bootstrappedCliPath(cacheDir) {
3218
3219
  }
3219
3220
  async function defaultProbeSystemNpm() {
3220
3221
  try {
3221
- await execFileAsync$2("npm", ["--version"], { timeout: 15e3 });
3222
+ await execFileAsync$3("npm", ["--version"], { timeout: 15e3 });
3222
3223
  return true;
3223
3224
  } catch {
3224
3225
  return false;
@@ -3236,7 +3237,7 @@ async function defaultDownloadTarball(url, destTgz) {
3236
3237
  await node_fs.promises.writeFile(destTgz, bytes);
3237
3238
  }
3238
3239
  async function defaultExtractTarball(tgzPath, destDir) {
3239
- await execFileAsync$2("tar", [
3240
+ await execFileAsync$3("tar", [
3240
3241
  "-xzf",
3241
3242
  tgzPath,
3242
3243
  "-C",
@@ -3307,7 +3308,7 @@ async function runNpm(args, options) {
3307
3308
  registry: options.registry,
3308
3309
  logger: options.logger
3309
3310
  });
3310
- return execFileAsync$2(invocation.command, [...invocation.argsPrefix, ...args], {
3311
+ return execFileAsync$3(invocation.command, [...invocation.argsPrefix, ...args], {
3311
3312
  ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
3312
3313
  timeout: options.timeout ?? 3e5,
3313
3314
  env: buildNpmChildEnv(options.env ?? process.env, process.execPath)
@@ -3388,7 +3389,7 @@ function resolveAddonClass(mod) {
3388
3389
  }
3389
3390
  //#endregion
3390
3391
  //#region src/kernel/deps/manifest-native-deps.ts
3391
- var execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
3392
+ var execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
3392
3393
  /**
3393
3394
  * Fallback bootstrap-cache location when the caller doesn't thread one
3394
3395
  * through (the forked addon-runner's spawn-time install). tmpdir survives the
@@ -3697,7 +3698,7 @@ async function rebuildNativeDeps(addonDir, packageNames, logger, npmCacheDir, re
3697
3698
  return;
3698
3699
  }
3699
3700
  logger.warn("@electron/rebuild not available — falling back to npx", { meta: { addonDir } });
3700
- await execFileAsync$1("npx", [
3701
+ await execFileAsync$2("npx", [
3701
3702
  "--yes",
3702
3703
  "electron-rebuild",
3703
3704
  "-m",
@@ -12119,7 +12120,7 @@ function requestPrivilegedInstall(req, socketPath = pkgdSocketPath(), timeoutMs
12119
12120
  * the failure it precedes ("smbclient: ENOENT") arrives minutes later in a
12120
12121
  * different addon's logs.
12121
12122
  */
12122
- var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
12123
+ var execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
12123
12124
  /** How long a single package-manager invocation may take. */
12124
12125
  var INSTALL_TIMEOUT_MS = 300 * 1e3;
12125
12126
  /** How long the `command -v <binary>` presence probe may take. */
@@ -12281,7 +12282,7 @@ function planFor(manager, packageName) {
12281
12282
  }
12282
12283
  async function defaultHasBinary(binary) {
12283
12284
  try {
12284
- await execFileAsync("command", ["-v", binary], {
12285
+ await execFileAsync$1("command", ["-v", binary], {
12285
12286
  timeout: PROBE_TIMEOUT_MS,
12286
12287
  shell: process.platform === "win32" ? false : "/bin/sh"
12287
12288
  });
@@ -12301,7 +12302,290 @@ async function defaultDetectManager() {
12301
12302
  return null;
12302
12303
  }
12303
12304
  async function defaultRun(command, args) {
12304
- await execFileAsync(command, [...args], { timeout: INSTALL_TIMEOUT_MS });
12305
+ await execFileAsync$1(command, [...args], { timeout: INSTALL_TIMEOUT_MS });
12306
+ }
12307
+ //#endregion
12308
+ //#region src/kernel/moleculer/runner-process-group.ts
12309
+ /**
12310
+ * A runner OWNS a process group, and the kernel reaps it on every runner exit (D668).
12311
+ *
12312
+ * ## Why
12313
+ *
12314
+ * An addon-runner spawns long-lived children: recorder ffmpeg writers, stream-broker
12315
+ * ffmpeg, llama-server, tailscaled, cloudflared, HAP ffmpeg. Their only kill was the
12316
+ * addon's own graceful `stop()`, which a runner that dies of SIGABRT (V8 OOM), SIGKILL
12317
+ * or a native crash never runs. ffmpeg ignores SIGPIPE and does not quit on stdin EOF,
12318
+ * so a dead reader does not stop it either. On 2026-09-28 at 10:21 CEST the hub's
12319
+ * recorder runner aborted; 28 ffmpeg writers survived as orphans and kept writing into
12320
+ * the SAME staging directories the respawned runner's 28 new writers used — 13 minutes
12321
+ * of dual writers on every camera, until they were killed by hand.
12322
+ *
12323
+ * Runners were spawned `detached: false`, i.e. inside hub-main's own group, so there
12324
+ * was no handle meaning "this runner and everything under it". Now every runner is
12325
+ * spawned `detached: true` on POSIX, which makes it a SESSION and group leader
12326
+ * (`setsid`): pgid === runner pid, and every descendant that does not itself call
12327
+ * `setsid`/`setpgid` inherits that group. A process group outlives its leader, so
12328
+ * `kill(-pgid)` still reaches the orphans after the runner is gone — which is exactly
12329
+ * the moment it matters.
12330
+ *
12331
+ * ## What `detached` does NOT change
12332
+ *
12333
+ * - The child stays REF'd: nothing calls `unref()`, so the parent still waits on it,
12334
+ * still gets its `exit`, still reads its stdout/stderr pipes.
12335
+ * - `process.ppid` is unchanged, so the runner can detect a lost parent before or
12336
+ * after READY and hard-stop its own group.
12337
+ * - UDS/stdio wiring is fd-based and independent of the session.
12338
+ *
12339
+ * What it DOES change: the runner is no longer in hub-main's group, so a signal sent to
12340
+ * hub-main's group (`kill -- -<pid>` in `server/backend/src/main.ts`, a terminal Ctrl+C)
12341
+ * no longer reaches runners by itself. `signalAllRunnerGroups` is the replacement, and
12342
+ * the process `exit` backstop below hard-stops any groups a hub/agent still owns.
12343
+ *
12344
+ * ## Out of scope, deliberately
12345
+ *
12346
+ * - Windows has no process groups in this sense; nothing here runs on `win32` and the
12347
+ * runner is spawned exactly as before.
12348
+ * - A descendant that calls `setsid`/`setpgid` itself (or is spawned `detached: true`
12349
+ * by an addon) leaves the group and is NOT reaped. No shipped addon does; one that
12350
+ * needs to must own its child's lifetime some other way.
12351
+ * - The kernel knows the runner id, not which device a child served. Per-device
12352
+ * attribution is the addon's (the recorder's own foreign-writer reap, D667).
12353
+ */
12354
+ var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
12355
+ /** SIGTERM → SIGKILL grace for a runner's group, in ms. ffmpeg finalises a segment well inside it. */
12356
+ var RUNNER_GROUP_REAP_GRACE_MS = 2e3;
12357
+ /** How often the group is probed for emptiness during the grace. */
12358
+ var GROUP_PROBE_INTERVAL_MS = 100;
12359
+ /** Whether a runner is spawned as a process-group leader on this platform. */
12360
+ function runnerOwnsProcessGroup(platform = process.platform) {
12361
+ return platform !== "win32";
12362
+ }
12363
+ function errnoOf(err) {
12364
+ if (typeof err !== "object" || err === null) return void 0;
12365
+ const code = Reflect.get(err, "code");
12366
+ return typeof code === "string" ? code : void 0;
12367
+ }
12368
+ function signalGroup(ops, pgid, signal) {
12369
+ try {
12370
+ ops.kill(-pgid, signal);
12371
+ return "delivered";
12372
+ } catch (err) {
12373
+ if (errnoOf(err) === "ESRCH") return "empty";
12374
+ return { error: `${errnoOf(err) ?? "unknown"}: ${err instanceof Error ? err.message : String(err)}` };
12375
+ }
12376
+ }
12377
+ function signalPid(ops, pid) {
12378
+ try {
12379
+ ops.kill(pid, 0);
12380
+ return "delivered";
12381
+ } catch (err) {
12382
+ return errnoOf(err) === "ESRCH" ? "empty" : { error: errnoOf(err) ?? "unknown" };
12383
+ }
12384
+ }
12385
+ /**
12386
+ * A pgid this module may address. `kill(-1)` signals every process the caller may
12387
+ * signal and `kill(-0)` is the caller's OWN group — both are catastrophes, not reaps.
12388
+ */
12389
+ function isAddressableGroup(pgid) {
12390
+ return Number.isInteger(pgid) && pgid > 1 && pgid !== process.pid;
12391
+ }
12392
+ /**
12393
+ * SIGTERM the whole group, wait up to `graceMs` for it to empty, SIGKILL what is left.
12394
+ *
12395
+ * pid reuse: POSIX never hands out a pid while a process group with that id exists, so
12396
+ * the SIGTERM always lands on our group. The SIGKILL is sent only when the group is
12397
+ * still populated at the end of the grace, which is the same group; the residual window
12398
+ * (group empties, the pid wraps around AND is reused as a new group leader, all inside
12399
+ * the grace) is not reachable in practice.
12400
+ */
12401
+ async function reapProcessGroup(pgid, ops) {
12402
+ if (!isAddressableGroup(pgid)) return {
12403
+ pgid,
12404
+ membersAtExit: null,
12405
+ killed: null,
12406
+ alreadyEmpty: true,
12407
+ error: `refused pgid ${pgid}`
12408
+ };
12409
+ const membersAtExit = await ops.listMembers(pgid);
12410
+ const term = signalGroup(ops, pgid, "SIGTERM");
12411
+ if (term === "empty") return {
12412
+ pgid,
12413
+ membersAtExit,
12414
+ killed: [],
12415
+ alreadyEmpty: true,
12416
+ error: null
12417
+ };
12418
+ if (typeof term === "object") return {
12419
+ pgid,
12420
+ membersAtExit,
12421
+ killed: null,
12422
+ alreadyEmpty: false,
12423
+ error: term.error
12424
+ };
12425
+ for (let waited = 0; waited < ops.graceMs; waited += GROUP_PROBE_INTERVAL_MS) {
12426
+ await ops.sleep(Math.min(GROUP_PROBE_INTERVAL_MS, ops.graceMs - waited));
12427
+ if (signalGroup(ops, pgid, 0) === "empty") return {
12428
+ pgid,
12429
+ membersAtExit,
12430
+ killed: [],
12431
+ alreadyEmpty: false,
12432
+ error: null
12433
+ };
12434
+ }
12435
+ const survivors = membersAtExit === null ? null : membersAtExit.filter((m) => signalPid(ops, m.pid) === "delivered");
12436
+ const kill = signalGroup(ops, pgid, "SIGKILL");
12437
+ return {
12438
+ pgid,
12439
+ membersAtExit,
12440
+ killed: kill === "empty" ? [] : survivors,
12441
+ alreadyEmpty: false,
12442
+ error: typeof kill === "object" ? kill.error : null
12443
+ };
12444
+ }
12445
+ /** `ffmpeg×28 llama-server×1` — what a reap line says it removed. */
12446
+ function summarizeMembers(members) {
12447
+ const counts = /* @__PURE__ */ new Map();
12448
+ for (const m of members) counts.set(m.command, (counts.get(m.command) ?? 0) + 1);
12449
+ return [...counts.entries()].map(([command, n]) => `${command}×${n}`).join(" ");
12450
+ }
12451
+ async function listMembersFromProc(pgid, reader) {
12452
+ const pids = await reader.listPids();
12453
+ return (await Promise.all(pids.map(async (pid) => {
12454
+ try {
12455
+ return require_proc_stat.parseProcStat(await reader.readStat(pid));
12456
+ } catch {
12457
+ return null;
12458
+ }
12459
+ }))).flatMap((row) => row !== null && row.pgid === pgid && row.state !== "Z" ? [{
12460
+ pid: row.pid,
12461
+ command: row.comm
12462
+ }] : []);
12463
+ }
12464
+ var PS_GROUP_ROW_RE = /^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/;
12465
+ /** Parse `ps -A -o pid=,pgid=,stat=,comm=` for one group, zombies excluded. */
12466
+ function parsePsGroupMembers(stdout, pgid) {
12467
+ const members = [];
12468
+ for (const line of stdout.split("\n")) {
12469
+ const match = line.trim().match(PS_GROUP_ROW_RE);
12470
+ if (!match) continue;
12471
+ const [, pidS, pgidS, stat = "", comm = ""] = match;
12472
+ if (Number(pgidS) !== pgid || stat.startsWith("Z")) continue;
12473
+ const command = comm.trim();
12474
+ members.push({
12475
+ pid: Number(pidS),
12476
+ command: command.slice(command.lastIndexOf("/") + 1)
12477
+ });
12478
+ }
12479
+ return members;
12480
+ }
12481
+ async function listMembersFromPs(pgid) {
12482
+ const { stdout } = await execFileAsync("ps", [
12483
+ "-A",
12484
+ "-o",
12485
+ "pid=,pgid=,stat=,comm="
12486
+ ], {
12487
+ timeout: 3e3,
12488
+ maxBuffer: 8 * 1024 * 1024
12489
+ });
12490
+ return parsePsGroupMembers(stdout, pgid);
12491
+ }
12492
+ /** Live members of `pgid`: `/proc` on Linux, `ps` elsewhere; `null` when neither answers. */
12493
+ async function listProcessGroupMembers(pgid, platform = process.platform) {
12494
+ try {
12495
+ return platform === "linux" ? await listMembersFromProc(pgid, require_proc_stat.nodeProcReader) : await listMembersFromPs(pgid);
12496
+ } catch {
12497
+ return null;
12498
+ }
12499
+ }
12500
+ var defaultProcessGroupOps = {
12501
+ kill: (pid, signal) => {
12502
+ process.kill(pid, signal);
12503
+ },
12504
+ listMembers: (pgid) => listProcessGroupMembers(pgid),
12505
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
12506
+ graceMs: RUNNER_GROUP_REAP_GRACE_MS
12507
+ };
12508
+ /**
12509
+ * Every runner group this process leads and has not finished reaping. Process-wide on
12510
+ * purpose: `process.on('exit')` and hub-main's signal handler must reach the groups of
12511
+ * every `$process` service in the process, and neither holds a service reference.
12512
+ */
12513
+ var ownedGroups = /* @__PURE__ */ new Map();
12514
+ var exitBackstopInstalled = false;
12515
+ function installExitBackstop() {
12516
+ if (exitBackstopInstalled) return;
12517
+ exitBackstopInstalled = true;
12518
+ process.on("exit", () => {
12519
+ for (const [pgid, group] of ownedGroups) try {
12520
+ group.kill(-pgid, "SIGKILL");
12521
+ } catch {}
12522
+ });
12523
+ }
12524
+ function registerRunnerGroup(pgid, group) {
12525
+ if (!isAddressableGroup(pgid)) return;
12526
+ ownedGroups.set(pgid, group);
12527
+ installExitBackstop();
12528
+ }
12529
+ function releaseRunnerGroup(pgid) {
12530
+ ownedGroups.delete(pgid);
12531
+ }
12532
+ /**
12533
+ * Signal every runner group this process owns, synchronously. Hub-main's SIGTERM/SIGINT
12534
+ * handler calls it: before D668 the runners were in hub-main's own group and its
12535
+ * `kill -- -<pid>` reached them and their children; now each is its own group.
12536
+ * Returns how many groups were signalled.
12537
+ */
12538
+ function signalAllRunnerGroups(signal) {
12539
+ let signalled = 0;
12540
+ for (const [pgid, group] of ownedGroups) try {
12541
+ group.kill(-pgid, signal);
12542
+ signalled += 1;
12543
+ } catch {}
12544
+ return signalled;
12545
+ }
12546
+ /**
12547
+ * Arm the runner's parent-death watchdog before addon code starts loading.
12548
+ *
12549
+ * A parent can die while an addon module or `initialize()` is still pending, so
12550
+ * installing this only after READY leaves exactly the boot window where a detached
12551
+ * runner can become an orphan. Any PPID change means the direct parent died and the
12552
+ * kernel reparented us; checking for a change also covers a non-PID-1 subreaper.
12553
+ */
12554
+ function startRunnerParentDeathWatchdog(onParentDeath, options = {}) {
12555
+ const initialPpid = options.initialPpid ?? process.ppid;
12556
+ if (!Number.isInteger(initialPpid) || initialPpid <= 1) return null;
12557
+ const readPpid = options.readPpid ?? (() => process.ppid);
12558
+ const intervalMs = options.intervalMs ?? 5e3;
12559
+ let fired = false;
12560
+ const timer = setInterval(() => {
12561
+ if (fired || readPpid() === initialPpid) return;
12562
+ fired = true;
12563
+ clearInterval(timer);
12564
+ onParentDeath();
12565
+ }, intervalMs);
12566
+ return timer;
12567
+ }
12568
+ /**
12569
+ * Last resort for a detached runner whose direct parent disappeared without an
12570
+ * `exit` hook (SIGKILL, native crash). The runner's own pid is also its pgid, so
12571
+ * one SIGKILL removes the runner and every descendant in that group atomically.
12572
+ *
12573
+ * Calling `kill(-process.pid, ...)` from an ordinary, non-group-leading process
12574
+ * returns ESRCH: while this pid exists, no unrelated process group can have the
12575
+ * same id. That makes the failed call a safe compatibility fallback for an older
12576
+ * parent that did not spawn the runner detached.
12577
+ */
12578
+ function killOwnRunnerGroupAfterParentLoss(platform = process.platform, kill = (target, signal) => {
12579
+ process.kill(target, signal);
12580
+ }) {
12581
+ const pid = process.pid;
12582
+ if (!runnerOwnsProcessGroup(platform) || !Number.isInteger(pid) || pid <= 1) return false;
12583
+ try {
12584
+ kill(-pid, "SIGKILL");
12585
+ return true;
12586
+ } catch {
12587
+ return false;
12588
+ }
12305
12589
  }
12306
12590
  //#endregion
12307
12591
  //#region src/kernel/moleculer/child-cap-dispatch.ts
@@ -12942,6 +13226,12 @@ Object.defineProperty(exports, "createV8Reclaimer", {
12942
13226
  return createV8Reclaimer;
12943
13227
  }
12944
13228
  });
13229
+ Object.defineProperty(exports, "defaultProcessGroupOps", {
13230
+ enumerable: true,
13231
+ get: function() {
13232
+ return defaultProcessGroupOps;
13233
+ }
13234
+ });
12945
13235
  Object.defineProperty(exports, "describeRss", {
12946
13236
  enumerable: true,
12947
13237
  get: function() {
@@ -13122,6 +13412,12 @@ Object.defineProperty(exports, "isArrayBufferCensusReport", {
13122
13412
  return isArrayBufferCensusReport;
13123
13413
  }
13124
13414
  });
13415
+ Object.defineProperty(exports, "killOwnRunnerGroupAfterParentLoss", {
13416
+ enumerable: true,
13417
+ get: function() {
13418
+ return killOwnRunnerGroupAfterParentLoss;
13419
+ }
13420
+ });
13125
13421
  Object.defineProperty(exports, "localEndpointPath", {
13126
13422
  enumerable: true,
13127
13423
  get: function() {
@@ -13176,6 +13472,12 @@ Object.defineProperty(exports, "readSocketQueues", {
13176
13472
  return readSocketQueues;
13177
13473
  }
13178
13474
  });
13475
+ Object.defineProperty(exports, "reapProcessGroup", {
13476
+ enumerable: true,
13477
+ get: function() {
13478
+ return reapProcessGroup;
13479
+ }
13480
+ });
13179
13481
  Object.defineProperty(exports, "reclaimIntervalMs", {
13180
13482
  enumerable: true,
13181
13483
  get: function() {
@@ -13194,6 +13496,18 @@ Object.defineProperty(exports, "registerEventBusService", {
13194
13496
  return registerEventBusService;
13195
13497
  }
13196
13498
  });
13499
+ Object.defineProperty(exports, "registerRunnerGroup", {
13500
+ enumerable: true,
13501
+ get: function() {
13502
+ return registerRunnerGroup;
13503
+ }
13504
+ });
13505
+ Object.defineProperty(exports, "releaseRunnerGroup", {
13506
+ enumerable: true,
13507
+ get: function() {
13508
+ return releaseRunnerGroup;
13509
+ }
13510
+ });
13197
13511
  Object.defineProperty(exports, "resolveAddonClass", {
13198
13512
  enumerable: true,
13199
13513
  get: function() {
@@ -13218,6 +13532,12 @@ Object.defineProperty(exports, "runNpm", {
13218
13532
  return runNpm;
13219
13533
  }
13220
13534
  });
13535
+ Object.defineProperty(exports, "runnerOwnsProcessGroup", {
13536
+ enumerable: true,
13537
+ get: function() {
13538
+ return runnerOwnsProcessGroup;
13539
+ }
13540
+ });
13221
13541
  Object.defineProperty(exports, "sampleSocketDirection", {
13222
13542
  enumerable: true,
13223
13543
  get: function() {
@@ -13260,6 +13580,12 @@ Object.defineProperty(exports, "shouldReclaim", {
13260
13580
  return shouldReclaim;
13261
13581
  }
13262
13582
  });
13583
+ Object.defineProperty(exports, "signalAllRunnerGroups", {
13584
+ enumerable: true,
13585
+ get: function() {
13586
+ return signalAllRunnerGroups;
13587
+ }
13588
+ });
13263
13589
  Object.defineProperty(exports, "socketDirectionBytes", {
13264
13590
  enumerable: true,
13265
13591
  get: function() {
@@ -13284,6 +13610,12 @@ Object.defineProperty(exports, "startRunnerHeapWatch", {
13284
13610
  return startRunnerHeapWatch;
13285
13611
  }
13286
13612
  });
13613
+ Object.defineProperty(exports, "startRunnerParentDeathWatchdog", {
13614
+ enumerable: true,
13615
+ get: function() {
13616
+ return startRunnerParentDeathWatchdog;
13617
+ }
13618
+ });
13287
13619
  Object.defineProperty(exports, "strandedMb", {
13288
13620
  enumerable: true,
13289
13621
  get: function() {
@@ -13296,6 +13628,12 @@ Object.defineProperty(exports, "subscribePassthrough", {
13296
13628
  return subscribePassthrough;
13297
13629
  }
13298
13630
  });
13631
+ Object.defineProperty(exports, "summarizeMembers", {
13632
+ enumerable: true,
13633
+ get: function() {
13634
+ return summarizeMembers;
13635
+ }
13636
+ });
13299
13637
  Object.defineProperty(exports, "udsChildLogToWorkerEntry", {
13300
13638
  enumerable: true,
13301
13639
  get: function() {
@@ -21776,7 +21776,15 @@ var RebuildObjectEmbeddingsInput = z.object({
21776
21776
  * force is logged at start and finish so a deliberately slow pass reads
21777
21777
  * differently from a stalled one.
21778
21778
  */
21779
- pacingMs: z.number().int().nonnegative().optional()
21779
+ pacingMs: z.number().int().nonnegative().optional(),
21780
+ /**
21781
+ * Re-embed ONLY the tracks whose vector is ALREADY in the pinned model's
21782
+ * space (D664) — the in-space skip inverted. It exists to redo vectors
21783
+ * written from the wrong box, which a normal pass would skip as done
21784
+ * forever. Requires `maxTracks`: a forced pass without a bound is refused
21785
+ * (`force-requires-max-tracks`).
21786
+ */
21787
+ force: z.boolean().optional()
21780
21788
  });
21781
21789
  /**
21782
21790
  * Result of emptying the CLIP index.
@@ -21812,6 +21820,7 @@ var WipeObjectEmbeddingsResultSchema = z.object({
21812
21820
  * - `reads-disagree` — the two reads named different choices.
21813
21821
  * - `not-a-clip-model` — the CONFIRMED id has no CLIP metadata, so there is no
21814
21822
  * index to fill and no dimension to check against (D649).
21823
+ * - `force-requires-max-tracks` — `force: true` with no `maxTracks` (D664).
21815
21824
  */
21816
21825
  var RebuildObjectEmbeddingsRefusalSchema = z.enum([
21817
21826
  "stores-unavailable",
@@ -21819,7 +21828,8 @@ var RebuildObjectEmbeddingsRefusalSchema = z.enum([
21819
21828
  "method-unavailable",
21820
21829
  "read-failed",
21821
21830
  "reads-disagree",
21822
- "not-a-clip-model"
21831
+ "not-a-clip-model",
21832
+ "force-requires-max-tracks"
21823
21833
  ]);
21824
21834
  /**
21825
21835
  * Acknowledgement that a rebuild STARTED.
@@ -21844,11 +21854,32 @@ var RebuildStatusSchema = z.object({
21844
21854
  running: z.boolean(),
21845
21855
  scanned: z.number(),
21846
21856
  rebuilt: z.number(),
21847
- /** Tracks whose key frame is gone — nothing to re-embed from. */
21857
+ /** Tracks whose key frame is gone — nothing to re-embed from. Pixels ONLY:
21858
+ * `missingEvent` is its own field since D664, never summed in. */
21848
21859
  missingKeyFrame: z.number(),
21849
- /** Tracks with no usable detection box. */
21860
+ /** Tracks with no object event at all (D664). */
21861
+ missingEvent: z.number(),
21862
+ /** Tracks with no box measured on the pixels to cut — see `boxSource.refused`. */
21850
21863
  missingBbox: z.number(),
21851
21864
  /**
21865
+ * Which rule placed each judged track's box (D664): the key frame row's own
21866
+ * `subjectBox`, `track.positions` at exactly its instant (older key frames),
21867
+ * the event box (a key frame recovered from the recording, at the event's
21868
+ * instant), or `refused` by name — the reasons are in the tagged log lines.
21869
+ */
21870
+ boxSource: z.object({
21871
+ subjectBox: z.number(),
21872
+ positions: z.number(),
21873
+ event: z.number(),
21874
+ refused: z.number()
21875
+ }),
21876
+ /** Tracks skipped because their vector is already in the pinned space. */
21877
+ alreadyInSpace: z.number(),
21878
+ /** Whether the current / last pass was a `force` pass; `null` before any (D664). */
21879
+ force: z.boolean().nullable(),
21880
+ /** In-space tracks a `force` pass selected for re-embedding. */
21881
+ forcedInSpace: z.number(),
21882
+ /**
21852
21883
  * Tracks an executing node REFUSED rather than broke on — an unreadable key
21853
21884
  * frame, a step that threw. Separate from `failed` because the remedy is
21854
21885
  * different, and because a whole camera silently contributing zero vectors
@@ -21776,7 +21776,15 @@ var RebuildObjectEmbeddingsInput = zod.z.object({
21776
21776
  * force is logged at start and finish so a deliberately slow pass reads
21777
21777
  * differently from a stalled one.
21778
21778
  */
21779
- pacingMs: zod.z.number().int().nonnegative().optional()
21779
+ pacingMs: zod.z.number().int().nonnegative().optional(),
21780
+ /**
21781
+ * Re-embed ONLY the tracks whose vector is ALREADY in the pinned model's
21782
+ * space (D664) — the in-space skip inverted. It exists to redo vectors
21783
+ * written from the wrong box, which a normal pass would skip as done
21784
+ * forever. Requires `maxTracks`: a forced pass without a bound is refused
21785
+ * (`force-requires-max-tracks`).
21786
+ */
21787
+ force: zod.z.boolean().optional()
21780
21788
  });
21781
21789
  /**
21782
21790
  * Result of emptying the CLIP index.
@@ -21812,6 +21820,7 @@ var WipeObjectEmbeddingsResultSchema = zod.z.object({
21812
21820
  * - `reads-disagree` — the two reads named different choices.
21813
21821
  * - `not-a-clip-model` — the CONFIRMED id has no CLIP metadata, so there is no
21814
21822
  * index to fill and no dimension to check against (D649).
21823
+ * - `force-requires-max-tracks` — `force: true` with no `maxTracks` (D664).
21815
21824
  */
21816
21825
  var RebuildObjectEmbeddingsRefusalSchema = zod.z.enum([
21817
21826
  "stores-unavailable",
@@ -21819,7 +21828,8 @@ var RebuildObjectEmbeddingsRefusalSchema = zod.z.enum([
21819
21828
  "method-unavailable",
21820
21829
  "read-failed",
21821
21830
  "reads-disagree",
21822
- "not-a-clip-model"
21831
+ "not-a-clip-model",
21832
+ "force-requires-max-tracks"
21823
21833
  ]);
21824
21834
  /**
21825
21835
  * Acknowledgement that a rebuild STARTED.
@@ -21844,11 +21854,32 @@ var RebuildStatusSchema = zod.z.object({
21844
21854
  running: zod.z.boolean(),
21845
21855
  scanned: zod.z.number(),
21846
21856
  rebuilt: zod.z.number(),
21847
- /** Tracks whose key frame is gone — nothing to re-embed from. */
21857
+ /** Tracks whose key frame is gone — nothing to re-embed from. Pixels ONLY:
21858
+ * `missingEvent` is its own field since D664, never summed in. */
21848
21859
  missingKeyFrame: zod.z.number(),
21849
- /** Tracks with no usable detection box. */
21860
+ /** Tracks with no object event at all (D664). */
21861
+ missingEvent: zod.z.number(),
21862
+ /** Tracks with no box measured on the pixels to cut — see `boxSource.refused`. */
21850
21863
  missingBbox: zod.z.number(),
21851
21864
  /**
21865
+ * Which rule placed each judged track's box (D664): the key frame row's own
21866
+ * `subjectBox`, `track.positions` at exactly its instant (older key frames),
21867
+ * the event box (a key frame recovered from the recording, at the event's
21868
+ * instant), or `refused` by name — the reasons are in the tagged log lines.
21869
+ */
21870
+ boxSource: zod.z.object({
21871
+ subjectBox: zod.z.number(),
21872
+ positions: zod.z.number(),
21873
+ event: zod.z.number(),
21874
+ refused: zod.z.number()
21875
+ }),
21876
+ /** Tracks skipped because their vector is already in the pinned space. */
21877
+ alreadyInSpace: zod.z.number(),
21878
+ /** Whether the current / last pass was a `force` pass; `null` before any (D664). */
21879
+ force: zod.z.boolean().nullable(),
21880
+ /** In-space tracks a `force` pass selected for re-embedding. */
21881
+ forcedInSpace: zod.z.number(),
21882
+ /**
21852
21883
  * Tracks an executing node REFUSED rather than broke on — an unreadable key
21853
21884
  * frame, a step that threw. Separate from `failed` because the remedy is
21854
21885
  * different, and because a whole camera silently contributing zero vectors