@camstack/system 1.2.272 → 1.2.274

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/camera-grid/addon.d.ts +13 -0
  12. package/dist/builtins/camera-grid/grid-canvas-seed.d.ts +62 -0
  13. package/dist/builtins/camera-grid/grid-canvas.d.ts +4 -2
  14. package/dist/builtins/camera-grid/grid-child.d.ts +10 -0
  15. package/dist/builtins/camera-grid/grid-compositor.d.ts +10 -0
  16. package/dist/builtins/camera-grid/index.js +321 -10
  17. package/dist/builtins/camera-grid/index.mjs +321 -10
  18. package/dist/builtins/console-logging/index.js +1 -1
  19. package/dist/builtins/console-logging/index.mjs +1 -1
  20. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  21. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  22. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  23. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  24. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  25. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  26. package/dist/builtins/hub-forwarder/index.js +1 -1
  27. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  28. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  30. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  31. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  32. package/dist/builtins/local-network/local-network.addon.js +1 -1
  33. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  34. package/dist/builtins/loki-logging/index.js +1 -1
  35. package/dist/builtins/loki-logging/index.mjs +1 -1
  36. package/dist/builtins/native-metrics/cgroup-pids.d.ts +112 -0
  37. package/dist/builtins/native-metrics/native-metrics.addon.d.ts +1 -0
  38. package/dist/builtins/native-metrics/native-metrics.addon.js +209 -1
  39. package/dist/builtins/native-metrics/native-metrics.addon.mjs +209 -1
  40. package/dist/builtins/platform-probe/index.js +1 -1
  41. package/dist/builtins/platform-probe/index.mjs +1 -1
  42. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  43. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  44. package/dist/builtins/snapshot/index.js +1 -1
  45. package/dist/builtins/snapshot/index.mjs +1 -1
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  47. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  48. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  49. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  50. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  51. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  52. package/dist/builtins/system-config/system-config.addon.js +1 -1
  53. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  54. package/dist/builtins/winston-logging/index.js +1 -1
  55. package/dist/builtins/winston-logging/index.mjs +1 -1
  56. package/dist/{dist-_YWIPtUH.js → dist-C_4Xt0Ya.js} +209 -11
  57. package/dist/{dist-Diz59Dhi.mjs → dist-DchKwh_m.mjs} +209 -11
  58. package/dist/index.js +12 -5
  59. package/dist/index.mjs +12 -5
  60. package/dist/kernel/moleculer/process-service.d.ts +54 -0
  61. package/dist/{retired-settings-keys-C2ocoH_l.js → retired-settings-keys-6wZNp7lr.js} +1 -1
  62. package/dist/{retired-settings-keys-C_Hg5d37.mjs → retired-settings-keys-DPSHUHMu.mjs} +1 -1
  63. package/package.json +1 -1
@@ -691,7 +691,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
691
691
  return EventCategory;
692
692
  }({});
693
693
  //#endregion
