camstack 1.2.135 → 1.2.137

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.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-DYWqXngV.js
23637
- var require_dist_DYWqXngV = __commonJS({
23638
- "../system/dist/dist-DYWqXngV.js"(exports) {
23636
+ // ../system/dist/dist-BOlMCOeT.js
23637
+ var require_dist_BOlMCOeT = __commonJS({
23638
+ "../system/dist/dist-BOlMCOeT.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -30499,6 +30499,16 @@ var require_dist_DYWqXngV = __commonJS({
30499
30499
  })) }), { auth: "admin" })
30500
30500
  }
30501
30501
  };
30502
+ function describeCustomActions(catalog) {
30503
+ const out = {};
30504
+ for (const [name, spec] of Object.entries(catalog)) out[name] = {
30505
+ kind: spec.kind,
30506
+ auth: spec.auth,
30507
+ scope: spec.scope,
30508
+ ...spec.caller === "required" ? { caller: "required" } : {}
30509
+ };
30510
+ return out;
30511
+ }
30502
30512
  var CustomModelDescriptorSchema = zod.z.object({
30503
30513
  stepId: zod.z.string(),
30504
30514
  entry: ModelCatalogEntrySchema
@@ -35018,6 +35028,7 @@ var require_dist_DYWqXngV = __commonJS({
35018
35028
  "addon-crash-loop",
35019
35029
  "addon-update-available",
35020
35030
  "server-update-available",
35031
+ "wrapper-update-available",
35021
35032
  "alarm-triggered",
35022
35033
  "alarm-armed",
35023
35034
  "alarm-disarmed",
@@ -37878,7 +37889,17 @@ var require_dist_DYWqXngV = __commonJS({
37878
37889
  bucketStart: zod.z.number(),
37879
37890
  motion: zod.z.number().int(),
37880
37891
  object: zod.z.number().int(),
37881
- audio: zod.z.number().int()
37892
+ audio: zod.z.number().int(),
37893
+ /**
37894
+ * Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
37895
+ * as opposed to `audio`, which is their COUNT (D431).
37896
+ *
37897
+ * ABSENT when the bucket holds no audio event. Absence means "nothing was
37898
+ * measured", which is not the same claim as silence, and a lane that cannot
37899
+ * tell them apart paints a quiet hour at full scale — which is exactly what
37900
+ * the count-on-a-level-meter did.
37901
+ */
37902
+ audioDbfs: zod.z.number().optional()
37882
37903
  });
37883
37904
  var EventDensityForDeviceSchema = zod.z.object({
37884
37905
  deviceId: zod.z.number(),
@@ -40682,6 +40703,45 @@ var require_dist_DYWqXngV = __commonJS({
40682
40703
  /** One operator-grade sentence: this node runs image X; the contract says Y. */
40683
40704
  message: zod.z.string()
40684
40705
  });
40706
+ var WrapperKindSchema = zod.z.enum([
40707
+ "docker",
40708
+ "electron",
40709
+ "native",
40710
+ "contradictory",
40711
+ "unknown"
40712
+ ]);
40713
+ var WrapperEvidenceSchema = zod.z.object({
40714
+ /** Which probe this row reports. */
40715
+ fact: zod.z.enum([
40716
+ "dockerenv-file",
40717
+ "proc-1-cgroup",
40718
+ "platform",
40719
+ "electron-app-version-env"
40720
+ ]),
40721
+ /**
40722
+ * `observed` — this process read it itself. `declared` — the shell told the
40723
+ * process and nothing here can check it. Only the Electron app version is
40724
+ * ever `declared`, and only because a child cannot see its parent.
40725
+ */
40726
+ mode: zod.z.enum(["observed", "declared"]),
40727
+ /** Did the probe support the fact it names? A `false` row is still evidence. */
40728
+ holds: zod.z.boolean(),
40729
+ /** What was actually read, verbatim enough for an operator to argue with. */
40730
+ detail: zod.z.string()
40731
+ });
40732
+ var WrapperIdentitySchema = zod.z.object({
40733
+ kind: WrapperKindSchema,
40734
+ /** Every probe that ran, in a stable order. Never empty. */
40735
+ evidence: zod.z.array(WrapperEvidenceSchema),
40736
+ /**
40737
+ * The version of the SHELL, when the shell has one it can state: the
40738
+ * Electron app version it declared, or the baked seed closure that
40739
+ * fingerprints a container / app bundle. `null` for `contradictory` and
40740
+ * `unknown` — which version belongs to the shell is precisely what is not
40741
+ * known there.
40742
+ */
40743
+ currentVersion: zod.z.string().nullable()
40744
+ });
40685
40745
  var ServerRollbackInfoSchema = zod.z.object({
40686
40746
  /** The version that failed (or was manually rolled back). */
40687
40747
  fromVersion: zod.z.string(),
@@ -40723,7 +40783,13 @@ var require_dist_DYWqXngV = __commonJS({
40723
40783
  * Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
40724
40784
  * version skew: an older provider's payload simply omits it.
40725
40785
  */
40726
- imageContract: ImageContractSchema.optional()
40786
+ imageContract: ImageContractSchema.optional(),
40787
+ /**
40788
+ * What this node runs INSIDE (see {@link WrapperIdentitySchema}). Optional
40789
+ * for version skew: an older provider's payload simply omits it, and an
40790
+ * absent wrapper is unknown — never `native`.
40791
+ */
40792
+ wrapper: WrapperIdentitySchema.optional()
40727
40793
  });
40728
40794
  var ServerUpdateCheckResultSchema = zod.z.object({
40729
40795
  packageName: zod.z.string(),
@@ -48386,7 +48452,23 @@ var require_dist_DYWqXngV = __commonJS({
48386
48452
  });
48387
48453
  var RecordingAvailabilitySchema = zod.z.object({
48388
48454
  deviceId: zod.z.number(),
48389
- ranges: zod.z.array(RecordingRangeSchema)
48455
+ ranges: zod.z.array(RecordingRangeSchema),
48456
+ /**
48457
+ * Every profile this camera has footage in — not only the one `ranges`
48458
+ * describes (D433).
48459
+ *
48460
+ * `ranges` answers for ONE profile by design: the timeline is a single bar,
48461
+ * and enumerating all of them triples the directory reads for a bar that
48462
+ * would look identical. But the answer used to say nothing about that, so a
48463
+ * caller asking "what can I export?" read the single preferred profile as
48464
+ * the only one that exists — on this deployment every camera records `high`
48465
+ * AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
48466
+ * UI could not see it.
48467
+ *
48468
+ * Cheap: it is the same shallow directory read the profile CHOICE already
48469
+ * makes. Ask for a specific profile's ranges with the `profile` input.
48470
+ */
48471
+ profilesWithFootage: zod.z.array(zod.z.string())
48390
48472
  });
48391
48473
  var RecordingDaysSchema = zod.z.object({
48392
48474
  deviceId: zod.z.number(),
@@ -48396,7 +48478,11 @@ var require_dist_DYWqXngV = __commonJS({
48396
48478
  var RecordingAvailabilityForDeviceSchema = zod.z.object({
48397
48479
  deviceId: zod.z.number(),
48398
48480
  read: zod.z.enum(["read", "unreadable"]),
48399
- ranges: zod.z.array(RecordingRangeSchema).readonly()
48481
+ ranges: zod.z.array(RecordingRangeSchema).readonly(),
48482
+ /** Same field, same meaning, as the singular answer (D433). A row that
48483
+ * dropped it would tell a grid caller the archive holds one profile.
48484
+ * Empty on an `'unreadable'` row: nobody looked. */
48485
+ profilesWithFootage: zod.z.array(zod.z.string()).readonly()
48400
48486
  });
48401
48487
  var RecordingDaysForDeviceSchema = zod.z.object({
48402
48488
  deviceId: zod.z.number(),
@@ -48611,7 +48697,14 @@ var require_dist_DYWqXngV = __commonJS({
48611
48697
  getAvailability: method(zod.z.object({
48612
48698
  deviceId: zod.z.number(),
48613
48699
  fromMs: zod.z.number(),
48614
- toMs: zod.z.number()
48700
+ toMs: zod.z.number(),
48701
+ /**
48702
+ * Answer for THIS profile instead of the preferred one (D433). Absent
48703
+ * keeps the timeline's behaviour — one bar, one profile, one set of
48704
+ * reads. `profilesWithFootage` on the answer says what may be asked
48705
+ * for.
48706
+ */
48707
+ profile: zod.z.string().optional()
48615
48708
  }), RecordingAvailabilitySchema, {
48616
48709
  kind: "query",
48617
48710
  auth: "protected"
@@ -61061,6 +61154,12 @@ var require_dist_DYWqXngV = __commonJS({
61061
61154
  return deriveBatteryPresence;
61062
61155
  }
61063
61156
  });
61157
+ Object.defineProperty(exports, "describeCustomActions", {
61158
+ enumerable: true,
61159
+ get: function() {
61160
+ return describeCustomActions;
61161
+ }
61162
+ });
61064
61163
  Object.defineProperty(exports, "deviceManagerCapability", {
61065
61164
  enumerable: true,
61066
61165
  get: function() {
@@ -61487,7 +61586,7 @@ var require_alerts_addon = __commonJS({
61487
61586
  [Symbol.toStringTag]: { value: "Module" }
61488
61587
  });
61489
61588
  require_chunk_Cek0wNdY();
61490
- var require_dist10 = require_dist_DYWqXngV();
61589
+ var require_dist10 = require_dist_BOlMCOeT();
61491
61590
  function selectExpired(alerts, cutoffMs) {
61492
61591
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
61493
61592
  }
@@ -62306,7 +62405,7 @@ var require_console_logging = __commonJS({
62306
62405
  [Symbol.toStringTag]: { value: "Module" }
62307
62406
  });
62308
62407
  require_chunk_Cek0wNdY();
62309
- var require_dist10 = require_dist_DYWqXngV();
62408
+ var require_dist10 = require_dist_BOlMCOeT();
62310
62409
  var require_formatter = require_formatter_DqAKDlvN();
62311
62410
  var LEVEL_RANK = {
62312
62411
  debug: 0,
@@ -62400,7 +62499,7 @@ var require_core_blocks_addon = __commonJS({
62400
62499
  "use strict";
62401
62500
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
62402
62501
  var require_chunk = require_chunk_Cek0wNdY();
62403
- var require_dist10 = require_dist_DYWqXngV();
62502
+ var require_dist10 = require_dist_BOlMCOeT();
62404
62503
  var node_crypto = __require("crypto");
62405
62504
  var node_fs_promises = __require("fs/promises");
62406
62505
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -63297,11 +63396,11 @@ var require_core_blocks = __commonJS({
63297
63396
  }
63298
63397
  });
63299
63398
 
63300
- // ../system/dist/retired-settings-keys-CXLIB19R.js
63301
- var require_retired_settings_keys_CXLIB19R = __commonJS({
63302
- "../system/dist/retired-settings-keys-CXLIB19R.js"(exports) {
63399
+ // ../system/dist/retired-settings-keys-xEAlYnv7.js
63400
+ var require_retired_settings_keys_xEAlYnv7 = __commonJS({
63401
+ "../system/dist/retired-settings-keys-xEAlYnv7.js"(exports) {
63303
63402
  "use strict";
63304
- var require_dist10 = require_dist_DYWqXngV();
63403
+ var require_dist10 = require_dist_BOlMCOeT();
63305
63404
  function settingsStoreIsAuthoritativeHere(env) {
63306
63405
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
63307
63406
  return raw === "" || raw === "hub";
@@ -65536,8 +65635,8 @@ var require_device_manager_addon = __commonJS({
65536
65635
  [Symbol.toStringTag]: { value: "Module" }
65537
65636
  });
65538
65637
  require_chunk_Cek0wNdY();
65539
- var require_dist10 = require_dist_DYWqXngV();
65540
- var require_retired_settings_keys = require_retired_settings_keys_CXLIB19R();
65638
+ var require_dist10 = require_dist_BOlMCOeT();
65639
+ var require_retired_settings_keys = require_retired_settings_keys_xEAlYnv7();
65541
65640
  var node_crypto = __require("crypto");
65542
65641
  var _camstack_types_node = require_node();
65543
65642
  var JOB_HISTORY = 20;
@@ -71162,7 +71261,7 @@ var require_hub_forwarder = __commonJS({
71162
71261
  [Symbol.toStringTag]: { value: "Module" }
71163
71262
  });
71164
71263
  require_chunk_Cek0wNdY();
71165
- var require_dist10 = require_dist_DYWqXngV();
71264
+ var require_dist10 = require_dist_BOlMCOeT();
71166
71265
  var require_formatter = require_formatter_DqAKDlvN();
71167
71266
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
71168
71267
  var HubForwarderDestination = class {
@@ -71299,7 +71398,7 @@ var require_liveness_monitor_addon = __commonJS({
71299
71398
  "use strict";
71300
71399
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
71301
71400
  require_chunk_Cek0wNdY();
71302
- var require_dist10 = require_dist_DYWqXngV();
71401
+ var require_dist10 = require_dist_BOlMCOeT();
71303
71402
  var NO_DEVICES = "liveness:no-devices";
71304
71403
  var ALL_OFFLINE = "liveness:all-devices-offline";
71305
71404
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -71495,7 +71594,7 @@ var require_local_auth_addon = __commonJS({
71495
71594
  [Symbol.toStringTag]: { value: "Module" }
71496
71595
  });
71497
71596
  var require_chunk = require_chunk_Cek0wNdY();
71498
- var require_dist10 = require_dist_DYWqXngV();
71597
+ var require_dist10 = require_dist_BOlMCOeT();
71499
71598
  var node_crypto = __require("crypto");
71500
71599
  node_crypto = require_chunk.__toESM(node_crypto);
71501
71600
  var crypto$1 = __require("crypto");
@@ -79308,7 +79407,7 @@ var require_loki_logging = __commonJS({
79308
79407
  [Symbol.toStringTag]: { value: "Module" }
79309
79408
  });
79310
79409
  require_chunk_Cek0wNdY();
79311
- var require_dist10 = require_dist_DYWqXngV();
79410
+ var require_dist10 = require_dist_BOlMCOeT();
79312
79411
  function sanitizeLabelName(raw) {
79313
79412
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
79314
79413
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -79873,7 +79972,7 @@ var require_native_metrics_addon = __commonJS({
79873
79972
  [Symbol.toStringTag]: { value: "Module" }
79874
79973
  });
79875
79974
  var require_chunk = require_chunk_Cek0wNdY();
79876
- var require_dist10 = require_dist_DYWqXngV();
79975
+ var require_dist10 = require_dist_BOlMCOeT();
79877
79976
  var node_fs_promises = __require("fs/promises");
79878
79977
  var node_child_process = __require("child_process");
79879
79978
  var node_util = __require("util");
@@ -82309,13 +82408,6 @@ var require_native_metrics_addon = __commonJS({
82309
82408
  cpuSplits
82310
82409
  });
82311
82410
  }
82312
- /**
82313
- * Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
82314
- * runner's diagnostic handler in addon-runner.ts), for deep per-addon memory
82315
- * attribution. Resolves the addon's worker pid from the kernel `$process.list`
82316
- * and signals it; the runner writes `/tmp/heap-<sanitized nodeId>.heapsnapshot`
82317
- * and logs its memoryUsage + heap-space breakdown. '$hub' targets this process.
82318
- */
82319
82411
  /** Raw `ps` scan returning every pid + command + resource stats. */
82320
82412
  async runPs() {
82321
82413
  try {
@@ -82457,7 +82549,7 @@ var require_filesystem_storage_addon = __commonJS({
82457
82549
  [Symbol.toStringTag]: { value: "Module" }
82458
82550
  });
82459
82551
  var require_chunk = require_chunk_Cek0wNdY();
82460
- var require_dist10 = require_dist_DYWqXngV();
82552
+ var require_dist10 = require_dist_BOlMCOeT();
82461
82553
  var node_crypto = __require("crypto");
82462
82554
  var node_fs_promises = __require("fs/promises");
82463
82555
  var node_path = __require("path");
@@ -83573,8 +83665,8 @@ var require_sqlite_settings_addon = __commonJS({
83573
83665
  [Symbol.toStringTag]: { value: "Module" }
83574
83666
  });
83575
83667
  var require_chunk = require_chunk_Cek0wNdY();
83576
- var require_dist10 = require_dist_DYWqXngV();
83577
- var require_retired_settings_keys = require_retired_settings_keys_CXLIB19R();
83668
+ var require_dist10 = require_dist_BOlMCOeT();
83669
+ var require_retired_settings_keys = require_retired_settings_keys_xEAlYnv7();
83578
83670
  var node_crypto = __require("crypto");
83579
83671
  var node_fs = __require("fs");
83580
83672
  var node_module = __require("module");
@@ -86078,7 +86170,7 @@ var require_storage_orchestrator_addon = __commonJS({
86078
86170
  [Symbol.toStringTag]: { value: "Module" }
86079
86171
  });
86080
86172
  var require_chunk = require_chunk_Cek0wNdY();
86081
- var require_dist10 = require_dist_DYWqXngV();
86173
+ var require_dist10 = require_dist_BOlMCOeT();
86082
86174
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
86083
86175
  var zod = require_zod();
86084
86176
  var node_crypto = __require("crypto");
@@ -89728,7 +89820,7 @@ var require_system_config_addon = __commonJS({
89728
89820
  [Symbol.toStringTag]: { value: "Module" }
89729
89821
  });
89730
89822
  require_chunk_Cek0wNdY();
89731
- var require_dist10 = require_dist_DYWqXngV();
89823
+ var require_dist10 = require_dist_BOlMCOeT();
89732
89824
  var SECTION_TITLES = {
89733
89825
  server: "Server",
89734
89826
  auth: "Authentication"
@@ -107789,7 +107881,7 @@ var require_winston_logging = __commonJS({
107789
107881
  [Symbol.toStringTag]: { value: "Module" }
107790
107882
  });
107791
107883
  var require_chunk = require_chunk_Cek0wNdY();
107792
- var require_dist10 = require_dist_DYWqXngV();
107884
+ var require_dist10 = require_dist_BOlMCOeT();
107793
107885
  var require_formatter = require_formatter_DqAKDlvN();
107794
107886
  var node_path = __require("path");
107795
107887
  node_path = require_chunk.__toESM(node_path);
@@ -120423,9 +120515,9 @@ var require_dist2 = __commonJS({
120423
120515
  }
120424
120516
  });
120425
120517
 
120426
- // ../system/dist/manifest-system-deps-DvY1bY_n.js
120427
- var require_manifest_system_deps_DvY1bY_n = __commonJS({
120428
- "../system/dist/manifest-system-deps-DvY1bY_n.js"(exports) {
120518
+ // ../system/dist/manifest-system-deps-Bo92lg5u.js
120519
+ var require_manifest_system_deps_Bo92lg5u = __commonJS({
120520
+ "../system/dist/manifest-system-deps-Bo92lg5u.js"(exports) {
120429
120521
  "use strict";
120430
120522
  var require_chunk = require_chunk_Cek0wNdY();
120431
120523
  require_hub_cap_forward_DmHNjbB0();
@@ -120444,6 +120536,7 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
120444
120536
  var node_http = __require("http");
120445
120537
  var node_v8 = __require("v8");
120446
120538
  node_v8 = require_chunk.__toESM(node_v8);
120539
+ var node_inspector_promises = __require("inspector/promises");
120447
120540
  var node_perf_hooks = __require("perf_hooks");
120448
120541
  var node_vm = __require("vm");
120449
120542
  node_vm = require_chunk.__toESM(node_vm);
@@ -120683,7 +120776,7 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
120683
120776
  var DECISIVE_HEAP_SPACES = ["old_space", "large_object_space"];
120684
120777
  var HEAP_SPACE_REPORT_MIN_MB = 32;
120685
120778
  var BYTES_PER_MB = 1048576;
120686
- function mb(bytes) {
120779
+ function mb$1(bytes) {
120687
120780
  return Math.round(bytes / BYTES_PER_MB);
120688
120781
  }
120689
120782
  function tenthsMb(bytes) {
@@ -120704,13 +120797,13 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
120704
120797
  }
120705
120798
  function selectReportedSpaces(spaces, minMb = 32) {
120706
120799
  const decisive = DECISIVE_HEAP_SPACES.map((name) => spaces.find((space) => space.name === name)).filter((space) => space !== void 0);
120707
- const rest = spaces.filter((space) => !DECISIVE_HEAP_SPACES.includes(space.name)).filter((space) => mb(space.usedBytes) >= minMb).toSorted((a, b) => b.usedBytes - a.usedBytes || a.name.localeCompare(b.name));
120800
+ const rest = spaces.filter((space) => !DECISIVE_HEAP_SPACES.includes(space.name)).filter((space) => mb$1(space.usedBytes) >= minMb).toSorted((a, b) => b.usedBytes - a.usedBytes || a.name.localeCompare(b.name));
120708
120801
  return [...decisive, ...rest];
120709
120802
  }
120710
120803
  function formatHeapSpaces(spaces, minMb = 32) {
120711
120804
  const selected = selectReportedSpaces(spaces, minMb);
120712
120805
  if (selected.length === 0) return "";
120713
- return ` ${selected.map((s) => `${heapSpaceField(s.name)}=${mb(s.usedBytes)}MB`).join(" ")}`;
120806
+ return ` ${selected.map((s) => `${heapSpaceField(s.name)}=${mb$1(s.usedBytes)}MB`).join(" ")}`;
120714
120807
  }
120715
120808
  var defaultDeps = {
120716
120809
  readMemory: () => process.memoryUsage(),
@@ -120726,6 +120819,137 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
120726
120819
  request.sink.warn(`[mem] node="${request.label}" space report failed: ${error instanceof Error ? error.message : String(error)}`);
120727
120820
  }
120728
120821
  }
120822
+ var ARRAY_BUFFER_CENSUS_THRESHOLD_MB = 512;
120823
+ var ARRAY_BUFFER_CENSUS_SUSTAIN_MS = 6e4;
120824
+ var ARRAY_BUFFER_CENSUS_COOLDOWN_MS = 30 * 6e4;
120825
+ var ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO = 0.5;
120826
+ function createArrayBufferCensusGate(options = {}) {
120827
+ const thresholdMb = options.thresholdMb ?? 512;
120828
+ const sustainMs = options.sustainMs ?? 6e4;
120829
+ const cooldownMs = options.cooldownMs ?? 18e5;
120830
+ let aboveSince = null;
120831
+ let lastTakenAt = Number.NEGATIVE_INFINITY;
120832
+ let previousMb = null;
120833
+ let previousAt = null;
120834
+ return { observe(arrayBuffersMb, at) {
120835
+ const previous = previousMb;
120836
+ const previousHeldForMs = previous !== null && previousAt !== null && aboveSince !== null ? previousAt - aboveSince : null;
120837
+ previousMb = arrayBuffersMb;
120838
+ previousAt = at;
120839
+ if (previous !== null && previousHeldForMs !== null && previousHeldForMs >= sustainMs && previous - arrayBuffersMb >= thresholdMb * 0.5) {
120840
+ aboveSince = null;
120841
+ return {
120842
+ kind: "released",
120843
+ fromMb: previous,
120844
+ toMb: arrayBuffersMb
120845
+ };
120846
+ }
120847
+ if (arrayBuffersMb < thresholdMb) {
120848
+ aboveSince = null;
120849
+ return { kind: "hold" };
120850
+ }
120851
+ aboveSince ??= at;
120852
+ const heldForMs = at - aboveSince;
120853
+ if (heldForMs < sustainMs) return { kind: "hold" };
120854
+ if (at - lastTakenAt < cooldownMs) return { kind: "hold" };
120855
+ lastTakenAt = at;
120856
+ return {
120857
+ kind: "take",
120858
+ reason: `arrayBuffers=${arrayBuffersMb}MB has been >=${thresholdMb}MB for ${heldForMs}ms`
120859
+ };
120860
+ } };
120861
+ }
120862
+ var HISTOGRAM_FN = `function () {
120863
+ var byBucket = new Map(), bySize = new Map()
120864
+ var count = 0, bytes = 0, detached = 0
120865
+ for (var i = 0; i < this.length; i++) {
120866
+ var b = this[i].byteLength
120867
+ count++
120868
+ bytes += b
120869
+ if (b === 0) { detached++; continue }
120870
+ var bucket = 1
120871
+ while (bucket < b) bucket *= 2
120872
+ var e = byBucket.get(bucket); if (!e) { e = [0, 0]; byBucket.set(bucket, e) }
120873
+ e[0]++; e[1] += b
120874
+ var s = bySize.get(b); if (!s) { s = [0, 0]; bySize.set(b, s) }
120875
+ s[0]++; s[1] += b
120876
+ }
120877
+ var buckets = []
120878
+ byBucket.forEach(function (v, k) { buckets.push({ upToBytes: k, count: v[0], bytes: v[1] }) })
120879
+ buckets.sort(function (a, b) { return b.bytes - a.bytes })
120880
+ var topSizes = []
120881
+ bySize.forEach(function (v, k) { topSizes.push({ byteLength: k, count: v[0], bytes: v[1] }) })
120882
+ topSizes.sort(function (a, b) { return b.bytes - a.bytes })
120883
+ return { count: count, bytes: bytes, detached: detached,
120884
+ buckets: buckets.slice(0, 6), topSizes: topSizes.slice(0, 5) }
120885
+ }`;
120886
+ function isFiniteNumber(value) {
120887
+ return typeof value === "number" && Number.isFinite(value);
120888
+ }
120889
+ function isBucket(value) {
120890
+ return typeof value === "object" && value !== null && isFiniteNumber(Reflect.get(value, "upToBytes")) && isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes"));
120891
+ }
120892
+ function isExactSize(value) {
120893
+ return typeof value === "object" && value !== null && isFiniteNumber(Reflect.get(value, "byteLength")) && isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes"));
120894
+ }
120895
+ function isArrayBufferCensusReport(value) {
120896
+ if (typeof value !== "object" || value === null) return false;
120897
+ const buckets = Reflect.get(value, "buckets");
120898
+ const topSizes = Reflect.get(value, "topSizes");
120899
+ return isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes")) && isFiniteNumber(Reflect.get(value, "detached")) && Array.isArray(buckets) && buckets.every(isBucket) && Array.isArray(topSizes) && topSizes.every(isExactSize);
120900
+ }
120901
+ function readObjectId(result, path) {
120902
+ let cursor = result;
120903
+ for (const key of path) {
120904
+ if (typeof cursor !== "object" || cursor === null) throw new Error(`inspector reply has no ${path.join(".")}`);
120905
+ cursor = Reflect.get(cursor, key);
120906
+ }
120907
+ if (typeof cursor !== "string") throw new Error(`inspector reply has no ${path.join(".")}`);
120908
+ return cursor;
120909
+ }
120910
+ function createInspectorArrayBufferCensus() {
120911
+ return async () => {
120912
+ const session = new node_inspector_promises.Session();
120913
+ session.connect();
120914
+ try {
120915
+ const prototypeObjectId = readObjectId(await session.post("Runtime.evaluate", { expression: "ArrayBuffer.prototype" }), ["result", "objectId"]);
120916
+ try {
120917
+ const objectId = readObjectId(await session.post("Runtime.queryObjects", { prototypeObjectId }), ["objects", "objectId"]);
120918
+ try {
120919
+ const evaluated = await session.post("Runtime.callFunctionOn", {
120920
+ objectId,
120921
+ functionDeclaration: HISTOGRAM_FN,
120922
+ returnByValue: true
120923
+ });
120924
+ const value = typeof evaluated === "object" && evaluated !== null ? Reflect.get(Reflect.get(evaluated, "result") ?? {}, "value") : void 0;
120925
+ if (!isArrayBufferCensusReport(value)) throw new Error("inspector returned a histogram of an unexpected shape");
120926
+ return value;
120927
+ } finally {
120928
+ await session.post("Runtime.releaseObject", { objectId });
120929
+ }
120930
+ } finally {
120931
+ await session.post("Runtime.releaseObject", { objectId: prototypeObjectId });
120932
+ }
120933
+ } finally {
120934
+ session.disconnect();
120935
+ }
120936
+ };
120937
+ }
120938
+ var MB$1 = 1048576;
120939
+ function mb(bytes) {
120940
+ return Math.round(bytes / MB$1);
120941
+ }
120942
+ function formatBucket(bucket) {
120943
+ return `<=${bucket.upToBytes >= MB$1 ? `${Math.round(bucket.upToBytes / MB$1)}MB` : bucket.upToBytes >= 1024 ? `${Math.round(bucket.upToBytes / 1024)}KB` : `${bucket.upToBytes}B`}:${bucket.count}x/${mb(bucket.bytes)}MB`;
120944
+ }
120945
+ function formatExact(size) {
120946
+ return `${size.byteLength}Bx${size.count}=${mb(size.bytes)}MB`;
120947
+ }
120948
+ function formatArrayBufferCensus(label, report, context) {
120949
+ const live = mb(report.bytes);
120950
+ const verdict = live < context.rawMb * 0.5 ? "mostly DEAD buffers awaiting a sweep, not a retention" : "LIVE \u2014 something holds them";
120951
+ return `[mem] ${label} arrayBuffer census: live=${live}MB in ${report.count} ArrayBuffers (raw arrayBuffers=${context.rawMb}MB at the trigger \u2192 ${verdict}) top sizes: ${report.topSizes.map(formatExact).join(", ") || "(none)"}; buckets: ${report.buckets.map(formatBucket).join(", ") || "(none)"}; detached=${report.detached} took=${context.tookMs}ms \u2014 cost: ONE forced full GC (the same pause as a reclaim pass), walked in memory, nothing serialised, nothing written; trigger: ${context.reason}`;
120952
+ }
120729
120953
  var HUB_MAIN_HEAP_WATCH_LABEL = "hub-main";
120730
120954
  var HEAP_WATCH_INTERVAL_MS = 6e4;
120731
120955
  var HEAP_WATCH_WARN_RATIO = 0.8;
@@ -120962,9 +121186,36 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
120962
121186
  if (previous === "escalated" && mode === "steady") sink.info(`[mem] ${label} heap back to routine \u2014 logging every ${intervalMs}ms`);
120963
121187
  checkRssBudget(sample, at);
120964
121188
  maybeReclaim(sample);
121189
+ judgeArrayBuffers(sample, at);
120965
121190
  } catch {
120966
121191
  }
120967
121192
  };
121193
+ const censusProbe = probes?.arrayBufferCensus;
121194
+ const censusGate = censusProbe === void 0 ? void 0 : createArrayBufferCensusGate(censusProbe);
121195
+ let censusInFlight = false;
121196
+ const judgeArrayBuffers = (sample, at) => {
121197
+ if (censusProbe === void 0 || censusGate === void 0) return;
121198
+ const verdict = censusGate.observe(sample.arrayBuffersMb, at);
121199
+ if (verdict.kind === "released") {
121200
+ sink.warn(`[mem] ${label} arrayBuffers RELEASED ${verdict.fromMb}MB\u2192${verdict.toMb}MB between two probes ${probeIntervalMs}ms apart \u2014 no owner logged this release; whatever held them let go in one instant (a socket that died, a queue that drained, a map that was cleared). Correlate with every line of this process in that window.`);
121201
+ return;
121202
+ }
121203
+ if (verdict.kind !== "take" || censusInFlight) return;
121204
+ censusInFlight = true;
121205
+ const startedAt = now();
121206
+ const rawMb = sample.arrayBuffersMb;
121207
+ censusProbe.census().then((report) => {
121208
+ sink.warn(formatArrayBufferCensus(label, report, {
121209
+ rawMb,
121210
+ tookMs: now() - startedAt,
121211
+ reason: verdict.reason
121212
+ }));
121213
+ }).catch((error) => {
121214
+ sink.warn(`[mem] ${label} arrayBuffer census FAILED after ${now() - startedAt}ms (trigger: ${verdict.reason}) \u2014 ${error instanceof Error ? error.message : String(error)}`);
121215
+ }).finally(() => {
121216
+ censusInFlight = false;
121217
+ });
121218
+ };
120968
121219
  const timer = setInterval(tick, probeIntervalMs);
120969
121220
  timer.unref?.();
120970
121221
  tick();
@@ -125709,7 +125960,8 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
125709
125960
  this.registeredHandler({
125710
125961
  childId: msg.childId,
125711
125962
  caps: msg.caps,
125712
- incarnation
125963
+ incarnation,
125964
+ ...msg.customActions !== void 0 ? { customActions: msg.customActions } : {}
125713
125965
  });
125714
125966
  return { ok: true };
125715
125967
  }
@@ -125794,6 +126046,15 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
125794
126046
  * subscriptions do), the register carries the real union — even if empty.
125795
126047
  */
125796
126048
  hasDeclaredEventPatterns = false;
126049
+ /**
126050
+ * The custom-action catalogs of every hosted addon, once the runner's init
126051
+ * loop has produced them (`updateCaps(caps, catalogs)` at post-init). `null`
126052
+ * until then, and OMITTED from the register frame while null: the parent
126053
+ * reads absence as "not described yet", and a pre-init register must never
126054
+ * look like "this child declares no actions". Kept on the client so every
126055
+ * later re-register (native-cap change, reconnect) re-carries it unchanged.
126056
+ */
126057
+ latestCustomActions = null;
125797
126058
  /** Events and logs queued while the channel is not yet open. */
125798
126059
  pendingEmits = [];
125799
126060
  /** Handler for parent→child events. Registered via `onEvent`. */
@@ -125933,12 +126194,7 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
125933
126194
  }
125934
126195
  if (msg.kind === "set-log-level") this.setLogLevelHandler?.(msg.level);
125935
126196
  });
125936
- const register = {
125937
- kind: "register",
125938
- childId: this.options.childId,
125939
- caps: this.latestCaps,
125940
- ...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {}
125941
- };
126197
+ const register = this.registerFrame(this.latestCaps);
125942
126198
  try {
125943
126199
  await channel.request(register);
125944
126200
  } catch (err) {
@@ -125964,16 +126220,26 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
125964
126220
  * it (device-restore can race ahead of the UDS connect). After `start()`,
125965
126221
  * the set is sent immediately.
125966
126222
  */
125967
- async updateCaps(caps) {
126223
+ async updateCaps(caps, customActions) {
125968
126224
  this.latestCaps = caps;
126225
+ if (customActions !== void 0) this.latestCustomActions = customActions;
125969
126226
  if (this.channel === null) return;
125970
- const register = {
126227
+ await this.channel.request(this.registerFrame(caps));
126228
+ }
126229
+ /**
126230
+ * The register frame: caps, plus everything a re-register must atomically
126231
+ * re-carry so no separate re-sync can be missed — the subscription union
126232
+ * (omitted if nothing ever declared, which keeps the fail-open shape) and the
126233
+ * custom-action catalogs (omitted until the init loop produced them).
126234
+ */
126235
+ registerFrame(caps) {
126236
+ return {
125971
126237
  kind: "register",
125972
126238
  childId: this.options.childId,
125973
126239
  caps,
125974
- ...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {}
126240
+ ...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {},
126241
+ ...this.latestCustomActions !== null ? { customActions: this.latestCustomActions } : {}
125975
126242
  };
125976
- await this.channel.request(register);
125977
126243
  }
125978
126244
  /**
125979
126245
  * Fire-and-forget: send a system event to the parent for forwarding to the
@@ -127946,6 +128212,30 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
127946
128212
  return AGENT_CAP_FWD_SERVICE;
127947
128213
  }
127948
128214
  });
128215
+ Object.defineProperty(exports, "ARRAY_BUFFER_CENSUS_COOLDOWN_MS", {
128216
+ enumerable: true,
128217
+ get: function() {
128218
+ return ARRAY_BUFFER_CENSUS_COOLDOWN_MS;
128219
+ }
128220
+ });
128221
+ Object.defineProperty(exports, "ARRAY_BUFFER_CENSUS_SUSTAIN_MS", {
128222
+ enumerable: true,
128223
+ get: function() {
128224
+ return ARRAY_BUFFER_CENSUS_SUSTAIN_MS;
128225
+ }
128226
+ });
128227
+ Object.defineProperty(exports, "ARRAY_BUFFER_CENSUS_THRESHOLD_MB", {
128228
+ enumerable: true,
128229
+ get: function() {
128230
+ return ARRAY_BUFFER_CENSUS_THRESHOLD_MB;
128231
+ }
128232
+ });
128233
+ Object.defineProperty(exports, "ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO", {
128234
+ enumerable: true,
128235
+ get: function() {
128236
+ return ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO;
128237
+ }
128238
+ });
127949
128239
  Object.defineProperty(exports, "AddonDepsManager", {
127950
128240
  enumerable: true,
127951
128241
  get: function() {
@@ -128276,6 +128566,12 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
128276
128566
  return createAddonService;
128277
128567
  }
128278
128568
  });
128569
+ Object.defineProperty(exports, "createArrayBufferCensusGate", {
128570
+ enumerable: true,
128571
+ get: function() {
128572
+ return createArrayBufferCensusGate;
128573
+ }
128574
+ });
128279
128575
  Object.defineProperty(exports, "createBrokerDeviceManagerApi", {
128280
128576
  enumerable: true,
128281
128577
  get: function() {
@@ -128300,6 +128596,12 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
128300
128596
  return createEventPlaneReader;
128301
128597
  }
128302
128598
  });
128599
+ Object.defineProperty(exports, "createInspectorArrayBufferCensus", {
128600
+ enumerable: true,
128601
+ get: function() {
128602
+ return createInspectorArrayBufferCensus;
128603
+ }
128604
+ });
128303
128605
  Object.defineProperty(exports, "createKernelHwAccel", {
128304
128606
  enumerable: true,
128305
128607
  get: function() {
@@ -128414,6 +128716,12 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
128414
128716
  return encodeFrame;
128415
128717
  }
128416
128718
  });
128719
+ Object.defineProperty(exports, "formatArrayBufferCensus", {
128720
+ enumerable: true,
128721
+ get: function() {
128722
+ return formatArrayBufferCensus;
128723
+ }
128724
+ });
128417
128725
  Object.defineProperty(exports, "formatEventPlane", {
128418
128726
  enumerable: true,
128419
128727
  get: function() {
@@ -128516,6 +128824,12 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
128516
128824
  return ipcParentLink;
128517
128825
  }
128518
128826
  });
128827
+ Object.defineProperty(exports, "isArrayBufferCensusReport", {
128828
+ enumerable: true,
128829
+ get: function() {
128830
+ return isArrayBufferCensusReport;
128831
+ }
128832
+ });
128519
128833
  Object.defineProperty(exports, "localEndpointPath", {
128520
128834
  enumerable: true,
128521
128835
  get: function() {
@@ -128699,10 +129013,11 @@ var require_manifest_system_deps_DvY1bY_n = __commonJS({
128699
129013
  }
128700
129014
  });
128701
129015
 
128702
- // ../system/dist/custom-action-registry-jY0NOZK8.js
128703
- var require_custom_action_registry_jY0NOZK8 = __commonJS({
128704
- "../system/dist/custom-action-registry-jY0NOZK8.js"(exports) {
129016
+ // ../system/dist/custom-action-registry-BDwM5qAK.js
129017
+ var require_custom_action_registry_BDwM5qAK = __commonJS({
129018
+ "../system/dist/custom-action-registry-BDwM5qAK.js"(exports) {
128705
129019
  "use strict";
129020
+ var require_dist10 = require_dist_BOlMCOeT();
128706
129021
  var CustomActionRegistry = class {
128707
129022
  byAddon = /* @__PURE__ */ new Map();
128708
129023
  registerAddon(addonId, catalog, handler) {
@@ -128729,6 +129044,24 @@ var require_custom_action_registry_jY0NOZK8 = __commonJS({
128729
129044
  listAddons() {
128730
129045
  return [...this.byAddon.keys()];
128731
129046
  }
129047
+ /**
129048
+ * Every registered catalog with its schemas stripped, keyed by addon id —
129049
+ * what a runner puts in its register frame so the hub learns the catalogs
129050
+ * from the process that owns them (D444). `hostedAddonIds` are the addons
129051
+ * this runner hosts: each is present in the result, with an EMPTY catalog
129052
+ * when it registered none, so the parent can say "declares no actions" per
129053
+ * addon instead of guessing from an absent key.
129054
+ */
129055
+ describeAll(hostedAddonIds) {
129056
+ const out = {};
129057
+ for (const addonId of hostedAddonIds) out[addonId] = {};
129058
+ for (const [addonId, actions] of this.byAddon) {
129059
+ const catalog = {};
129060
+ for (const [name, entry] of actions) catalog[name] = entry.spec;
129061
+ out[addonId] = require_dist10.describeCustomActions(catalog);
129062
+ }
129063
+ return out;
129064
+ }
128732
129065
  };
128733
129066
  Object.defineProperty(exports, "CustomActionRegistry", {
128734
129067
  enumerable: true,
@@ -132498,7 +132831,7 @@ var require_dist3 = __commonJS({
132498
132831
  "use strict";
132499
132832
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
132500
132833
  var require_chunk = require_chunk_Cek0wNdY();
132501
- var require_dist10 = require_dist_DYWqXngV();
132834
+ var require_dist10 = require_dist_BOlMCOeT();
132502
132835
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
132503
132836
  require_alerts();
132504
132837
  var require_formatter = require_formatter_DqAKDlvN();
@@ -132525,9 +132858,9 @@ var require_dist3 = __commonJS({
132525
132858
  var require_builtins_winston_logging_index = require_winston_logging();
132526
132859
  var require_event_loop_stall_monitor = require_event_loop_stall_monitor_DM9OAzy8();
132527
132860
  var require_tls$1 = require_tls_BxQlomxd();
132528
- var require_manifest_system_deps = require_manifest_system_deps_DvY1bY_n();
132861
+ var require_manifest_system_deps = require_manifest_system_deps_Bo92lg5u();
132529
132862
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
132530
- var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
132863
+ var require_custom_action_registry = require_custom_action_registry_BDwM5qAK();
132531
132864
  var zod = require_zod();
132532
132865
  var node_crypto = __require("crypto");
132533
132866
  var _camstack_types_node = require_node();
@@ -213209,6 +213542,10 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
213209
213542
  exports.AGENT_CAP_FWD_SERVICE = require_manifest_system_deps.AGENT_CAP_FWD_SERVICE;
213210
213543
  exports.AGENT_READINESS_SERVICE_NAME = AGENT_READINESS_SERVICE_NAME;
213211
213544
  exports.ALL_CAPABILITY_DEFINITIONS = require_dist10.ALL_CAPABILITY_DEFINITIONS;
213545
+ exports.ARRAY_BUFFER_CENSUS_COOLDOWN_MS = require_manifest_system_deps.ARRAY_BUFFER_CENSUS_COOLDOWN_MS;
213546
+ exports.ARRAY_BUFFER_CENSUS_SUSTAIN_MS = require_manifest_system_deps.ARRAY_BUFFER_CENSUS_SUSTAIN_MS;
213547
+ exports.ARRAY_BUFFER_CENSUS_THRESHOLD_MB = require_manifest_system_deps.ARRAY_BUFFER_CENSUS_THRESHOLD_MB;
213548
+ exports.ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO = require_manifest_system_deps.ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO;
213212
213549
  exports.AddonApiFactory = AddonApiFactory;
213213
213550
  exports.AddonDepsManager = require_manifest_system_deps.AddonDepsManager;
213214
213551
  exports.AddonEngineManager = AddonEngineManager;
@@ -213392,6 +213729,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
213392
213729
  exports.createAddonContext = require_manifest_system_deps.createAddonContext;
213393
213730
  exports.createAddonDataPlaneFacility = require_manifest_system_deps.createAddonDataPlaneFacility;
213394
213731
  exports.createAddonService = require_manifest_system_deps.createAddonService;
213732
+ exports.createArrayBufferCensusGate = require_manifest_system_deps.createArrayBufferCensusGate;
213395
213733
  exports.createAuthenticatedFileServer = require_event_loop_stall_monitor.createAuthenticatedFileServer;
213396
213734
  exports.createBroker = createBroker2;
213397
213735
  exports.createBrokerDeviceManagerApi = require_manifest_system_deps.createBrokerDeviceManagerApi;
@@ -213403,6 +213741,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
213403
213741
  exports.createFileDataPlaneHandler = require_event_loop_stall_monitor.createFileDataPlaneHandler;
213404
213742
  exports.createHubCapForwardService = require_hub_cap_forward.createHubCapForwardService;
213405
213743
  exports.createHubService = createHubService;
213744
+ exports.createInspectorArrayBufferCensus = require_manifest_system_deps.createInspectorArrayBufferCensus;
213406
213745
  exports.createKernelHwAccel = require_manifest_system_deps.createKernelHwAccel;
213407
213746
  exports.createLocalTransport = require_manifest_system_deps.createLocalTransport;
213408
213747
  exports.createLoopDelayMeter = require_manifest_system_deps.createLoopDelayMeter;
@@ -213472,6 +213811,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
213472
213811
  return _camstack_types_node.findInPath;
213473
213812
  }
213474
213813
  });
213814
+ exports.formatArrayBufferCensus = require_manifest_system_deps.formatArrayBufferCensus;
213475
213815
  exports.formatEventPlane = require_manifest_system_deps.formatEventPlane;
213476
213816
  exports.formatHeapSpaces = require_manifest_system_deps.formatHeapSpaces;
213477
213817
  exports.formatLogLine = require_formatter.formatLogLine;
@@ -213527,6 +213867,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
213527
213867
  });
213528
213868
  exports.ipcParentLink = require_manifest_system_deps.ipcParentLink;
213529
213869
  exports.isAddonDeploySource = isAddonDeploySource;
213870
+ exports.isArrayBufferCensusReport = require_manifest_system_deps.isArrayBufferCensusReport;
213530
213871
  exports.isArrayOutputSchema = require_dist10.isArrayOutputSchema;
213531
213872
  exports.isClusterSecretMismatchError = isClusterSecretMismatchError;
213532
213873
  exports.isCollectionArrayMethod = require_dist10.isCollectionArrayMethod;
@@ -220331,6 +220672,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
220331
220672
  }
220332
220673
  };
220333
220674
  }
220675
+ function describeCustomActions(catalog) {
220676
+ const out = {};
220677
+ for (const [name, spec] of Object.entries(catalog)) out[name] = {
220678
+ kind: spec.kind,
220679
+ auth: spec.auth,
220680
+ scope: spec.scope,
220681
+ ...spec.caller === "required" ? { caller: "required" } : {}
220682
+ };
220683
+ return out;
220684
+ }
220334
220685
  var CustomModelDescriptorSchema = zod.z.object({
220335
220686
  stepId: zod.z.string(),
220336
220687
  entry: ModelCatalogEntrySchema
@@ -225290,6 +225641,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225290
225641
  "addon-crash-loop",
225291
225642
  "addon-update-available",
225292
225643
  "server-update-available",
225644
+ "wrapper-update-available",
225293
225645
  "alarm-triggered",
225294
225646
  "alarm-armed",
225295
225647
  "alarm-disarmed",
@@ -226147,6 +226499,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
226147
226499
  value: "server-update-available",
226148
226500
  label: "Server update available"
226149
226501
  },
226502
+ {
226503
+ value: "wrapper-update-available",
226504
+ label: "Container / app update available"
226505
+ },
226150
226506
  {
226151
226507
  value: "addon-updated",
226152
226508
  label: "Addons updated"
@@ -228610,7 +228966,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
228610
228966
  bucketStart: zod.z.number(),
228611
228967
  motion: zod.z.number().int(),
228612
228968
  object: zod.z.number().int(),
228613
- audio: zod.z.number().int()
228969
+ audio: zod.z.number().int(),
228970
+ /**
228971
+ * Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
228972
+ * as opposed to `audio`, which is their COUNT (D431).
228973
+ *
228974
+ * ABSENT when the bucket holds no audio event. Absence means "nothing was
228975
+ * measured", which is not the same claim as silence, and a lane that cannot
228976
+ * tell them apart paints a quiet hour at full scale — which is exactly what
228977
+ * the count-on-a-level-meter did.
228978
+ */
228979
+ audioDbfs: zod.z.number().optional()
228614
228980
  });
228615
228981
  var EVENT_DENSITY_BATCH_MAX = 200;
228616
228982
  var EventDensityForDeviceSchema = zod.z.object({
@@ -231609,6 +231975,45 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
231609
231975
  /** One operator-grade sentence: this node runs image X; the contract says Y. */
231610
231976
  message: zod.z.string()
231611
231977
  });
231978
+ var WrapperKindSchema = zod.z.enum([
231979
+ "docker",
231980
+ "electron",
231981
+ "native",
231982
+ "contradictory",
231983
+ "unknown"
231984
+ ]);
231985
+ var WrapperEvidenceSchema = zod.z.object({
231986
+ /** Which probe this row reports. */
231987
+ fact: zod.z.enum([
231988
+ "dockerenv-file",
231989
+ "proc-1-cgroup",
231990
+ "platform",
231991
+ "electron-app-version-env"
231992
+ ]),
231993
+ /**
231994
+ * `observed` — this process read it itself. `declared` — the shell told the
231995
+ * process and nothing here can check it. Only the Electron app version is
231996
+ * ever `declared`, and only because a child cannot see its parent.
231997
+ */
231998
+ mode: zod.z.enum(["observed", "declared"]),
231999
+ /** Did the probe support the fact it names? A `false` row is still evidence. */
232000
+ holds: zod.z.boolean(),
232001
+ /** What was actually read, verbatim enough for an operator to argue with. */
232002
+ detail: zod.z.string()
232003
+ });
232004
+ var WrapperIdentitySchema = zod.z.object({
232005
+ kind: WrapperKindSchema,
232006
+ /** Every probe that ran, in a stable order. Never empty. */
232007
+ evidence: zod.z.array(WrapperEvidenceSchema),
232008
+ /**
232009
+ * The version of the SHELL, when the shell has one it can state: the
232010
+ * Electron app version it declared, or the baked seed closure that
232011
+ * fingerprints a container / app bundle. `null` for `contradictory` and
232012
+ * `unknown` — which version belongs to the shell is precisely what is not
232013
+ * known there.
232014
+ */
232015
+ currentVersion: zod.z.string().nullable()
232016
+ });
231612
232017
  var ServerRollbackInfoSchema = zod.z.object({
231613
232018
  /** The version that failed (or was manually rolled back). */
231614
232019
  fromVersion: zod.z.string(),
@@ -231650,7 +232055,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
231650
232055
  * Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
231651
232056
  * version skew: an older provider's payload simply omits it.
231652
232057
  */
231653
- imageContract: ImageContractSchema.optional()
232058
+ imageContract: ImageContractSchema.optional(),
232059
+ /**
232060
+ * What this node runs INSIDE (see {@link WrapperIdentitySchema}). Optional
232061
+ * for version skew: an older provider's payload simply omits it, and an
232062
+ * absent wrapper is unknown — never `native`.
232063
+ */
232064
+ wrapper: WrapperIdentitySchema.optional()
231654
232065
  });
231655
232066
  var ServerUpdateCheckResultSchema = zod.z.object({
231656
232067
  packageName: zod.z.string(),
@@ -239461,7 +239872,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239461
239872
  });
239462
239873
  var RecordingAvailabilitySchema = zod.z.object({
239463
239874
  deviceId: zod.z.number(),
239464
- ranges: zod.z.array(RecordingRangeSchema)
239875
+ ranges: zod.z.array(RecordingRangeSchema),
239876
+ /**
239877
+ * Every profile this camera has footage in — not only the one `ranges`
239878
+ * describes (D433).
239879
+ *
239880
+ * `ranges` answers for ONE profile by design: the timeline is a single bar,
239881
+ * and enumerating all of them triples the directory reads for a bar that
239882
+ * would look identical. But the answer used to say nothing about that, so a
239883
+ * caller asking "what can I export?" read the single preferred profile as
239884
+ * the only one that exists — on this deployment every camera records `high`
239885
+ * AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
239886
+ * UI could not see it.
239887
+ *
239888
+ * Cheap: it is the same shallow directory read the profile CHOICE already
239889
+ * makes. Ask for a specific profile's ranges with the `profile` input.
239890
+ */
239891
+ profilesWithFootage: zod.z.array(zod.z.string())
239465
239892
  });
239466
239893
  var RecordingDaysSchema = zod.z.object({
239467
239894
  deviceId: zod.z.number(),
@@ -239472,7 +239899,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239472
239899
  var RecordingAvailabilityForDeviceSchema = zod.z.object({
239473
239900
  deviceId: zod.z.number(),
239474
239901
  read: zod.z.enum(["read", "unreadable"]),
239475
- ranges: zod.z.array(RecordingRangeSchema).readonly()
239902
+ ranges: zod.z.array(RecordingRangeSchema).readonly(),
239903
+ /** Same field, same meaning, as the singular answer (D433). A row that
239904
+ * dropped it would tell a grid caller the archive holds one profile.
239905
+ * Empty on an `'unreadable'` row: nobody looked. */
239906
+ profilesWithFootage: zod.z.array(zod.z.string()).readonly()
239476
239907
  });
239477
239908
  var RecordingDaysForDeviceSchema = zod.z.object({
239478
239909
  deviceId: zod.z.number(),
@@ -239687,7 +240118,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239687
240118
  getAvailability: require_sleep.method(zod.z.object({
239688
240119
  deviceId: zod.z.number(),
239689
240120
  fromMs: zod.z.number(),
239690
- toMs: zod.z.number()
240121
+ toMs: zod.z.number(),
240122
+ /**
240123
+ * Answer for THIS profile instead of the preferred one (D433). Absent
240124
+ * keeps the timeline's behaviour — one bar, one profile, one set of
240125
+ * reads. `profilesWithFootage` on the answer says what may be asked
240126
+ * for.
240127
+ */
240128
+ profile: zod.z.string().optional()
239691
240129
  }), RecordingAvailabilitySchema, {
239692
240130
  kind: "query",
239693
240131
  auth: "protected"
@@ -256574,7 +257012,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
256574
257012
  case "deviceTypes":
256575
257013
  return kind.startsWith("device-") || kind === "detection-blind";
256576
257014
  case "nodeIds":
256577
- return kind.startsWith("node-") || kind === "addon-crash-loop" || kind === "addon-update-available" || kind === "addon-updated" || kind === "server-update-available" || kind === "server-updated";
257015
+ return kind.startsWith("node-") || kind === "addon-crash-loop" || kind === "addon-update-available" || kind === "addon-updated" || kind === "server-update-available" || kind === "wrapper-update-available" || kind === "server-updated";
256578
257016
  case "packageNames":
256579
257017
  return kind.endsWith("update-available") || kind === "addon-updated" || kind === "server-updated";
256580
257018
  }
@@ -259610,6 +260048,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
259610
260048
  exports.WidgetMetadataSchema = WidgetMetadataSchema;
259611
260049
  exports.WidgetRemoteSchema = WidgetRemoteSchema;
259612
260050
  exports.WidgetSizeEnum = WidgetSizeEnum;
260051
+ exports.WrapperEvidenceSchema = WrapperEvidenceSchema;
260052
+ exports.WrapperIdentitySchema = WrapperIdentitySchema;
260053
+ exports.WrapperKindSchema = WrapperKindSchema;
259613
260054
  exports.YAMNET_TO_MACRO = YAMNET_TO_MACRO;
259614
260055
  exports.ZoneCrossingDirectionSchema = ZoneCrossingDirectionSchema;
259615
260056
  exports.ZoneCrossingSchema = ZoneCrossingSchema;
@@ -259738,6 +260179,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
259738
260179
  exports.deriveCameraSwitches = deriveCameraSwitches;
259739
260180
  exports.deriveDetailCropRect = deriveDetailCropRect;
259740
260181
  exports.deriveRecordingMode = deriveRecordingMode;
260182
+ exports.describeCustomActions = describeCustomActions;
259741
260183
  exports.describeModelVariant = describeModelVariant;
259742
260184
  exports.detectAccessRole = detectAccessRole;
259743
260185
  exports.detectionPipelineCapability = detectionPipelineCapability;
@@ -260657,6 +261099,7 @@ var require_server_root = __commonJS({
260657
261099
  DEV_UPLOADS_DIRNAME: /* @__PURE__ */ __name(() => DEV_UPLOADS_DIRNAME, "DEV_UPLOADS_DIRNAME"),
260658
261100
  DEV_UPLOADS_KEEP_COUNT: /* @__PURE__ */ __name(() => DEV_UPLOADS_KEEP_COUNT, "DEV_UPLOADS_KEEP_COUNT"),
260659
261101
  DEV_UPLOAD_MANIFEST_FILE: /* @__PURE__ */ __name(() => DEV_UPLOAD_MANIFEST_FILE, "DEV_UPLOAD_MANIFEST_FILE"),
261102
+ ELECTRON_APP_VERSION_ENV: /* @__PURE__ */ __name(() => ELECTRON_APP_VERSION_ENV, "ELECTRON_APP_VERSION_ENV"),
260660
261103
  HOST_EXTERNAL_SPECIFIERS: /* @__PURE__ */ __name(() => HOST_EXTERNAL_SPECIFIERS, "HOST_EXTERNAL_SPECIFIERS"),
260661
261104
  HUB_ROOT_SPEC: /* @__PURE__ */ __name(() => HUB_ROOT_SPEC2, "HUB_ROOT_SPEC"),
260662
261105
  PENDING_ROOT_SWAP_FILE: /* @__PURE__ */ __name(() => PENDING_ROOT_SWAP_FILE, "PENDING_ROOT_SWAP_FILE"),
@@ -260673,6 +261116,7 @@ var require_server_root = __commonJS({
260673
261116
  currentDir: /* @__PURE__ */ __name(() => currentDir, "currentDir"),
260674
261117
  currentEntryPath: /* @__PURE__ */ __name(() => currentEntryPath, "currentEntryPath"),
260675
261118
  detectWorkspaceRoot: /* @__PURE__ */ __name(() => detectWorkspaceRoot, "detectWorkspaceRoot"),
261119
+ detectWrapperIdentity: /* @__PURE__ */ __name(() => detectWrapperIdentity, "detectWrapperIdentity"),
260676
261120
  devChannelEpoch: /* @__PURE__ */ __name(() => devChannelEpoch, "devChannelEpoch"),
260677
261121
  devUploadManifestPath: /* @__PURE__ */ __name(() => devUploadManifestPath, "devUploadManifestPath"),
260678
261122
  devUploadVersionDir: /* @__PURE__ */ __name(() => devUploadVersionDir, "devUploadVersionDir"),
@@ -260690,6 +261134,7 @@ var require_server_root = __commonJS({
260690
261134
  readPendingRootSwap: /* @__PURE__ */ __name(() => readPendingRootSwap, "readPendingRootSwap"),
260691
261135
  readRestartIntentMarker: /* @__PURE__ */ __name(() => readRestartIntentMarker, "readRestartIntentMarker"),
260692
261136
  readServerRootState: /* @__PURE__ */ __name(() => readServerRootState, "readServerRootState"),
261137
+ readWrapperProbes: /* @__PURE__ */ __name(() => readWrapperProbes, "readWrapperProbes"),
260693
261138
  registerActiveRootResolver: /* @__PURE__ */ __name(() => registerActiveRootResolver, "registerActiveRootResolver"),
260694
261139
  restartIntentMarkerPath: /* @__PURE__ */ __name(() => restartIntentMarkerPath, "restartIntentMarkerPath"),
260695
261140
  rootEntryPath: /* @__PURE__ */ __name(() => rootEntryPath2, "rootEntryPath"),
@@ -261369,6 +261814,93 @@ var require_server_root = __commonJS({
261369
261814
  };
261370
261815
  }
261371
261816
  __name(assessImageContract, "assessImageContract");
261817
+ var ELECTRON_APP_VERSION_ENV = "CAMSTACK_AGENT_APP_VERSION";
261818
+ var CONTAINER_CGROUP_MARKERS = [
261819
+ "docker",
261820
+ "containerd",
261821
+ "kubepods",
261822
+ "podman"
261823
+ ];
261824
+ function cgroupNamesAContainer(contents) {
261825
+ const lower = contents.toLowerCase();
261826
+ return CONTAINER_CGROUP_MARKERS.some((marker) => lower.includes(marker));
261827
+ }
261828
+ __name(cgroupNamesAContainer, "cgroupNamesAContainer");
261829
+ function cgroupDetail(contents) {
261830
+ const lines = contents.split("\n").filter((line) => line.trim().length > 0);
261831
+ const named = lines.find((line) => cgroupNamesAContainer(line));
261832
+ return (named ?? lines[0] ?? "").trim().slice(0, 200);
261833
+ }
261834
+ __name(cgroupDetail, "cgroupDetail");
261835
+ function detectWrapperIdentity(inputs) {
261836
+ const { platform, dockerEnvFileExists, proc1Cgroup, electronAppVersion, seedVersion } = inputs;
261837
+ const linux = platform === "linux";
261838
+ const cgroupAnswered = proc1Cgroup !== null;
261839
+ const cgroupSaysContainer = proc1Cgroup !== null && cgroupNamesAContainer(proc1Cgroup);
261840
+ const containerObserved = dockerEnvFileExists || cgroupSaysContainer;
261841
+ const electronDeclared = electronAppVersion !== void 0 && electronAppVersion.length > 0;
261842
+ const containerRuledOut = !containerObserved && (cgroupAnswered || !linux);
261843
+ const evidence = [
261844
+ {
261845
+ fact: "platform",
261846
+ mode: "observed",
261847
+ holds: linux,
261848
+ detail: linux ? `process.platform=${platform} \u2014 a container is possible here` : `process.platform=${platform} \u2014 a linux container cannot run on this platform`
261849
+ },
261850
+ {
261851
+ fact: "dockerenv-file",
261852
+ mode: "observed",
261853
+ holds: dockerEnvFileExists,
261854
+ detail: dockerEnvFileExists ? "/.dockerenv exists" : "/.dockerenv not present"
261855
+ },
261856
+ {
261857
+ fact: "proc-1-cgroup",
261858
+ mode: "observed",
261859
+ holds: cgroupSaysContainer,
261860
+ detail: proc1Cgroup === null ? "/proc/1/cgroup unreadable \u2014 this probe did NOT answer" : cgroupDetail(proc1Cgroup)
261861
+ },
261862
+ {
261863
+ fact: "electron-app-version-env",
261864
+ mode: "declared",
261865
+ holds: electronDeclared,
261866
+ detail: electronDeclared ? `CAMSTACK_AGENT_APP_VERSION=${String(electronAppVersion)}` : "CAMSTACK_AGENT_APP_VERSION not set"
261867
+ }
261868
+ ];
261869
+ if (containerObserved && electronDeclared) {
261870
+ return {
261871
+ kind: "contradictory",
261872
+ evidence,
261873
+ currentVersion: null
261874
+ };
261875
+ }
261876
+ if (containerObserved) {
261877
+ return {
261878
+ kind: "docker",
261879
+ evidence,
261880
+ currentVersion: seedVersion
261881
+ };
261882
+ }
261883
+ if (electronDeclared) {
261884
+ return {
261885
+ kind: "electron",
261886
+ evidence,
261887
+ currentVersion: electronAppVersion ?? null
261888
+ };
261889
+ }
261890
+ if (containerRuledOut) {
261891
+ return {
261892
+ kind: "native",
261893
+ evidence,
261894
+ currentVersion: seedVersion
261895
+ };
261896
+ }
261897
+ return {
261898
+ kind: "unknown",
261899
+ evidence,
261900
+ currentVersion: null
261901
+ };
261902
+ }
261903
+ __name(detectWrapperIdentity, "detectWrapperIdentity");
261372
261904
  var fs4 = __toESM2(__require("fs"));
261373
261905
  var path4 = __toESM2(__require("path"));
261374
261906
  function detectWorkspaceRoot(fromDir) {
@@ -261510,6 +262042,26 @@ var require_server_root = __commonJS({
261510
262042
  var NPM_INSTALL_TIMEOUT_MS = 15 * 6e4;
261511
262043
  var RESTART_REASON_PREFIX = "server-update";
261512
262044
  var STALE_TRANSIENT_MS = 24 * 60 * 60 * 1e3;
262045
+ function readWrapperProbes() {
262046
+ let proc1Cgroup = null;
262047
+ try {
262048
+ proc1Cgroup = fs6.readFileSync("/proc/1/cgroup", "utf-8");
262049
+ } catch {
262050
+ proc1Cgroup = null;
262051
+ }
262052
+ let dockerEnvFileExists = false;
262053
+ try {
262054
+ dockerEnvFileExists = fs6.existsSync("/.dockerenv");
262055
+ } catch {
262056
+ dockerEnvFileExists = false;
262057
+ }
262058
+ return {
262059
+ platform: process.platform,
262060
+ dockerEnvFileExists,
262061
+ proc1Cgroup
262062
+ };
262063
+ }
262064
+ __name(readWrapperProbes, "readWrapperProbes");
261513
262065
  function readPackageVersion(pkgJsonPath) {
261514
262066
  try {
261515
262067
  const raw = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf-8"));
@@ -261536,6 +262088,7 @@ var require_server_root = __commonJS({
261536
262088
  execNpm;
261537
262089
  ensureNativePrebuildsFn;
261538
262090
  env;
262091
+ readWrapperProbesFn;
261539
262092
  now;
261540
262093
  runningPackageJsonPath;
261541
262094
  workspaceProbeDir;
@@ -261563,6 +262116,7 @@ var require_server_root = __commonJS({
261563
262116
  });
261564
262117
  this.ensureNativePrebuildsFn = options.ensureNativePrebuilds ?? (async () => void 0);
261565
262118
  this.env = options.env ?? process.env;
262119
+ this.readWrapperProbesFn = options.readWrapperProbes ?? readWrapperProbes;
261566
262120
  this.now = options.now ?? Date.now;
261567
262121
  this.runningPackageJsonPath = options.runningPackageJsonPath;
261568
262122
  this.workspaceProbeDir = options.workspaceProbeDir;
@@ -261588,6 +262142,20 @@ var require_server_root = __commonJS({
261588
262142
  if (seedDir === void 0 || seedDir.length === 0) return null;
261589
262143
  return readPackageVersion(path6.join(seedDir, "package.json"));
261590
262144
  }
262145
+ /**
262146
+ * What this node runs INSIDE. Observed where it can be (the container
262147
+ * probes), declared only where it cannot (the Electron app version, which a
262148
+ * child process has no other way to learn). A contradiction between the two
262149
+ * is reported as `contradictory`, never resolved — see `wrapper-identity.ts`
262150
+ * and D437.
262151
+ */
262152
+ wrapperIdentity() {
262153
+ return detectWrapperIdentity({
262154
+ ...this.readWrapperProbesFn(),
262155
+ electronAppVersion: this.env[ELECTRON_APP_VERSION_ENV],
262156
+ seedVersion: this.seedVersion()
262157
+ });
262158
+ }
261591
262159
  rootDir() {
261592
262160
  return serverRootDir(this.dataDir);
261593
262161
  }
@@ -261626,7 +262194,11 @@ var require_server_root = __commonJS({
261626
262194
  seedVersion: this.seedVersion(),
261627
262195
  latestVersion,
261628
262196
  runningVersion
261629
- })
262197
+ }),
262198
+ // The other half of the same question. `imageContract` says the shell is
262199
+ // BEHIND; this says WHAT the shell is, which is the only thing that can
262200
+ // turn that verdict into an instruction the operator can act on.
262201
+ wrapper: this.wrapperIdentity()
261630
262202
  };
261631
262203
  }
261632
262204
  // ── Check ─────────────────────────────────────────────────────────────
@@ -392998,6 +393570,7 @@ var require_update_availability_emitter = __commonJS({
392998
393570
  nodeId: scope
392999
393571
  }));
393000
393572
  const head = list[0];
393573
+ const wrapperKind = head.wrapperKind;
393001
393574
  this.eventBus.emit({
393002
393575
  id: `update.available:${target}:${scope}:${packages.map((pkg) => `${pkg.packageName}@${pkg.latestVersion}`).toSorted().join(",")}`,
393003
393576
  timestamp: /* @__PURE__ */ new Date(),
@@ -393010,7 +393583,8 @@ var require_update_availability_emitter = __commonJS({
393010
393583
  latestVersion: head.latestVersion,
393011
393584
  packages,
393012
393585
  nodeId: scope,
393013
- nodeIds: [scope]
393586
+ nodeIds: [scope],
393587
+ ...wrapperKind !== void 0 ? { wrapperKind } : {}
393014
393588
  }
393015
393589
  });
393016
393590
  }
@@ -409777,6 +410351,7 @@ var require_update_check_scheduler = __commonJS({
409777
410351
  continue;
409778
410352
  }
409779
410353
  await this.attempt("node server package", node.id, () => this.targets.checkNodeServerUpdate(node.id, node.isHub));
410354
+ await this.attempt("node wrapper", node.id, () => this.targets.checkWrapperUpdate(node.id, node.isHub));
409780
410355
  if (node.isHub)
409781
410356
  continue;
409782
410357
  await this.attempt("agent addon packages", node.id, () => this.targets.checkAgentAddons(node.id));
@@ -424327,6 +424902,161 @@ var require_ws_request_census = __commonJS({
424327
424902
  }
424328
424903
  });
424329
424904
 
424905
+ // ../../server/backend/dist/api/trpc/ws-slow-consumer-guard.js
424906
+ var require_ws_slow_consumer_guard = __commonJS({
424907
+ "../../server/backend/dist/api/trpc/ws-slow-consumer-guard.js"(exports) {
424908
+ "use strict";
424909
+ Object.defineProperty(exports, "__esModule", { value: true });
424910
+ exports.WsSlowConsumerGuard = exports.WS_SLOW_CONSUMER_POLL_MS = exports.WS_SLOW_CONSUMER_GRACE_MS = exports.WS_SLOW_CONSUMER_MAX_BUFFERED_BYTES = void 0;
424911
+ exports.wsConnectionFacts = wsConnectionFacts;
424912
+ var client_ip_js_1 = require_client_ip();
424913
+ exports.WS_SLOW_CONSUMER_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;
424914
+ exports.WS_SLOW_CONSUMER_GRACE_MS = 3e4;
424915
+ exports.WS_SLOW_CONSUMER_POLL_MS = 5e3;
424916
+ var ABNORMAL_CLOSURE = 1006;
424917
+ var UNIDENTIFIED = "(unidentified)";
424918
+ var WsSlowConsumerGuard = class {
424919
+ connections = /* @__PURE__ */ new Set();
424920
+ bySocket = /* @__PURE__ */ new WeakMap();
424921
+ logger;
424922
+ maxBufferedBytes;
424923
+ graceMs;
424924
+ pollMs;
424925
+ now;
424926
+ timer = null;
424927
+ constructor(options) {
424928
+ this.logger = options.logger;
424929
+ this.maxBufferedBytes = options.maxBufferedBytes ?? exports.WS_SLOW_CONSUMER_MAX_BUFFERED_BYTES;
424930
+ this.graceMs = options.graceMs ?? exports.WS_SLOW_CONSUMER_GRACE_MS;
424931
+ this.pollMs = options.pollMs ?? exports.WS_SLOW_CONSUMER_POLL_MS;
424932
+ this.now = options.now ?? (() => Date.now());
424933
+ }
424934
+ /** Watch one connection until it closes. */
424935
+ attach(socket, facts) {
424936
+ const connection = {
424937
+ socket,
424938
+ facts,
424939
+ openedAt: this.now(),
424940
+ principal: UNIDENTIFIED,
424941
+ overSince: null,
424942
+ peakBufferedBytes: 0,
424943
+ terminated: false
424944
+ };
424945
+ this.connections.add(connection);
424946
+ this.bySocket.set(socket, connection);
424947
+ socket.once("close", (code, reason) => {
424948
+ this.connections.delete(connection);
424949
+ this.logger.info("tRPC WS connection closed", {
424950
+ meta: {
424951
+ ...this.describe(connection),
424952
+ code,
424953
+ reason: reason.length > 0 ? reason.toString("utf8") : "",
424954
+ closedBy: connection.terminated ? "slow-consumer-guard" : code === ABNORMAL_CLOSURE ? "peer gone or keep-alive PING unanswered" : "peer"
424955
+ }
424956
+ });
424957
+ });
424958
+ }
424959
+ /** Name the connection's principal once the WS context resolved it. */
424960
+ identify(socket, principal) {
424961
+ const connection = this.bySocket.get(socket);
424962
+ if (connection !== void 0)
424963
+ connection.principal = principal;
424964
+ }
424965
+ /** Read every queue once. Exposed for the spec; production runs it on {@link start}. */
424966
+ poll() {
424967
+ const at = this.now();
424968
+ for (const connection of this.connections) {
424969
+ try {
424970
+ this.judge(connection, at);
424971
+ } catch (err) {
424972
+ this.logger.warn("tRPC WS slow-consumer guard could not read a socket", {
424973
+ meta: { ...this.describe(connection), error: errorMessage(err) }
424974
+ });
424975
+ }
424976
+ }
424977
+ }
424978
+ start() {
424979
+ if (this.timer !== null)
424980
+ return;
424981
+ this.timer = setInterval(() => this.poll(), this.pollMs);
424982
+ this.timer.unref?.();
424983
+ }
424984
+ stop() {
424985
+ if (this.timer === null)
424986
+ return;
424987
+ clearInterval(this.timer);
424988
+ this.timer = null;
424989
+ }
424990
+ judge(connection, at) {
424991
+ if (connection.terminated)
424992
+ return;
424993
+ const buffered = connection.socket.bufferedAmount;
424994
+ if (buffered > connection.peakBufferedBytes)
424995
+ connection.peakBufferedBytes = buffered;
424996
+ if (buffered <= this.maxBufferedBytes) {
424997
+ if (connection.overSince !== null) {
424998
+ this.logger.info("tRPC WS client drained a backlog that had crossed the bound", {
424999
+ meta: {
425000
+ ...this.describe(connection),
425001
+ overForMs: at - connection.overSince,
425002
+ bufferedBytes: buffered
425003
+ }
425004
+ });
425005
+ connection.overSince = null;
425006
+ connection.peakBufferedBytes = buffered;
425007
+ }
425008
+ return;
425009
+ }
425010
+ connection.overSince ??= at;
425011
+ const overForMs = at - connection.overSince;
425012
+ if (overForMs < this.graceMs)
425013
+ return;
425014
+ connection.terminated = true;
425015
+ this.logger.warn("tRPC WS client disconnected \u2014 slow consumer: it stopped reading and its send queue stayed above the bound for the whole grace period (a backgrounded viewer keeps the socket open with its JS suspended; the queue lived in hub-main old_space)", {
425016
+ meta: {
425017
+ ...this.describe(connection),
425018
+ bufferedBytes: buffered,
425019
+ overForMs,
425020
+ boundBytes: this.maxBufferedBytes,
425021
+ graceMs: this.graceMs
425022
+ }
425023
+ });
425024
+ connection.socket.terminate();
425025
+ }
425026
+ describe(connection) {
425027
+ return {
425028
+ ip: connection.facts.ip ?? UNIDENTIFIED,
425029
+ userAgent: connection.facts.userAgent ?? UNIDENTIFIED,
425030
+ principal: connection.principal,
425031
+ bytesRead: safeCount(connection.facts.bytesRead),
425032
+ bytesWritten: safeCount(connection.facts.bytesWritten),
425033
+ peakBufferedBytes: connection.peakBufferedBytes,
425034
+ connectionAgeMs: this.now() - connection.openedAt
425035
+ };
425036
+ }
425037
+ };
425038
+ exports.WsSlowConsumerGuard = WsSlowConsumerGuard;
425039
+ function safeCount(read) {
425040
+ try {
425041
+ return read();
425042
+ } catch {
425043
+ return null;
425044
+ }
425045
+ }
425046
+ function errorMessage(err) {
425047
+ return err instanceof Error ? err.message : String(err);
425048
+ }
425049
+ function wsConnectionFacts(req) {
425050
+ return {
425051
+ ip: (0, client_ip_js_1.extractClientIp)(req),
425052
+ userAgent: (0, client_ip_js_1.extractUserAgent)(req),
425053
+ bytesRead: () => req.socket.bytesRead,
425054
+ bytesWritten: () => req.socket.bytesWritten
425055
+ };
425056
+ }
425057
+ }
425058
+ });
425059
+
424330
425060
  // ../../server/backend/dist/auth/addon-route-jwt-gate.js
424331
425061
  var require_addon_route_jwt_gate = __commonJS({
424332
425062
  "../../server/backend/dist/auth/addon-route-jwt-gate.js"(exports) {
@@ -425196,6 +425926,58 @@ var require_integration_visibility = __commonJS({
425196
425926
  }
425197
425927
  });
425198
425928
 
425929
+ // ../../server/backend/dist/core/addon/forked-custom-actions.js
425930
+ var require_forked_custom_actions = __commonJS({
425931
+ "../../server/backend/dist/core/addon/forked-custom-actions.js"(exports) {
425932
+ "use strict";
425933
+ Object.defineProperty(exports, "__esModule", { value: true });
425934
+ exports.toForkedCustomActionsSpec = toForkedCustomActionsSpec;
425935
+ exports.childCatalogsFingerprint = childCatalogsFingerprint;
425936
+ exports.summarizeChildCatalogs = summarizeChildCatalogs;
425937
+ var zod_1 = require_zod();
425938
+ function toForkedCustomActionsSpec(descriptor) {
425939
+ const spec = {};
425940
+ for (const [name, action] of Object.entries(descriptor)) {
425941
+ spec[name] = {
425942
+ input: zod_1.z.unknown(),
425943
+ output: zod_1.z.unknown(),
425944
+ kind: action.kind,
425945
+ auth: action.auth,
425946
+ scope: action.scope,
425947
+ ...action.caller === "required" ? { caller: "required" } : {}
425948
+ };
425949
+ }
425950
+ return spec;
425951
+ }
425952
+ function childCatalogsFingerprint(catalogs) {
425953
+ return JSON.stringify(sortKeysDeep(catalogs));
425954
+ }
425955
+ function sortKeysDeep(value) {
425956
+ if (Array.isArray(value))
425957
+ return value.map(sortKeysDeep);
425958
+ if (value === null || typeof value !== "object")
425959
+ return value;
425960
+ const out = {};
425961
+ for (const key of Object.keys(value).sort()) {
425962
+ out[key] = sortKeysDeep(Reflect.get(value, key));
425963
+ }
425964
+ return out;
425965
+ }
425966
+ function summarizeChildCatalogs(catalogs) {
425967
+ const withActions = [];
425968
+ const withoutActions = [];
425969
+ for (const addonId of Object.keys(catalogs).sort()) {
425970
+ const count = Object.keys(catalogs[addonId] ?? {}).length;
425971
+ if (count > 0)
425972
+ withActions.push(`${addonId}:${count}`);
425973
+ else
425974
+ withoutActions.push(addonId);
425975
+ }
425976
+ return { withActions, withoutActions };
425977
+ }
425978
+ }
425979
+ });
425980
+
425199
425981
  // ../../server/backend/dist/core/addon/prune-misplaced-addons.js
425200
425982
  var require_prune_misplaced_addons = __commonJS({
425201
425983
  "../../server/backend/dist/core/addon/prune-misplaced-addons.js"(exports) {
@@ -425290,49 +426072,6 @@ var require_prune_misplaced_addons = __commonJS({
425290
426072
  }
425291
426073
  });
425292
426074
 
425293
- // ../../server/backend/dist/core/addon/require-cache.js
425294
- var require_require_cache = __commonJS({
425295
- "../../server/backend/dist/core/addon/require-cache.js"(exports) {
425296
- "use strict";
425297
- var __importDefault = exports && exports.__importDefault || function(mod) {
425298
- return mod && mod.__esModule ? mod : { "default": mod };
425299
- };
425300
- Object.defineProperty(exports, "__esModule", { value: true });
425301
- exports.purgeRequireCacheUnder = purgeRequireCacheUnder;
425302
- exports.bundleGraphRootFor = bundleGraphRootFor;
425303
- var node_fs_1 = __importDefault(__require("fs"));
425304
- var node_path_1 = __importDefault(__require("path"));
425305
- function purgeRequireCacheUnder(dir) {
425306
- try {
425307
- const cache2 = __require.cache;
425308
- if (cache2 === void 0)
425309
- return;
425310
- const prefixes = candidatePrefixes(dir);
425311
- for (const key of Object.keys(cache2)) {
425312
- if (prefixes.some((prefix) => key.startsWith(prefix)))
425313
- delete cache2[key];
425314
- }
425315
- } catch {
425316
- }
425317
- }
425318
- function bundleGraphRootFor(addonDir, entryPath) {
425319
- const distDir = node_path_1.default.resolve(addonDir, "dist");
425320
- const withSep = distDir.endsWith(node_path_1.default.sep) ? distDir : `${distDir}${node_path_1.default.sep}`;
425321
- return entryPath.startsWith(withSep) ? distDir : node_path_1.default.dirname(entryPath);
425322
- }
425323
- function candidatePrefixes(dir) {
425324
- const withSep = (d) => d.endsWith(node_path_1.default.sep) ? d : `${d}${node_path_1.default.sep}`;
425325
- const given = withSep(dir);
425326
- let real = given;
425327
- try {
425328
- real = withSep(node_fs_1.default.realpathSync(dir));
425329
- } catch {
425330
- }
425331
- return real === given ? [given] : [given, real];
425332
- }
425333
- }
425334
- });
425335
-
425336
426075
  // ../../server/backend/dist/core/addon/runner-convergence.js
425337
426076
  var require_runner_convergence = __commonJS({
425338
426077
  "../../server/backend/dist/core/addon/runner-convergence.js"(exports) {
@@ -425451,7 +426190,6 @@ var require_addon_registry_service = __commonJS({
425451
426190
  var fs = __importStar(__require("fs"));
425452
426191
  var os = __importStar(__require("os"));
425453
426192
  var path = __importStar(__require("path"));
425454
- var node_url_1 = __require("url");
425455
426193
  var system_1 = require_dist3();
425456
426194
  var types_1 = require_dist4();
425457
426195
  var client_1 = require_dist2();
@@ -425461,8 +426199,8 @@ var require_addon_registry_service = __commonJS({
425461
426199
  var device_meta_mirror_js_1 = require_device_meta_mirror();
425462
426200
  var integration_visibility_js_1 = require_integration_visibility();
425463
426201
  var package_dir_utils_1 = require_package_dir_utils();
426202
+ var forked_custom_actions_js_1 = require_forked_custom_actions();
425464
426203
  var prune_misplaced_addons_js_1 = require_prune_misplaced_addons();
425465
- var require_cache_js_1 = require_require_cache();
425466
426204
  var runner_convergence_1 = require_runner_convergence();
425467
426205
  var runner_spawn_fanout_js_1 = require_runner_spawn_fanout();
425468
426206
  function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
@@ -425608,10 +426346,14 @@ var require_addon_registry_service = __commonJS({
425608
426346
  // through this from the `api.addons.custom` tRPC procedure.
425609
426347
  customActionRegistry = new system_1.CustomActionRegistry();
425610
426348
  /**
425611
- * Keeps `purgeRequireCacheUnder` + `import()` atomic per addon directory now
425612
- * that the boot runner plan overlaps (D171). See `runner-spawn-fanout.ts`.
426349
+ * Fingerprint of the custom-action catalogs each UDS child last DESCRIBED
426350
+ * (keyed by child id), and the addon ids registered from that description.
426351
+ * A re-register carrying the same catalogs is a no-op with no line; a child
426352
+ * whose new description drops an addon has that addon's actions withdrawn.
426353
+ * See {@link applyChildCustomActions}.
425613
426354
  */
425614
- moduleLoadSerializer = (0, runner_spawn_fanout_js_1.createKeyedSerializer)();
426355
+ childCatalogFingerprints = /* @__PURE__ */ new Map();
426356
+ childCatalogAddons = /* @__PURE__ */ new Map();
425615
426357
  /**
425616
426358
  * AddonIds whose group-runner disconnect is operator-initiated (update /
425617
426359
  * restart / uninstall). The Moleculer `$node.disconnected` handler skips
@@ -427215,6 +427957,7 @@ var require_addon_registry_service = __commonJS({
427215
427957
  wireCapabilityConsumers() {
427216
427958
  if (!this.capabilityRegistry)
427217
427959
  return;
427960
+ this.moleculer.onChildCustomActions((child) => this.applyChildCustomActions(child));
427218
427961
  this.eventBusService.subscribe({ category: "capability:provider-registered" }, (event) => {
427219
427962
  const rawCapability = event.data["capability"];
427220
427963
  const rawAddonId = event.data["addonId"];
@@ -427778,7 +428521,6 @@ var require_addon_registry_service = __commonJS({
427778
428521
  throw new Error(`Failed to spawn runner "${runnerId}" (${addons.length} addons): ${(0, types_1.errMsg)(err)}`, { cause: err });
427779
428522
  }
427780
428523
  for (const { addonId } of addons) {
427781
- await this.registerForkedAddonCustomActions(addonId, runnerId);
427782
428524
  const entry = this.addonEntries.get(addonId);
427783
428525
  if (entry) {
427784
428526
  entry.initialized = true;
@@ -427859,86 +428601,72 @@ var require_addon_registry_service = __commonJS({
427859
428601
  this.recentGroupRespawns.set(runnerId, Date.now());
427860
428602
  }
427861
428603
  for (const { addonId: memberId } of roster) {
427862
- await this.registerForkedAddonCustomActions(memberId, runnerId);
427863
428604
  const memberEntry = this.addonEntries.get(memberId);
427864
428605
  if (memberEntry)
427865
428606
  memberEntry.initialized = true;
427866
428607
  }
427867
428608
  }
427868
428609
  /**
427869
- * (Re-)register the custom-action catalog for a forked / group-hosted
427870
- * addon against the shared `CustomActionRegistry`.
427871
- *
427872
- * The catalog (zod `input`/`output` specs) is read STATICALLY from the
427873
- * addon module's `customActions` named export — the handler dispatches
427874
- * over UDS via `LocalChildRegistry.callAddonOnChild(addonId,
427875
- * {target:'custom', action, args})` (F3 — replaces the removed per-addon
427876
- * Moleculer `custom.<action>` action), so the only divergence vs an
427877
- * in-process addon is the transport, exactly like cap methods. The hub's
427878
- * `CustomActionRegistry` validates input/output around this dispatch.
427879
- *
427880
- * Why a fresh import: `this.addonLoader`'s `module` namespace is
427881
- * captured once at boot. After a hot-update (`installFromTgz` →
427882
- * `restartAddon`) the on-disk bundle is newer than that cached module,
427883
- * so re-reading the boot-time `module` would register a STALE catalog
427884
- * (or none at all, if the addon was first installed after boot). We
427885
- * re-`import()` the entry with a cache-busting query so Node's ESM
427886
- * loader hands back the current bundle.
427887
- *
427888
- * Idempotent: drops any prior registration first. No-op (with a debug
427889
- * log) when the addon exports no `customActions` — most addons don't.
427890
- */
427891
- async registerForkedAddonCustomActions(addonId, runnerId) {
427892
- this.customActionRegistry.unregisterAddon(addonId);
427893
- const entry = this.addonEntries.get(addonId);
427894
- const addonDir = entry?.addonDir;
427895
- const declarationEntry = entry?.declaration?.entry;
427896
- if (!addonDir || !declarationEntry)
428610
+ * Apply the custom-action catalogs a UDS child DESCRIBED in its register
428611
+ * frame (`RegisteredChild.customActions`, schemas stripped) to the hub-wide
428612
+ * `CustomActionRegistry`, dispatching each action back to that child over
428613
+ * the addon-call plane.
428614
+ *
428615
+ * ## Why the child describes, and the hub never reads the bundle
428616
+ *
428617
+ * Until D444 this was `registerForkedAddonCustomActions`: purge the require
428618
+ * cache, re-import the addon's entry INSIDE hub-main, read its
428619
+ * `customActions` export. Measured 2026-09-10 on the live hub: 9.3 MB of
428620
+ * `addon-pipeline-orchestrator/dist/index.js` evaluated in hub-main by a
428621
+ * sampling heap profile, ten deploys in the process's 23 h life, NONE of
428622
+ * which exported a catalog — and the only line saying so was at `debug`,
428623
+ * which the hub does not write. Every evaluation pins whatever module-scope
428624
+ * state the evicted graph left behind, and the bundle-reload guard existed
428625
+ * only to keep that re-execution survivable (D284). The runner is the
428626
+ * process that owns the addon; it describes its catalog once its init loop
428627
+ * has produced it, in the post-init register frame, and validates the
428628
+ * action's input and output itself (`child-addon-call-dispatch.ts`).
428629
+ *
428630
+ * ## What one description does
428631
+ *
428632
+ * - identical to the child's last description → nothing, silently (a
428633
+ * native-cap change or reconnect re-sends the same frame);
428634
+ * - otherwise every addon in it is (re-)registered — an EMPTY catalog
428635
+ * withdraws that addon's actions — and an addon the previous description
428636
+ * named but this one does not is withdrawn too;
428637
+ * - and ONE `info` line per applied description names what it produced,
428638
+ * including "nothing": a load that produces nothing and says nothing is
428639
+ * the branch D391 forbids.
428640
+ */
428641
+ applyChildCustomActions(child) {
428642
+ const catalogs = child.customActions;
428643
+ if (catalogs === void 0)
427897
428644
  return;
427898
- const entryFile = declarationEntry.replace(/^\.\//, "").replace(/^src\//, "dist/").replace(/\.ts$/, ".js");
427899
- let entryPath = path.resolve(addonDir, entryFile);
427900
- if (!fs.existsSync(entryPath)) {
427901
- const base = entryPath.replace(/\.(js|cjs|mjs)$/, "");
427902
- const alternatives = [
427903
- `${base}.cjs`,
427904
- `${base}.mjs`,
427905
- path.resolve(addonDir, "dist", "index.js"),
427906
- path.resolve(addonDir, "dist", "index.cjs"),
427907
- path.resolve(addonDir, "dist", "index.mjs"),
427908
- path.resolve(addonDir, declarationEntry)
427909
- ];
427910
- entryPath = alternatives.find((p) => fs.existsSync(p)) ?? entryPath;
427911
- }
427912
- if (!fs.existsSync(entryPath))
427913
- return;
427914
- let catalog;
427915
- try {
427916
- const graphRoot = (0, require_cache_js_1.bundleGraphRootFor)(addonDir, entryPath);
427917
- const modUnknown = await this.moduleLoadSerializer.run(graphRoot, async () => {
427918
- (0, require_cache_js_1.purgeRequireCacheUnder)(graphRoot);
427919
- const cacheBustedUrl = `${(0, node_url_1.pathToFileURL)(entryPath).href}?t=${Date.now()}`;
427920
- const nativeImport = new Function("specifier", "return import(specifier)");
427921
- return nativeImport(cacheBustedUrl);
427922
- });
427923
- catalog = modUnknown && typeof modUnknown === "object" ? modUnknown["customActions"] : void 0;
427924
- } catch (err) {
427925
- this.logger.warn("Failed to load custom-action catalog for forked addon", {
427926
- tags: { addonId },
427927
- meta: { error: (0, types_1.errMsg)(err) }
427928
- });
427929
- return;
427930
- }
427931
- if (!catalog || typeof catalog !== "object") {
427932
- this.logger.debug("Forked addon exports no custom actions", {
427933
- tags: { addonId },
427934
- meta: { runnerId }
427935
- });
428645
+ const fingerprint = (0, forked_custom_actions_js_1.childCatalogsFingerprint)(catalogs);
428646
+ if (this.childCatalogFingerprints.get(child.childId) === fingerprint)
427936
428647
  return;
428648
+ this.childCatalogFingerprints.set(child.childId, fingerprint);
428649
+ const described = new Set(Object.keys(catalogs));
428650
+ for (const previous of this.childCatalogAddons.get(child.childId) ?? []) {
428651
+ if (!described.has(previous))
428652
+ this.customActionRegistry.unregisterAddon(previous);
428653
+ }
428654
+ this.childCatalogAddons.set(child.childId, described);
428655
+ for (const [addonId, descriptor] of Object.entries(catalogs)) {
428656
+ if (Object.keys(descriptor).length === 0) {
428657
+ this.customActionRegistry.unregisterAddon(addonId);
428658
+ continue;
428659
+ }
428660
+ this.customActionRegistry.registerAddon(addonId, (0, forked_custom_actions_js_1.toForkedCustomActionsSpec)(descriptor), (action, input, caller) => this.dispatchForkedCustomAction(addonId, action, input, caller));
427937
428661
  }
427938
- this.customActionRegistry.registerAddon(addonId, catalog, (action, input, caller) => this.dispatchForkedCustomAction(addonId, action, input, caller));
427939
- this.logger.info("Runner addon custom actions registered", {
427940
- tags: { addonId },
427941
- meta: { runnerId }
428662
+ const summary = (0, forked_custom_actions_js_1.summarizeChildCatalogs)(catalogs);
428663
+ this.logger.info("UDS child custom-action catalogs applied", {
428664
+ meta: {
428665
+ childId: child.childId,
428666
+ incarnation: child.incarnation,
428667
+ registered: summary.withActions.length > 0 ? summary.withActions.join(",") : "(none)",
428668
+ withoutActions: summary.withoutActions.length > 0 ? summary.withoutActions.join(",") : "(none)"
428669
+ }
427942
428670
  });
427943
428671
  }
427944
428672
  /**
@@ -429908,6 +430636,15 @@ var require_moleculer_service = __commonJS({
429908
430636
  * See {@link ChildManifestGate}.
429909
430637
  */
429910
430638
  childManifestGate = new ChildManifestGate();
430639
+ /**
430640
+ * Receives every child register frame that DESCRIBES its custom-action
430641
+ * catalogs (the post-init one and every re-register after it). Consulted
430642
+ * BEFORE {@link ChildManifestGate}: the gate compares caps, and a post-init
430643
+ * register of a non-device runner carries the same caps as its pre-init one
430644
+ * — skipping it would lose the only frame that carries the catalogs (D444).
430645
+ * The consumer (`AddonRegistryService`) dedupes by catalog fingerprint.
430646
+ */
430647
+ childCustomActionsHandler = null;
429911
430648
  /**
429912
430649
  * Fixed-period agent-readiness snapshot sweep (D8 reconcile) — repairs
429913
430650
  * agent-origin readiness deltas lost while the agent stayed connected.
@@ -429985,6 +430722,14 @@ var require_moleculer_service = __commonJS({
429985
430722
  get childRegistry() {
429986
430723
  return this.localChildRegistry;
429987
430724
  }
430725
+ /**
430726
+ * Register the (single) handler for a child's described custom-action
430727
+ * catalogs. See {@link childCustomActionsHandler}. Called once by
430728
+ * `AddonRegistryService` when it wires its consumers.
430729
+ */
430730
+ onChildCustomActions(handler) {
430731
+ this.childCustomActionsHandler = handler;
430732
+ }
429988
430733
  /** The CapRouteResolver once onModuleInit has completed; null before that. */
429989
430734
  get capRouteResolver() {
429990
430735
  return this.resolver;
@@ -430237,6 +430982,15 @@ var require_moleculer_service = __commonJS({
430237
430982
  registry.onChildRegistered((child) => {
430238
430983
  const hubNodeId = this.brokerSafe.nodeID;
430239
430984
  const childNodeId = `${hubNodeId}/${child.childId}`;
430985
+ if (child.customActions !== void 0) {
430986
+ try {
430987
+ this.childCustomActionsHandler?.(child);
430988
+ } catch (err) {
430989
+ logger.warn("UDS child custom-action catalogs could not be applied", {
430990
+ meta: { nodeId: childNodeId, incarnation: child.incarnation, error: (0, types_1.errMsg)(err) }
430991
+ });
430992
+ }
430993
+ }
430240
430994
  const params = buildChildUdsManifest(childNodeId, child.childId, child.caps);
430241
430995
  if (!this.childManifestGate.shouldApply(child.childId, child.incarnation, params)) {
430242
430996
  if (this.childManifestGate.sampleDue()) {
@@ -432030,6 +432784,62 @@ var require_topology_emitter_service = __commonJS({
432030
432784
  }
432031
432785
  });
432032
432786
 
432787
+ // ../../server/backend/dist/core/updates/wrapper-update-checker.js
432788
+ var require_wrapper_update_checker = __commonJS({
432789
+ "../../server/backend/dist/core/updates/wrapper-update-checker.js"(exports) {
432790
+ "use strict";
432791
+ Object.defineProperty(exports, "__esModule", { value: true });
432792
+ exports.WrapperUpdateChecker = exports.WRAPPER_PACKAGE_NAME = void 0;
432793
+ exports.WRAPPER_PACKAGE_NAME = "camstack-wrapper";
432794
+ var WrapperUpdateChecker = class {
432795
+ emitter;
432796
+ logger;
432797
+ readStatus;
432798
+ constructor(options) {
432799
+ this.emitter = options.emitter;
432800
+ this.logger = options.logger;
432801
+ this.readStatus = options.readStatus;
432802
+ }
432803
+ async check(nodeId, isHub) {
432804
+ const status = await this.readStatus(nodeId, isHub);
432805
+ const wrapper = status.wrapper;
432806
+ const contract = status.imageContract;
432807
+ if (wrapper === void 0 || contract === void 0) {
432808
+ throw new Error(`node ${nodeId} reports no wrapper identity (server-management payload predates it)`);
432809
+ }
432810
+ if (contract.state === "in-sync") {
432811
+ this.emitter.publishSnapshot("wrapper", [], nodeId);
432812
+ return;
432813
+ }
432814
+ const seedVersion = contract.seedVersion;
432815
+ const contractVersion = contract.contractVersion;
432816
+ const behind = contract.state === "behind-patch" || contract.state === "behind-series";
432817
+ if (!behind || seedVersion === null || contractVersion === null) {
432818
+ this.logger.warn("Wrapper check inconclusive \u2014 nothing published", {
432819
+ tags: { nodeId },
432820
+ meta: { state: contract.state, wrapperKind: wrapper.kind, seedVersion, contractVersion }
432821
+ });
432822
+ return;
432823
+ }
432824
+ const candidate = {
432825
+ target: "wrapper",
432826
+ packageName: exports.WRAPPER_PACKAGE_NAME,
432827
+ // The SHELL's versions, not the running code's. `wrapper.currentVersion`
432828
+ // is what the shell says about ITSELF (an Electron app version), which
432829
+ // is not on the same scale as a release; the seed is, and the seed is
432830
+ // what `contractVersion` was compared against.
432831
+ currentVersion: seedVersion,
432832
+ latestVersion: contractVersion,
432833
+ nodeId,
432834
+ wrapperKind: wrapper.kind
432835
+ };
432836
+ this.emitter.publishSnapshot("wrapper", [candidate], nodeId);
432837
+ }
432838
+ };
432839
+ exports.WrapperUpdateChecker = WrapperUpdateChecker;
432840
+ }
432841
+ });
432842
+
432033
432843
  // ../../server/backend/dist/manual-boot.js
432034
432844
  var require_manual_boot = __commonJS({
432035
432845
  "../../server/backend/dist/manual-boot.js"(exports) {
@@ -432109,9 +432919,11 @@ var require_manual_boot = __commonJS({
432109
432919
  var storage_service_1 = require_storage_service();
432110
432920
  var stream_probe_service_1 = require_stream_probe_service();
432111
432921
  var topology_emitter_service_1 = require_topology_emitter_service();
432922
+ var update_availability_emitter_js_1 = require_update_availability_emitter();
432112
432923
  var update_availability_store_js_1 = require_update_availability_store();
432113
432924
  var agent_installed_packages_js_1 = require_agent_installed_packages();
432114
432925
  var update_check_scheduler_js_1 = require_update_check_scheduler();
432926
+ var wrapper_update_checker_js_1 = require_wrapper_update_checker();
432115
432927
  var ServiceContainer = class {
432116
432928
  services = /* @__PURE__ */ new Map();
432117
432929
  register(ctor, instance) {
@@ -432218,6 +433030,20 @@ var require_manual_boot = __commonJS({
432218
433030
  const topologyEmitterService = new topology_emitter_service_1.TopologyEmitterService(eventBusService, agentRegistryService, addonRegistryService, (0, cap_providers_1.createNodeRootPackageLookup)(moleculerService, serverUpdateService));
432219
433031
  const postBootService = new post_boot_service_1.PostBootService(addonRegistryService, eventBusService, loggingService);
432220
433032
  const updateCheckLogger = loggingService.createLogger("UpdateCheck");
433033
+ const wrapperUpdateChecker = new wrapper_update_checker_js_1.WrapperUpdateChecker({
433034
+ logger: updateCheckLogger,
433035
+ emitter: new update_availability_emitter_js_1.UpdateAvailabilityEmitter(eventBusService, { type: "core", id: "wrapper-update-check" }, new update_availability_store_js_1.FileUpdateAvailabilityStore(availabilityDataDir, "wrapper-update", loggingService.createLogger("UpdateAvailability"))),
433036
+ readStatus: async (nodeId, isHub) => {
433037
+ if (isHub)
433038
+ return serverUpdateService.getServerPackageStatus();
433039
+ const proxy = moleculerService.createCapabilityProxy("server-management", nodeId);
433040
+ if (proxy === null) {
433041
+ throw new Error(`server-management unreachable on ${nodeId}`);
433042
+ }
433043
+ const status = await proxy["getServerPackageStatus"]?.({});
433044
+ return types_1.ServerPackageStatusSchema.parse(status);
433045
+ }
433046
+ });
432221
433047
  const updateCheckScheduler = new update_check_scheduler_js_1.UpdateCheckScheduler({
432222
433048
  logger: updateCheckLogger,
432223
433049
  getIntervalSeconds: () => addonPackageService.getAutoUpdateSettings().updateCheckIntervalSeconds,
@@ -432241,7 +433067,8 @@ var require_manual_boot = __commonJS({
432241
433067
  return void 0;
432242
433068
  }
432243
433069
  return proxy["checkServerUpdate"]?.({});
432244
- }
433070
+ },
433071
+ checkWrapperUpdate: (nodeId, isHub) => wrapperUpdateChecker.check(nodeId, isHub)
432245
433072
  }
432246
433073
  });
432247
433074
  addonPackageService.setUpdateCheckRescheduler(() => updateCheckScheduler.reschedule());
@@ -432448,6 +433275,7 @@ var require_main4 = __commonJS({
432448
433275
  var trpc_error_principal_1 = require_trpc_error_principal();
432449
433276
  var trpc_error_device_tags_1 = require_trpc_error_device_tags();
432450
433277
  var ws_request_census_1 = require_ws_request_census();
433278
+ var ws_slow_consumer_guard_js_1 = require_ws_slow_consumer_guard();
432451
433279
  var addon_route_jwt_gate_js_1 = require_addon_route_jwt_gate();
432452
433280
  var addon_route_share_gate_js_1 = require_addon_route_share_gate();
432453
433281
  var session_cookie_js_1 = require_session_cookie();
@@ -432572,7 +433400,17 @@ var require_main4 = __commonJS({
432572
433400
  // per peer and per frame kind. Same lazy registry, same reason.
432573
433401
  socketPlane: (0, system_1.createSocketPlaneReader)({
432574
433402
  registry: () => moleculerForEventPlane?.childRegistry ?? null
432575
- })
433403
+ }),
433404
+ // The ArrayBuffer census, armed by the probe itself. Twice on 2026-09-10
433405
+ // this process's `arrayBuffers` climbed linearly for an hour (to 1.3 GB
433406
+ // and to 5 GB, both LIVE across a forced compaction) and released in one
433407
+ // instant, with no line at either boundary; the measurement that names
433408
+ // the shape existed and was never run because it needed a human awake
433409
+ // during the episode. Now the heartbeat takes it: one forced full GC —
433410
+ // the pause a reclaim pass already costs — walked in memory, nothing
433411
+ // written, on a threshold held for a minute, at most twice an hour.
433412
+ // It is NOT a heap snapshot; see `array-buffer-census.ts`.
433413
+ arrayBufferCensus: { census: (0, system_1.createInspectorArrayBufferCensus)() }
432576
433414
  });
432577
433415
  cleanupOrphanProcesses();
432578
433416
  let spaIndexHtml = null;
@@ -433172,15 +434010,31 @@ var require_main4 = __commonJS({
433172
434010
  concurrencyLimit: 10
433173
434011
  }
433174
434012
  });
434013
+ const wsSlowConsumerGuard = new ws_slow_consumer_guard_js_1.WsSlowConsumerGuard({
434014
+ logger: app.get(logging_service_1.LoggingService).createLogger("tRPC:ws")
434015
+ });
434016
+ wsSlowConsumerGuard.start();
433175
434017
  wss.on("connection", (client, req) => {
433176
434018
  (0, ws_request_census_1.attachWsCensusSession)(httpRequestCensus, client, req, app.get(logging_service_1.LoggingService).createLogger("tRPC:ws"));
434019
+ wsSlowConsumerGuard.attach(client, (0, ws_slow_consumer_guard_js_1.wsConnectionFacts)(req));
433177
434020
  });
433178
434021
  (0, ws_1.applyWSSHandler)({
433179
434022
  wss,
433180
434023
  router: appRouter,
434024
+ // The complement of the bound above, for the SUSPENDED client: a PING is
434025
+ // a text frame the client's JS must answer, and a backgrounded app's JS
434026
+ // does not run — so the socket is terminated ~40 s into the background
434027
+ // instead of accumulating for the night, and the app reconnects on
434028
+ // return (every WS client of this hub is `@trpc/client` >= 11, which
434029
+ // answers PING with PONG; the viewer pins ^11.16). The bound stays: it
434030
+ // covers a client whose JS runs but whose link cannot drain the events.
434031
+ // A termination here closes with 1006; the guard's close line names it.
434032
+ keepAlive: { enabled: true, pingMs: 3e4, pongWaitMs: 1e4 },
433181
434033
  createContext: async (opts) => {
433182
434034
  const wsCtx = await (0, trpc_context_1.createWsTrpcContext)(opts, authService, addonRegistry, shareTokenService, app.get(logging_service_1.LoggingService).createLogger("tRPC:ws"));
433183
- (0, ws_request_census_1.identifyWsCensusSession)(opts.res, (0, trpc_error_principal_1.describeTrpcPrincipal)(wsCtx.user));
434035
+ const principal = (0, trpc_error_principal_1.describeTrpcPrincipal)(wsCtx.user);
434036
+ (0, ws_request_census_1.identifyWsCensusSession)(opts.res, principal);
434037
+ wsSlowConsumerGuard.identify(opts.res, principal);
433184
434038
  return wsCtx;
433185
434039
  },
433186
434040
  onError: ({ path: trpcPath, error, ctx, input }) => {