694
- //#region ../types/dist/sleep-GU_us3DG.mjs
694
+ //#region ../types/dist/sleep-CiR4z7r9.mjs
695
695
  /**
696
696
  * The audio chunk plane's byte format, and the ONE expansion from a coded
697
697
  * window to float samples (D455).
@@ -1857,6 +1857,17 @@ var CamProfileSchema = z.enum([
1857
1857
  "mid",
1858
1858
  "low"
1859
1859
  ]);
1860
+ /**
1861
+ * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
1862
+ *
1863
+ * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
1864
+ * that consumer owns". It lives beside `CamProfile` because a rung is the only
1865
+ * operator-facing way to name a stream: a cam-stream id is broker-internal and
1866
+ * moves when a slot is reassigned, so a stored id silently stops meaning what
1867
+ * the operator picked. Today's one user is the audio analyser, whose rule is
1868
+ * "the lowest AVAILABLE native rung" (D537).
1869
+ */
1870
+ var AudioStreamPickSchema = z.union([CamProfileSchema, z.literal("auto")]);
1860
1871
  var CamStreamKindSchema = z.enum([
1861
1872
  "pull-rtsp",
1862
1873
  "pull-rtmp",
@@ -3404,7 +3415,7 @@ function sleep(ms) {
3404
3415
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
3405
3416
  }
3406
3417
  //#endregion
3407
- //#region ../types/dist/canonical-hash-CvL03d3i.mjs
3418
+ //#region ../types/dist/canonical-hash-CPK2Dy60.mjs
3408
3419
  var AUDIO_ENCODER_BY_CODEC = {
3409
3420
  opus: "libopus",
3410
3421
  aac: "aac",
@@ -3505,16 +3516,29 @@ function resolveStreamMaps(selection) {
3505
3516
  };
3506
3517
  }
3507
3518
  /** The `-vf` filter args, or `[]` when a consumer `-vf` already claims the slot. */
3508
- function buildVideoFilterArgs(scale, outputArgs, filterGraph) {
3519
+ function buildVideoFilterArgs(scale, outputArgs, filterGraph, hwUpload) {
3509
3520
  if (filterGraph) {
3510
3521
  if (scale) throw new Error("FfmpegInvocation: video.scale cannot be combined with a filter graph — scale inside the graph instead (the -vf would feed an output the graph already feeds)");
3511
3522
  if (outputArgs.some((a) => a === "-vf")) throw new Error("FfmpegInvocation: outputArgs carries a -vf while a filter graph feeds the same output — fold the filter into the graph");
3512
3523
  return [];
3513
3524
  }
3514
- if (!scale) return [];
3515
3525
  if (outputArgs.some((a) => a === "-vf")) return [];
3516
- if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}`];
3517
- return ["-vf", `scale='min(${scale.width},iw)':'min(${scale.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2`];
3526
+ if (!scale) return hwUpload.length > 0 ? ["-vf", hwUpload] : [];
3527
+ const suffix = hwUpload.length > 0 ? `,${hwUpload}` : "";
3528
+ if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}${suffix}`];
3529
+ return ["-vf", `scale='min(${scale.width},iw)':'min(${scale.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2${suffix}`];
3530
+ }
3531
+ /**
3532
+ * The upload filter a hardware encode needs, or `''`.
3533
+ *
3534
+ * Nothing is uploaded when the DECODE already left the frames on the GPU
3535
+ * (`-hwaccel vaapi`): they are surfaces already, and uploading a surface is an
3536
+ * error rather than a no-op.
3537
+ */
3538
+ function resolveHwUpload(device, decodeHwAccel) {
3539
+ if (device === void 0) return "";
3540
+ if (!isSoftwareDecode(decodeHwAccel)) return "";
3541
+ return "format=nv12,hwupload";
3518
3542
  }
3519
3543
  /** Rate-control args for an encode plan. */
3520
3544
  function buildRateControlArgs(video) {
@@ -3534,7 +3558,7 @@ function buildRateControlArgs(video) {
3534
3558
  ];
3535
3559
  }
3536
3560
  /** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
3537
- function buildVideoArgs(video, outputArgs, filterGraph) {
3561
+ function buildVideoArgs(video, outputArgs, filterGraph, hwEncode) {
3538
3562
  if (video.kind === "raw") {
3539
3563
  if (filterGraph && video.filter !== void 0) throw new Error("FfmpegInvocation: video.filter cannot be combined with a filter graph — fold the filter into the graph");
3540
3564
  return [
@@ -3554,8 +3578,10 @@ function buildVideoArgs(video, outputArgs, filterGraph) {
3554
3578
  ...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
3555
3579
  ];
3556
3580
  }
3581
+ const hwUpload = hwEncode === void 0 ? "" : resolveHwUpload(hwEncode.device, hwEncode.decodeHwAccel);
3582
+ if (hwEncode !== void 0 && filterGraph) throw new Error("FfmpegInvocation: a hardware encode fed by a filter graph must upload INSIDE the graph — hwEncodeDevice cannot be combined with filterGraph");
3557
3583
  const args = [
3558
- ...buildVideoFilterArgs(video.scale, outputArgs, filterGraph),
3584
+ ...buildVideoFilterArgs(video.scale, outputArgs, filterGraph, hwUpload),
3559
3585
  "-c:v",
3560
3586
  video.encoder
3561
3587
  ];
@@ -3725,10 +3751,18 @@ function buildAudioSidecarArgs(sidecar) {
3725
3751
  * (`0:a:0?`): the sidecar is defined as the SOURCE's microphone on its own RTP
3726
3752
  * output, not as whatever the main output happens to carry.
3727
3753
  */
3754
+ /** The hardware-encode context for this invocation, or `undefined`. */
3755
+ function hwEncodeFor(inv) {
3756
+ return inv.hwEncodeDevice === void 0 ? void 0 : {
3757
+ device: inv.hwEncodeDevice,
3758
+ decodeHwAccel: inv.decodeHwAccel
3759
+ };
3760
+ }
3728
3761
  function buildFfmpegArgs(inv) {
3729
3762
  const graph = inv.filterGraph ?? null;
3730
3763
  const head = [
3731
3764
  ...logBannerArgs(inv.logLevel),
3765
+ ...inv.hwEncodeDevice !== void 0 ? ["-vaapi_device", inv.hwEncodeDevice.renderNode] : [],
3732
3766
  ...buildInputArgs(inv.input, inv.decodeHwAccel),
3733
3767
  ...(inv.extraInputs ?? []).flatMap((extra) => buildInputArgs(extra, inv.decodeHwAccel)),
3734
3768
  ...graph ? ["-filter_complex", graph.graph] : []
@@ -3742,7 +3776,7 @@ function buildFfmpegArgs(inv) {
3742
3776
  "-an",
3743
3777
  "-map",
3744
3778
  maps.video ?? "0:v:0",
3745
- ...buildVideoArgs(inv.video, inv.outputArgs, graph),
3779
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph, hwEncodeFor(inv)),
3746
3780
  ...threadArgs,
3747
3781
  ...inv.outputArgs,
3748
3782
  ...buildRtpOutputArgs(inv.sink.video)
@@ -3765,7 +3799,7 @@ function buildFfmpegArgs(inv) {
3765
3799
  return [
3766
3800
  ...head,
3767
3801
  ...videoMapArgs,
3768
- ...buildVideoArgs(inv.video, inv.outputArgs, graph),
3802
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph, hwEncodeFor(inv)),
3769
3803
  ...threadArgs,
3770
3804
  ...mutedAudio ? [] : audioMapArgs,
3771
3805
  ...audioArgs,
@@ -4205,6 +4239,7 @@ var CameraSwitchIdSchema = z.enum([
4205
4239
  "device-audio",
4206
4240
  "broker-audio",
4207
4241
  "audio-analysis",
4242
+ "event-saving",
4208
4243
  "recording",
4209
4244
  "notifications"
4210
4245
  ]);
@@ -4231,6 +4266,7 @@ var CAMERA_SWITCH_ORDER = [
4231
4266
  "device-audio",
4232
4267
  "broker-audio",
4233
4268
  "audio-analysis",
4269
+ "event-saving",
4234
4270
  "recording",
4235
4271
  "notifications"
4236
4272
  ];
@@ -4814,11 +4850,31 @@ var RecordingBandTriggersSchema = RecordingTriggersSchema;
4814
4850
  * band engine's `applies` rule). `start`/`end` are `HH:MM`; an `end <= start`
4815
4851
  * span wraps past midnight (handled by the band engine).
4816
4852
  */
4853
+ /**
4854
+ * Whether a band's footage carries sound.
4855
+ *
4856
+ * The recorder has always attached with `audio: 'aac'`, so every band of every
4857
+ * camera recorded sound whether anyone wanted it or not. The broker already
4858
+ * serves the other answer — `audio: 'none'` takes the video-only `/muted`
4859
+ * restream — so this was a choice the product could always have offered.
4860
+ *
4861
+ * It belongs on the BAND rather than the camera because one camera can want
4862
+ * both: silent across the shop floor during opening hours, sound overnight.
4863
+ * And it applies to BOTH modes — an events band firing on motion has the same
4864
+ * question to answer as a continuous one.
4865
+ */
4866
+ var RecordingBandAudioSchema = z.enum(["on", "muted"]);
4817
4867
  var RecordingBandSchema = z.object({
4818
4868
  days: z.array(RecordingWeekdaySchema),
4819
4869
  start: z.string().regex(HHMM),
4820
4870
  end: z.string().regex(HHMM),
4821
4871
  mode: RecordingBandModeSchema,
4872
+ /**
4873
+ * Sound, or silence. OPTIONAL and absent-means-`on`: every recording
4874
+ * written before this existed carried sound, and a band whose key is
4875
+ * missing must keep meaning what it meant. See {@link RecordingBandAudioSchema}.
4876
+ */
4877
+ audio: RecordingBandAudioSchema.optional(),
4822
4878
  triggers: RecordingBandTriggersSchema.optional(),
4823
4879
  /**
4824
4880
  * RETIRED (D381). A band no longer carries a pre-buffer of its own.
@@ -19996,6 +20052,7 @@ var MediaFileKindEnum = z.enum([
19996
20052
  "keyFrameSmall",
19997
20053
  "thumbnailSmall",
19998
20054
  "confirmationCrop",
20055
+ "debugCrop",
19999
20056
  "sceneFrame"
20000
20057
  ]);
20001
20058
  /**
@@ -22307,6 +22364,18 @@ var RunnerCameraConfigSchema = z.object({
22307
22364
  motionStreamId: z.string(),
22308
22365
  detectionStreamId: z.string(),
22309
22366
  /**
22367
+ * Which rung this camera's audio analysis listens on — `auto` (the lowest
22368
+ * AVAILABLE native rung) or an explicit one.
22369
+ *
22370
+ * Not consumed by the runner: carried here so it travels with the camera's
22371
+ * config the way `audioMotionWindowMs` does, and so the orchestrator's
22372
+ * watchdog resolves the same stream the audio subscription did. OPTIONAL
22373
+ * with no default — absent means `auto`, which is what every camera written
22374
+ * before this field had, and an absent optional adds nothing to the
22375
+ * generated cap-input defaults (D537).
22376
+ */
22377
+ audioStreamPick: AudioStreamPickSchema.optional(),
22378
+ /**
22310
22379
  * Motion sources that can activate the pipeline. At least one must
22311
22380
  * be present. Multiple sources OR-ed: any one firing arms the
22312
22381
  * cooldown timer and transitions watching → active. The same
@@ -25941,6 +26010,104 @@ var WebrtcStreamChoiceSchema = z.object({
25941
26010
  bitrateKbps: z.number().nullable()
25942
26011
  });
25943
26012
  /**
26013
+ * The last move the adaptive ladder made on this session, and the numbers it
26014
+ * rested on. Mirrors `LadderDecision` in the stream-broker's
26015
+ * `adaptive-controller.ts` — the log line's evidence, made readable.
26016
+ *
26017
+ * Nullability is load-bearing: `lossRatio: null` means the loss tracker had
26018
+ * no window to report, which is NOT a link with zero loss.
26019
+ */
26020
+ var WebrtcLadderDecisionSchema = z.object({
26021
+ /** `loss` — RTCP / client-reported packet loss; `stall` — the picture
26022
+ * stopped moving; `healthy` — a promotion. */
26023
+ cause: z.enum([
26024
+ "loss",
26025
+ "stall",
26026
+ "healthy"
26027
+ ]),
26028
+ /** The rung the ladder moved TO. Null for a move that changed no rung
26029
+ * (an MTU reduction, a forced transcode-down at the bottom). */
26030
+ tier: CamProfileSchema.nullable(),
26031
+ /** Whether that move forced the egress transcode-down rung. */
26032
+ forcedTranscodeDown: z.boolean(),
26033
+ /** Loss ratio over the tracker's window at the moment of the decision. */
26034
+ lossRatio: z.number().nullable(),
26035
+ /** The client's reported downlink at that moment, in kbps. */
26036
+ clientKbps: z.number().nullable(),
26037
+ /** How long the stall detector had been seeing nothing arrive. */
26038
+ stalledForMs: z.number().nullable(),
26039
+ /** Whether the broker WAS sending while that was measured — the half that
26040
+ * separates "the camera stopped" from "the link stopped". */
26041
+ serverSending: z.boolean().nullable(),
26042
+ /** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
26043
+ * must not read like one from this second. */
26044
+ at: z.number()
26045
+ });
26046
+ /** Per-tier count of MEASURED downgrades this viewer session has suffered. */
26047
+ var WebrtcTierFailureSchema = z.object({
26048
+ tier: CamProfileSchema,
26049
+ failures: z.number().int().nonnegative()
26050
+ });
26051
+ /**
26052
+ * What the SERVER decided for one WebRTC session — the half the client cannot
26053
+ * measure. See `getSessionDebug`.
26054
+ */
26055
+ var WebrtcSessionDebugSchema = z.object({
26056
+ sessionId: z.string(),
26057
+ /** Whether the session still exists here. Everything below is null when
26058
+ * it does not — a dead session has no measurements, not zero ones. */
26059
+ alive: z.boolean(),
26060
+ /** Epoch ms this snapshot was taken, so the overlay can show its age. */
26061
+ observedAt: z.number(),
26062
+ /** The broker actually feeding this session (`<deviceId>/<camStreamId>`). */
26063
+ brokerId: z.string().nullable(),
26064
+ camStreamId: z.string().nullable(),
26065
+ /** `derived` = this hub BUILT the rung being served (a transcode/scale of
26066
+ * another stream); `native` = the camera's own encode. */
26067
+ sourceKind: z.enum([
26068
+ "native",
26069
+ "derived",
26070
+ "unknown"
26071
+ ]),
26072
+ /** Whether the ladder is running at all (an `adaptive` target). */
26073
+ adaptive: z.boolean(),
26074
+ /** The rung `auto` ACTUALLY resolved to right now. Null when the broker
26075
+ * is bound to no profile slot (a manually-activated stream). */
26076
+ servedTier: CamProfileSchema.nullable(),
26077
+ /** Which node ingests the source. Null when unresolved. */
26078
+ ingestNodeId: z.string().nullable(),
26079
+ /** Whether that node is a remote one (the media crosses a node boundary
26080
+ * before it reaches this session). Null when unresolved. */
26081
+ ingestIsRemote: z.boolean().nullable(),
26082
+ /** Codec off the source, and the one negotiated with this client. */
26083
+ sourceCodec: z.string().nullable(),
26084
+ negotiatedCodec: z.string().nullable(),
26085
+ /** Whether the EGRESS leg re-encodes for this client or forwards the
26086
+ * source bytes untouched. */
26087
+ egressLeg: z.enum([
26088
+ "copy",
26089
+ "transcode",
26090
+ "unknown"
26091
+ ]),
26092
+ /** What the broker itself reports about the rung it is serving. */
26093
+ brokerStatus: z.string().nullable(),
26094
+ brokerCodec: z.string().nullable(),
26095
+ brokerBitrateKbps: z.number().nullable(),
26096
+ brokerInputFps: z.number().nullable(),
26097
+ brokerUptimeMs: z.number().nullable(),
26098
+ /** RTP packets this session has sent — the server's own "am I sending?". */
26099
+ rtpPacketsSent: z.number().nullable(),
26100
+ /** D42: the track may be carrying the recorded feeder, not live. The ladder
26101
+ * is suspended there, so a frozen picture in `recorded` mode means
26102
+ * something entirely different. */
26103
+ trackMode: z.enum(["live", "recorded"]).nullable(),
26104
+ lastDecision: WebrtcLadderDecisionSchema.nullable(),
26105
+ tierFailures: z.array(WebrtcTierFailureSchema),
26106
+ /** Successful promotions in this viewer session (drives the progressive
26107
+ * healthy timer). Null when no ladder is running. */
26108
+ upgradeCount: z.number().nullable()
26109
+ });
26110
+ /**
25944
26111
  * WebRTC session — device-scoped facade over the system `webrtc` cap.
25945
26112
  *
25946
26113
  * Exposes per-device signaling (`createSession`, `handleAnswer`,
@@ -26180,7 +26347,27 @@ var webrtcSessionCapability = {
26180
26347
  * the same answer as "it ended": either way nothing is holding it up.
26181
26348
  */
26182
26349
  alive: z.boolean()
26183
- }))
26350
+ })),
26351
+ /**
26352
+ * The SERVER's half of the stream-debug overlay, for one live session.
26353
+ *
26354
+ * The client can measure its own transport (`getStats()`); it cannot see
26355
+ * which rung `auto` actually resolved to, why the ladder last moved, or
26356
+ * whether the rung it is being served is the camera's own encode or
26357
+ * something this hub built. That asymmetry is why "quality is stuck on
26358
+ * low" reports have historically been unanswerable from the viewer alone.
26359
+ *
26360
+ * EVERY measurement here is nullable and `null` means UNKNOWN, never zero
26361
+ * (D393): a session whose ladder has never moved reports
26362
+ * `lastDecision: null`, which is a different fact from a ladder that moved
26363
+ * for `healthy` with a 0 loss ratio.
26364
+ *
26365
+ * Read-only, ~1 Hz, polled by the overlay only while it is visible.
26366
+ */
26367
+ getSessionDebug: method(z.object({
26368
+ deviceId: z.number().int().nonnegative(),
26369
+ sessionId: z.string()
26370
+ }), WebrtcSessionDebugSchema, { kind: "query" })
26184
26371
  },
26185
26372
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
26186
26373
  mount: { kind: "server-provided" }
@@ -45782,6 +45969,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
45782
45969
  addonId: null,
45783
45970
  access: "view"
45784
45971
  },
45972
+ "webrtcSession.getSessionDebug": {
45973
+ capName: "webrtc-session",
45974
+ capScope: "device",
45975
+ addonId: null,
45976
+ access: "view"
45977
+ },
45785
45978
  "webrtcSession.getSessionState": {
45786
45979
  capName: "webrtc-session",
45787
45980
  capScope: "device",
@@ -47765,6 +47958,11 @@ Object.freeze({
47765
47958
  form: "single",
47766
47959
  optional: false
47767
47960
  }],
47961
+ "webrtcSession.getSessionDebug": [{
47962
+ name: "deviceId",
47963
+ form: "single",
47964
+ optional: false
47965
+ }],
47768
47966
  "webrtcSession.getSessionState": [{
47769
47967
  name: "deviceId",
47770
47968
  form: "single",
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-_YWIPtUH.js");
3
+ const require_dist = require("./dist-C_4Xt0Ya.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
@@ -94942,14 +94942,19 @@ function runnerRssBudgetMb(addons) {
94942
94942
  return declared.length === 0 ? void 0 : Math.max(...declared);
94943
94943
  }
94944
94944
  /**
94945
- * Per-placement V8 heap flags (Block D). LIGHT runners cap their young generation
94946
- * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win — and
94947
- * keep an opt-in old-space cap (`CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`, unset = uncapped). HEAVY
94945
+ * Per-placement V8 flags (Block D). LIGHT runners cap their young generation
94946
+ * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win — bound
94947
+ * V8's platform thread pool (`--v8-pool-size`, see {@link LIGHT_V8_POOL_SIZE_DEFAULT}: a measured
94948
+ * ~58 threads across the hub's 29 light runners, against a cgroup `pids.max` this host has already
94949
+ * hit) and keep an opt-in old-space cap (`CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`, unset = uncapped). HEAVY
94948
94950
  * runners keep V8's default young generation but ALWAYS carry an old-space ceiling; see
94949
94951
  * {@link HEAVY_MAX_OLD_MB_DEFAULT} for why, and how a runner declares its own.
94950
94952
  *
94951
94953
  * Kill-switch: `CAMSTACK_RUNNER_HEAP_TUNING=off` disables all flags (recover without a rebuild).
94952
- * Overrides: `CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB` (default 2), `CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`
94954
+ * Overrides: `CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB` (default 2),
94955
+ * `CAMSTACK_RUNNER_LIGHT_V8_POOL_SIZE` (default {@link LIGHT_V8_POOL_SIZE_DEFAULT}; below 1 omits
94956
+ * the flag — `0` means "size from the CPU count" to V8, not "off"),
94957
+ * `CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`
94953
94958
  * (default unset = no old-space cap), `CAMSTACK_RUNNER_HEAVY_MAX_OLD_MB`
94954
94959
  * (default {@link HEAVY_MAX_OLD_MB_DEFAULT}; `0` restores the old uncapped behaviour).
94955
94960
  */
@@ -94960,6 +94965,8 @@ function runnerHeapFlags(addons) {
94960
94965
  if (!heavy) {
94961
94966
  const semiMb = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB", 2);
94962
94967
  if (semiMb > 0) flags.push(`--max-semi-space-size=${semiMb}`);
94968
+ const v8Pool = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_V8_POOL_SIZE", 2);
94969
+ if (v8Pool > 0) flags.push(`--v8-pool-size=${v8Pool}`);
94963
94970
  }
94964
94971
  const oldMb = runnerMaxOldSpaceMb(addons, heavy);
94965
94972
  if (oldMb > 0) flags.push(`--max-old-space-size=${oldMb}`);
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { An as readinessKey, Cn as expandCapMethods, F as addonSettingsCapability, Ft as procedureAuthKey, Mn as scopeKey, Mt as objectInputDeclaresAddonId, On as parseJsonObject, Pn as EventCategory$1, S as RUNTIME_DEFAULTS, Tt as looseSchema, Ut as scopesAllowAddon, Vt as resolveMethodAuth, Wt as scopesAllowDeviceCap, _n as asJsonObject$1, _t as isVoidInput, an as errMsg$1, bn as createEvent, bt as lifecycleJobSchema, cn as DATAPLANE_SECRET_HEADER$1, dt as isArrayOutputSchema, ft as isCollectionArrayMethod, g as LOG_LEVEL_RANK, hn as ReadinessTimeoutError, jn as resolveCapMount, kn as parseJsonUnknown, ln as DEVICE_SETTINGS_CONTRIBUTION_METHODS, mn as ReadinessRegistry, mt as isObjectInput, st as extractNestedAddonId, t as ALL_CAPABILITY_DEFINITIONS, un as DEVICE_STATUS_METHOD, vn as asNumber, vt as kebabToCamel, wt as logLevelAtMost, xn as emitDownForOwnedCaps, y as METHOD_ACCESS_MAP, yn as asString$1 } from "./dist-Diz59Dhi.mjs";
2
+ import { An as readinessKey, Cn as expandCapMethods, F as addonSettingsCapability, Ft as procedureAuthKey, Mn as scopeKey, Mt as objectInputDeclaresAddonId, On as parseJsonObject, Pn as EventCategory$1, S as RUNTIME_DEFAULTS, Tt as looseSchema, Ut as scopesAllowAddon, Vt as resolveMethodAuth, Wt as scopesAllowDeviceCap, _n as asJsonObject$1, _t as isVoidInput, an as errMsg$1, bn as createEvent, bt as lifecycleJobSchema, cn as DATAPLANE_SECRET_HEADER$1, dt as isArrayOutputSchema, ft as isCollectionArrayMethod, g as LOG_LEVEL_RANK, hn as ReadinessTimeoutError, jn as resolveCapMount, kn as parseJsonUnknown, ln as DEVICE_SETTINGS_CONTRIBUTION_METHODS, mn as ReadinessRegistry, mt as isObjectInput, st as extractNestedAddonId, t as ALL_CAPABILITY_DEFINITIONS, un as DEVICE_STATUS_METHOD, vn as asNumber, vt as kebabToCamel, wt as logLevelAtMost, xn as emitDownForOwnedCaps, y as METHOD_ACCESS_MAP, yn as asString$1 } from "./dist-DchKwh_m.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
@@ -94935,14 +94935,19 @@ function runnerRssBudgetMb(addons) {
94935
94935
  return declared.length === 0 ? void 0 : Math.max(...declared);
94936
94936
  }
94937
94937
  /**
94938
- * Per-placement V8 heap flags (Block D). LIGHT runners cap their young generation
94939
- * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win — and
94940
- * keep an opt-in old-space cap (`CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`, unset = uncapped). HEAVY
94938
+ * Per-placement V8 flags (Block D). LIGHT runners cap their young generation
94939
+ * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win — bound
94940
+ * V8's platform thread pool (`--v8-pool-size`, see {@link LIGHT_V8_POOL_SIZE_DEFAULT}: a measured
94941
+ * ~58 threads across the hub's 29 light runners, against a cgroup `pids.max` this host has already
94942
+ * hit) and keep an opt-in old-space cap (`CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`, unset = uncapped). HEAVY
94941
94943
  * runners keep V8's default young generation but ALWAYS carry an old-space ceiling; see
94942
94944
  * {@link HEAVY_MAX_OLD_MB_DEFAULT} for why, and how a runner declares its own.
94943
94945
  *
94944
94946
  * Kill-switch: `CAMSTACK_RUNNER_HEAP_TUNING=off` disables all flags (recover without a rebuild).
94945
- * Overrides: `CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB` (default 2), `CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`
94947
+ * Overrides: `CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB` (default 2),
94948
+ * `CAMSTACK_RUNNER_LIGHT_V8_POOL_SIZE` (default {@link LIGHT_V8_POOL_SIZE_DEFAULT}; below 1 omits
94949
+ * the flag — `0` means "size from the CPU count" to V8, not "off"),
94950
+ * `CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`
94946
94951
  * (default unset = no old-space cap), `CAMSTACK_RUNNER_HEAVY_MAX_OLD_MB`
94947
94952
  * (default {@link HEAVY_MAX_OLD_MB_DEFAULT}; `0` restores the old uncapped behaviour).
94948
94953
  */
@@ -94953,6 +94958,8 @@ function runnerHeapFlags(addons) {
94953
94958
  if (!heavy) {
94954
94959
  const semiMb = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB", 2);
94955
94960
  if (semiMb > 0) flags.push(`--max-semi-space-size=${semiMb}`);
94961
+ const v8Pool = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_V8_POOL_SIZE", 2);
94962
+ if (v8Pool > 0) flags.push(`--v8-pool-size=${v8Pool}`);
94956
94963
  }
94957
94964
  const oldMb = runnerMaxOldSpaceMb(addons, heavy);
94958
94965
  if (oldMb > 0) flags.push(`--max-old-space-size=${oldMb}`);
@@ -59,6 +59,60 @@ interface RunnerAddonSpec {
59
59
  * Reaching for a bigger number here when RSS is the complaint is the standard wrong move.
60
60
  */
61
61
  export declare const HEAVY_MAX_OLD_MB_DEFAULT = 1024;
62
+ /**
63
+ * V8 platform worker threads for a LIGHT runner (`--v8-pool-size`).
64
+ *
65
+ * ## What this bounds
66
+ *
67
+ * Node gives every process a V8 platform thread pool — background compilation,
68
+ * parallel marking and sweeping — defaulted to 4. It is not the libuv pool
69
+ * (`UV_THREADPOOL_SIZE`, which serves `fs`/`dns`/`zlib`/`crypto`) and it is
70
+ * deliberately not touched here; see below.
71
+ *
72
+ * ## Why it is worth a flag
73
+ *
74
+ * A container cgroup counts THREADS, not processes, and this cluster forks a
75
+ * runner per addon (D2). Measured on the live hub 2026-09-18 (20 cores,
76
+ * `pids.max=2048`), after 22 fork refusals in 45 minutes earlier that day:
77
+ *
78
+ * pids.current 991 · 47 node processes holding 469 threads
79
+ * of those, 29 LIGHT runners holding 274 (mean 9.4 threads each)
80
+ *
81
+ * A light runner is a control-plane addon with a 2 MB young generation. It does
82
+ * not generate the background GC work four marking threads exist for, and it
83
+ * pays 4 threads of the cgroup's pid budget for them anyway. Two returns ~58
84
+ * threads — ~6% of the container's live thread count — for background GC that
85
+ * is, on these heaps, a few milliseconds a cycle.
86
+ *
87
+ * ## Why TWO and never ZERO
88
+ *
89
+ * `--v8-pool-size=0` does not disable the pool: it tells V8 to size it from the
90
+ * detected parallelism. Measured locally 2026-09-18 on a node process holding
91
+ * the libuv pool open:
92
+ *
93
+ * default 11 threads
94
+ * --v8-pool-size=2 9 threads
95
+ * --v8-pool-size=0 18 threads
96
+ *
97
+ * So a value below 1 omits the flag entirely rather than being passed through —
98
+ * the same trap as `MALLOC_ARENA_MAX=0` in `runner-native-allocator.ts`. To turn
99
+ * the tuning off, use `CAMSTACK_RUNNER_HEAP_TUNING=off`.
100
+ *
101
+ * ## What is deliberately NOT bounded
102
+ *
103
+ * HEAVY runners keep V8's default: they hold real heaps (the recorder declares
104
+ * 3072 MB) where parallel marking is the difference between a pause and a stall.
105
+ *
106
+ * `UV_THREADPOOL_SIZE` is left alone on every runner. Its 4 threads are lazily
107
+ * spawned, and the measurement above says most light runners have spawned all
108
+ * four — i.e. they really do run concurrent `fs` work. Halving that would trade
109
+ * ~58 more threads for serialized filesystem I/O in runners whose I/O profile
110
+ * has not been measured, on a media system. A thread saving is not worth a
111
+ * media gap.
112
+ *
113
+ * Override: `CAMSTACK_RUNNER_LIGHT_V8_POOL_SIZE`.
114
+ */
115
+ export declare const LIGHT_V8_POOL_SIZE_DEFAULT = 2;
62
116
  interface SpawnedProcess {
63
117
  readonly pid: number;
64
118
  readonly nodeId: string;
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-_YWIPtUH.js");
1
+ const require_dist = require("./dist-C_4Xt0Ya.js");
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -1,4 +1,4 @@
1
- import { _n as asJsonObject } from "./dist-Diz59Dhi.mjs";
1
+ import { _n as asJsonObject } from "./dist-DchKwh_m.mjs";
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.272",
3
+ "version": "1.2.274",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",