@camstack/system 1.2.230 → 1.2.232

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 (60) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.d.ts +1 -0
  4. package/dist/addon-utils.js +6 -1
  5. package/dist/addon-utils.mjs +3 -2
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/alerts/alerts.addon.js +1 -1
  11. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +17 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +17 -1
  14. package/dist/builtins/console-logging/index.js +1 -1
  15. package/dist/builtins/console-logging/index.mjs +1 -1
  16. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  17. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  18. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  19. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  22. package/dist/builtins/hub-forwarder/index.js +1 -1
  23. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  24. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  26. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  28. package/dist/builtins/local-network/local-network.addon.js +1 -1
  29. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  30. package/dist/builtins/loki-logging/index.js +1 -1
  31. package/dist/builtins/loki-logging/index.mjs +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  34. package/dist/builtins/platform-probe/index.js +1 -1
  35. package/dist/builtins/platform-probe/index.mjs +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  38. package/dist/builtins/snapshot/index.js +5 -140
  39. package/dist/builtins/snapshot/index.mjs +2 -137
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{custom-action-registry-YD6rwIuz.js → custom-action-registry-YHpAdksL.js} +1 -1
  51. package/dist/{custom-action-registry-zTQXMaNb.mjs → custom-action-registry-gaO1AlT2.mjs} +1 -1
  52. package/dist/{dist-C0M6CBhV.mjs → dist-BX6SpI0i.mjs} +235 -11
  53. package/dist/{dist-zr6q3Aad.js → dist-CEIkHEdy.js} +235 -11
  54. package/dist/index.js +2 -2
  55. package/dist/index.mjs +2 -2
  56. package/dist/{retired-settings-keys-DFwQTxAT.mjs → retired-settings-keys-DDV06eUr.mjs} +1 -1
  57. package/dist/{retired-settings-keys-BqKPfC0_.js → retired-settings-keys-FLZlwa2U.js} +1 -1
  58. package/dist/snapshot-courtesy-LoJNgHHZ.mjs +140 -0
  59. package/dist/snapshot-courtesy-oeTWCXPv.js +165 -0
  60. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  let zod = require("zod");
2
- //#region ../types/dist/event-category-ZyX6jcse.mjs
2
+ //#region ../types/dist/event-category-BVDXG4tB.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -616,6 +616,20 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
616
616
  * pull-reconcile from the provider's `getStatus` on reconnect. */
617
617
  EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
618
618
  EventCategory["BackupCompleted"] = "backup.completed";
619
+ /**
620
+ * A whole backup RUN finished — every destination attempted, win or lose.
621
+ *
622
+ * `backup.completed` fires once per DESTINATION, which is the right grain for
623
+ * a progress UI and the wrong one for a notification: an operator with three
624
+ * destinations would be told three times. And a run where two of three
625
+ * destinations succeeded is not a clean success — a single "backup
626
+ * completed" that hid the failed one would be a lie, so the count of each is
627
+ * carried here and the message says both.
628
+ *
629
+ * Emitted by the backup orchestrator only after the destination loop, so a
630
+ * run that dies during the BUILD phase produces no completion at all.
631
+ */
632
+ EventCategory["BackupRunCompleted"] = "backup.run-completed";
619
633
  EventCategory["BackupRestored"] = "backup.restored";
620
634
  EventCategory["NotificationDispatched"] = "notification.dispatched";
621
635
  EventCategory["NotificationFailed"] = "notification.failed";
@@ -676,7 +690,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
676
690
  return EventCategory;
677
691
  }({});
678
692
  //#endregion
679
- //#region ../types/dist/sleep-DBKu2-U5.mjs
693
+ //#region ../types/dist/sleep-DEuj7E3j.mjs
680
694
  /**
681
695
  * The audio chunk plane's byte format, and the ONE expansion from a coded
682
696
  * window to float samples (D455).
@@ -3467,7 +3481,23 @@ var VideoEncodeSchema = zod.z.object({
3467
3481
  "zerolatency",
3468
3482
  "film",
3469
3483
  "animation"
3470
- ]).optional()
3484
+ ]).optional(),
3485
+ /**
3486
+ * ONE slice per access unit.
3487
+ *
3488
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
3489
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
3490
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
3491
+ * frame-end per frame: the first pictures render and the video then freezes
3492
+ * for good while the audio, on its own plane, plays on. Measured on this hub
3493
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
3494
+ *
3495
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
3496
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
3497
+ * consumers meaning the same thing spelled differently would stop sharing one
3498
+ * child. Absent means "whatever the encoder does" — today's behaviour.
3499
+ */
3500
+ singleSlicePerFrame: zod.z.boolean().optional()
3471
3501
  });
3472
3502
  var AudioEncodeSchema = zod.z.union([zod.z.literal("passthrough"), zod.z.object({
3473
3503
  codec: zod.z.enum([
@@ -3520,7 +3550,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
3520
3550
  audio: "passthrough"
3521
3551
  };
3522
3552
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
3523
- ({ ...BASE_LIVE_EGRESS_PROFILE });
3553
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
3524
3554
  /**
3525
3555
  * Deep wiring healthcheck — snapshot of active reachability probes across
3526
3556
  * every declared capability + widget of every installed plugin, on every
@@ -8470,6 +8500,72 @@ var cameraPipelineConfigCapability = {
8470
8500
  exposesDeviceSettings: true,
8471
8501
  methods: {}
8472
8502
  };
8503
+ /**
8504
+ * The signals a device can emit to WAKE its own stream.
8505
+ *
8506
+ * A camera whose stream is built on demand sleeps until something asks for it,
8507
+ * and "something" cannot be a consumer that is merely attached — a Frigate-style
8508
+ * puller holds a session open for ever, and treating that as demand would keep
8509
+ * a battery camera awake for ever, which is the whole thing the battery is for
8510
+ * (D173). So the wake has to come from the CAMERA: an event it noticed by
8511
+ * itself, with no stream running.
8512
+ *
8513
+ * ## The vocabulary is the PROVIDER'S, not ours
8514
+ *
8515
+ * Like `consumables`, this cap declares no vocabulary of its own. A provider
8516
+ * names each signal with a `code` it chooses and a `label` an operator reads.
8517
+ * Reolink offers motion and camera-native detection; another provider may offer
8518
+ * a tamper, a doorbell press, a PIR, or something no camera in this fleet has
8519
+ * yet. A fixed enum here would mean every new signal is a framework release.
8520
+ *
8521
+ * It is deliberately NOT derived from the caps a device already binds. Whether
8522
+ * a camera CAN push firmware motion is expressed by `motionSources` containing
8523
+ * `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
8524
+ * binding — but both answer "what drives the detection pipeline", which is a
8525
+ * different question from "what may wake a sleeping stream". A camera can do
8526
+ * the first and not be trusted with the second, and the operator picks per
8527
+ * camera. Two questions, two authorities.
8528
+ *
8529
+ * ## Availability is not permission
8530
+ *
8531
+ * `listSignals` says what the device CAN emit. Whether a given signal actually
8532
+ * wakes the stream is the operator's per-camera choice, held by the broker
8533
+ * alongside the cooldown — see the stream-broker cap's wake settings. A
8534
+ * provider declaring a signal is not a provider enabling it.
8535
+ */
8536
+ /** One signal a device can emit. */
8537
+ var StreamSignalSchema = zod.z.object({
8538
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
8539
+ code: zod.z.string().min(1),
8540
+ /** What an operator reads in the picker. The provider's own wording. */
8541
+ label: zod.z.string().min(1),
8542
+ /**
8543
+ * Whether the provider recommends this signal ON when a camera is first set
8544
+ * up. A provider knows which of its signals are cheap and reliable; an
8545
+ * operator should not have to discover that by trial. Reolink recommends
8546
+ * both of its own.
8547
+ */
8548
+ recommended: zod.z.boolean()
8549
+ });
8550
+ var StreamSignalsStatusSchema = zod.z.object({
8551
+ signals: zod.z.array(StreamSignalSchema),
8552
+ lastFetchedAt: zod.z.number()
8553
+ });
8554
+ var streamSignalsCapability = {
8555
+ name: "stream-signals",
8556
+ scope: "device",
8557
+ deviceNative: true,
8558
+ mode: "singleton",
8559
+ deviceTypes: Object.values(DeviceType),
8560
+ runtimeState: StreamSignalsStatusSchema,
8561
+ methods: {
8562
+ /**
8563
+ * What this device can emit. Empty is a valid and common answer — most
8564
+ * cameras have nothing to offer here, and an empty list is what makes the
8565
+ * broker's picker show nothing rather than a false choice.
8566
+ */
8567
+ listSignals: method(zod.z.void(), zod.z.array(StreamSignalSchema).readonly()) }
8568
+ };
8473
8569
  /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8474
8570
  var StreamFormatSchema = zod.z.enum([
8475
8571
  "webrtc",
@@ -8879,6 +8975,46 @@ var streamBrokerCapability = {
8879
8975
  mode: "singleton",
8880
8976
  exposesDeviceSettings: true,
8881
8977
  methods: {
8978
+ /**
8979
+ * A device reporting that one of ITS OWN signals just fired.
8980
+ *
8981
+ * The provider calls this; nothing polls. The broker then decides whether
8982
+ * to wake the source, and the decision has two terms that must BOTH hold:
8983
+ *
8984
+ * - the signal is ENABLED for this camera (the operator's per-camera
8985
+ * choice — a camera may declare a signal the operator does not want
8986
+ * waking anything), and
8987
+ * - a consumer is actually attached and waiting on the prestream. Waking
8988
+ * a camera nobody is watching is the one thing a battery camera must
8989
+ * never do, and a signal arriving with no consumer is exactly that.
8990
+ *
8991
+ * Reported, never inferred: a signal that fires while the stream is
8992
+ * already up refreshes the hold instead of starting a second one, which is
8993
+ * what makes continuous motion keep a camera awake without stacking
8994
+ * timers.
8995
+ *
8996
+ * `code` is the provider's own, from `streamSignals.listSignals`. An
8997
+ * unknown code is REFUSED rather than treated as a wildcard wake — a
8998
+ * provider and an operator disagreeing about a name must not become a
8999
+ * camera that wakes for everything.
9000
+ */
9001
+ reportStreamSignal: method(zod.z.object({
9002
+ deviceId: zod.z.number().int().nonnegative(),
9003
+ /** The provider's signal code. */
9004
+ code: zod.z.string().min(1),
9005
+ /** Ms epoch. Absent ⇒ now. */
9006
+ at: zod.z.number().optional()
9007
+ }), zod.z.object({
9008
+ /** Whether the broker acted on it, and if not, why. */
9009
+ accepted: zod.z.boolean(),
9010
+ reason: zod.z.enum([
9011
+ "woke",
9012
+ "hold-extended",
9013
+ "not-enabled",
9014
+ "no-consumer",
9015
+ "unknown-code"
9016
+ ])
9017
+ }), { kind: "mutation" }),
8882
9018
  publishCameraStream: method(zod.z.object({
8883
9019
  deviceId: zod.z.number().int().nonnegative(),
8884
9020
  camStreamId: zod.z.string().min(1),
@@ -9356,6 +9492,16 @@ var PickStreamRequirementsSchema = zod.z.object({
9356
9492
  acceptCodecs: zod.z.array(StreamCodecSchema).readonly().optional(),
9357
9493
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
9358
9494
  minHeight: zod.z.number().int().positive().optional(),
9495
+ /**
9496
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
9497
+ *
9498
+ * A consumer can have a ceiling as real as its floor: Alexa documents
9499
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
9500
+ * Without this the ceiling had to be re-implemented by every caller — and
9501
+ * one caller implementing it privately is how a second scoring authority
9502
+ * gets born.
9503
+ */
9504
+ maxHeight: zod.z.number().int().positive().optional(),
9359
9505
  /** Minimum horizontal resolution. */
9360
9506
  minWidth: zod.z.number().int().positive().optional(),
9361
9507
  /**
@@ -9372,7 +9518,22 @@ var PickStreamRequirementsSchema = zod.z.object({
9372
9518
  * transcoded" guard: if the device is already serving the consumer's
9373
9519
  * codec end-to-end, there's nothing to optimise.
9374
9520
  */
9375
- requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional()
9521
+ requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional(),
9522
+ /**
9523
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
9524
+ * understanding that it will be transcoded.
9525
+ *
9526
+ * Default `false` — today's behaviour, and the right one for a bypass
9527
+ * question ("is there a stream I can forward untouched?"). Set `true` to
9528
+ * ask the larger question: "what is the best stream for me, transcoding if
9529
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
9530
+ * does not, so a passthrough is never lost to a transcode; the answer says
9531
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
9532
+ *
9533
+ * This is what lets one picker serve both the bypass and the full source
9534
+ * choice, instead of a consumer scoring privately when the bypass misses.
9535
+ */
9536
+ allowTranscode: zod.z.boolean().optional()
9376
9537
  }).readonly();
9377
9538
  var PickStreamPreferencesSchema = zod.z.object({
9378
9539
  /**
@@ -9386,12 +9547,32 @@ var PickStreamPreferencesSchema = zod.z.object({
9386
9547
  * picks the tallest stream; `'lowest'` picks the shortest (used by
9387
9548
  * memory-constrained consumers / Apple Home guest sessions).
9388
9549
  */
9389
- resolutionPreference: zod.z.enum(["highest", "lowest"]).optional()
9550
+ resolutionPreference: zod.z.enum(["highest", "lowest"]).optional(),
9551
+ /**
9552
+ * The height the consumer actually wants to DELIVER.
9553
+ *
9554
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
9555
+ * above the target wins, and only if nothing reaches it does the tallest
9556
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
9557
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
9558
+ * express that: it says "as big as possible", which is a different wish.
9559
+ *
9560
+ * Ignored when absent, so every existing caller keeps its ordering.
9561
+ */
9562
+ targetHeight: zod.z.number().int().positive().optional()
9390
9563
  }).readonly();
9391
9564
  var PickedCamStreamSchema = zod.z.object({
9392
9565
  camStreamId: zod.z.string(),
9393
9566
  codec: zod.z.string().optional(),
9394
9567
  resolution: CamStreamResolutionSchema.optional(),
9568
+ /**
9569
+ * Whether serving this stream requires a decode + re-encode.
9570
+ *
9571
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
9572
+ * when the caller asked for it with `allowTranscode`, so a caller that did
9573
+ * not ask cannot be handed a cost it never agreed to pay.
9574
+ */
9575
+ transcodes: zod.z.boolean(),
9395
9576
  /** One-line explanation of why this stream won — for logs / debug UI. */
9396
9577
  reason: zod.z.string()
9397
9578
  });
@@ -15468,6 +15649,8 @@ var NcSystemEventKindSchema = zod.z.enum([
15468
15649
  "device-enabled",
15469
15650
  "device-battery-low",
15470
15651
  "device-battery-normal",
15652
+ "device-consumable-low",
15653
+ "device-consumable-normal",
15471
15654
  "stream-online",
15472
15655
  "stream-offline",
15473
15656
  "node-online",
@@ -15486,6 +15669,7 @@ var NcSystemEventKindSchema = zod.z.enum([
15486
15669
  "addon-updated",
15487
15670
  "server-updated",
15488
15671
  "export-completed",
15672
+ "backup-completed",
15489
15673
  "camera-online",
15490
15674
  "camera-offline",
15491
15675
  "camera-disabled",
@@ -24927,10 +25111,28 @@ var webrtcSessionCapability = {
24927
25111
  getSessionState: method(zod.z.object({
24928
25112
  deviceId: zod.z.number().int().nonnegative(),
24929
25113
  sessionId: zod.z.string()
24930
- }), zod.z.object({ pendingRenegotiation: zod.z.object({
24931
- target: WebrtcStreamTargetSchema,
24932
- epoch: zod.z.number()
24933
- }).nullable() }))
25114
+ }), zod.z.object({
25115
+ pendingRenegotiation: zod.z.object({
25116
+ target: WebrtcStreamTargetSchema,
25117
+ epoch: zod.z.number()
25118
+ }).nullable(),
25119
+ /**
25120
+ * Whether the session still EXISTS.
25121
+ *
25122
+ * A consumer that holds a resource for the life of a session needs to
25123
+ * be able to ask, because a session does not always end the way it
25124
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
25125
+ * sent `SessionDisconnected`, so the Alexa exporter's
25126
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
25127
+ * H.264 transcode kept running for NOBODY for more than twenty
25128
+ * minutes. The WebRTC session had logged `WebRTC session closed`
25129
+ * minutes earlier — the broker knew; the holder had no way to ask.
25130
+ *
25131
+ * `false` for a session id the provider has never heard of, which is
25132
+ * the same answer as "it ended": either way nothing is holding it up.
25133
+ */
25134
+ alive: zod.z.boolean()
25135
+ }))
24934
25136
  },
24935
25137
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
24936
25138
  mount: { kind: "server-provided" }
@@ -36953,6 +37155,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
36953
37155
  streamBrokerCapability,
36954
37156
  streamCatalogCapability,
36955
37157
  streamParamsCapability,
37158
+ streamSignalsCapability,
36956
37159
  switchCapability,
36957
37160
  systemCapability,
36958
37161
  tamperCapability,
@@ -42395,6 +42598,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
42395
42598
  addonId: null,
42396
42599
  access: "create"
42397
42600
  },
42601
+ "streamBroker.reportStreamSignal": {
42602
+ capName: "stream-broker",
42603
+ capScope: "system",
42604
+ addonId: null,
42605
+ access: "create"
42606
+ },
42398
42607
  "streamBroker.restartProfile": {
42399
42608
  capName: "stream-broker",
42400
42609
  capScope: "system",
@@ -42479,6 +42688,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
42479
42688
  addonId: null,
42480
42689
  access: "create"
42481
42690
  },
42691
+ "streamSignals.listSignals": {
42692
+ capName: "stream-signals",
42693
+ capScope: "device",
42694
+ addonId: null,
42695
+ access: "view"
42696
+ },
42482
42697
  "switch.setState": {
42483
42698
  capName: "switch",
42484
42699
  capScope: "device",
@@ -44851,6 +45066,11 @@ Object.freeze({
44851
45066
  form: "single",
44852
45067
  optional: false
44853
45068
  }],
45069
+ "streamBroker.reportStreamSignal": [{
45070
+ name: "deviceId",
45071
+ form: "single",
45072
+ optional: false
45073
+ }],
44854
45074
  "streamBroker.restartProfile": [{
44855
45075
  name: "deviceId",
44856
45076
  form: "single",
@@ -45083,7 +45303,7 @@ Object.freeze({
45083
45303
  });
45084
45304
  /**
45085
45305
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
45086
- * `runtimeState`. 67 caps: 35 `restored`, 32 `session`.
45306
+ * `runtimeState`. 68 caps: 35 `restored`, 33 `session`.
45087
45307
  *
45088
45308
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
45089
45309
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -45309,6 +45529,10 @@ var RUNTIME_STATE_POLICY = {
45309
45529
  durability: "restored",
45310
45530
  volatileFields: ["lastFetchedAt"]
45311
45531
  },
45532
+ "stream-signals": {
45533
+ durability: "session",
45534
+ volatileFields: []
45535
+ },
45312
45536
  "switch": {
45313
45537
  durability: "restored",
45314
45538
  volatileFields: ["lastChangedAt"]
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-zr6q3Aad.js");
3
+ const require_dist = require("./dist-CEIkHEdy.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");
@@ -29,7 +29,7 @@ const require_event_loop_stall_monitor = require("./event-loop-stall-monitor-CAs
29
29
  const require_tls$1 = require("./tls-BFs4PzoW.js");
30
30
  const require_manifest_system_deps = require("./manifest-system-deps-CrSP0Kkp.js");
31
31
  const require_resource_monitor = require("./resource-monitor-LVE1BoLs.js");
32
- const require_custom_action_registry = require("./custom-action-registry-YD6rwIuz.js");
32
+ const require_custom_action_registry = require("./custom-action-registry-YHpAdksL.js");
33
33
  let zod = require("zod");
34
34
  let node_crypto = require("node:crypto");
35
35
  let _camstack_types_node = require("@camstack/types/node");
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 { A as addonSettingsCapability, Dt as procedureAuthKey, Ft as scopesAllowDeviceCap, Jt as errMsg$1, Mt as resolveMethodAuth, Pt as scopesAllowAddon, Qt as DEVICE_STATUS_METHOD, Xt as DATAPLANE_SECRET_HEADER$1, Zt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, _ as RUNTIME_DEFAULTS, _n as readinessKey, _t as logLevelAtMost, an as asJsonObject$1, at as isArrayOutputSchema, cn as createEvent, ct as isObjectInput, dn as expandCapMethods, dt as isVoidInput, ft as kebabToCamel, gn as parseJsonUnknown, h as METHOD_ACCESS_MAP, hn as parseJsonObject, ln as emitDownForOwnedCaps, mt as lifecycleJobSchema, nn as ReadinessRegistry, on as asNumber, ot as isCollectionArrayMethod, p as LOG_LEVEL_RANK, rn as ReadinessTimeoutError, sn as asString$1, t as ALL_CAPABILITY_DEFINITIONS, tt as extractNestedAddonId, vn as resolveCapMount, vt as looseSchema, wt as objectInputDeclaresAddonId, xn as EventCategory$1, yn as scopeKey } from "./dist-C0M6CBhV.mjs";
2
+ import { A as addonSettingsCapability, Dt as procedureAuthKey, Ft as scopesAllowDeviceCap, Jt as errMsg$1, Mt as resolveMethodAuth, Pt as scopesAllowAddon, Qt as DEVICE_STATUS_METHOD, Xt as DATAPLANE_SECRET_HEADER$1, Zt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, _ as RUNTIME_DEFAULTS, _n as readinessKey, _t as logLevelAtMost, an as asJsonObject$1, at as isArrayOutputSchema, cn as createEvent, ct as isObjectInput, dn as expandCapMethods, dt as isVoidInput, ft as kebabToCamel, gn as parseJsonUnknown, h as METHOD_ACCESS_MAP, hn as parseJsonObject, ln as emitDownForOwnedCaps, mt as lifecycleJobSchema, nn as ReadinessRegistry, on as asNumber, ot as isCollectionArrayMethod, p as LOG_LEVEL_RANK, rn as ReadinessTimeoutError, sn as asString$1, t as ALL_CAPABILITY_DEFINITIONS, tt as extractNestedAddonId, vn as resolveCapMount, vt as looseSchema, wt as objectInputDeclaresAddonId, xn as EventCategory$1, yn as scopeKey } from "./dist-BX6SpI0i.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";
@@ -28,7 +28,7 @@ import { _ as fetchJson, a as createFileDataPlaneHandler, c as parseRangeHeader,
28
28
  import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-2xbgg78V.mjs";
29
29
  import { $ as buildUdsNativeCapProxy, $t as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, A as createUdsLoggerWithControl, An as recordSocketFrame, At as HUB_MAIN_HEAP_WATCH_LABEL, B as readMoleculerFanoutMode, Bn as diffCapActionCensus, Bt as createLoopDelayMeter, C as ipcParentLink, Cn as createSocketPlaneMeter, Ct as runNpm, D as createUdsEventBus, Dn as readSocketQueues, Dt as HEAP_RECLAIM_TRIGGER_MB, E as createUdsEventBridge, En as formatSocketPlane, Et as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, F as CapRouteError, Fn as CAP_ACTION_UNLABELLED, Ft as RUNNER_HEAP_WATCH_INTERVAL_MS, G as createLocalTransport, Gn as formatEventPlane, Gt as parseRssBudgetMb, H as PARENT_ROUTED_PROVIDER_ADDON_ID, Hn as createEventPlaneMeter, Ht as describeRss, I as classifyCapRoute, In as EMPTY_CAP_ACTION_READING, It as RUNNER_RSS_BUDGET_ENV, J as SocketChannel, Jt as startHeapWatch, K as UdsLocalTransportClient, Kt as reclaimIntervalMs, L as LocalChildClient, Ln as MAX_CAP_ACTIONS_PER_PEER, Lt as ZERO_LOOP_DELAY, M as AGENT_CAP_FWD_SERVICE, Mn as socketDirectionBytes, Mt as HUB_RSS_BUDGET_ENV, N as CapRouteResolver, Nn as socketDirectionMessages, Nt as RSS_BUDGET_REANNOUNCE_MIN_MS, O as udsChildLogToWorkerEntry, On as EMPTY_SOCKET_DIRECTION, Ot as HEAP_WATCH_INTERVAL_MS, P as callWithServiceDiscovery, Pn as CAP_ACTION_OVERFLOW_LABEL, Pt as RSS_BUDGET_RELEASE_RATIO, Q as buildNativeCapProxy, Qt as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, R as LocalChildRegistry, Rn as capActionLabel, Rt as buildHeapSample, S as buildLinkChain, Sn as SOCKET_QUEUE_TOP_N, St as resolveNpmInvocation, T as createParentUnownedCallHandler, Tn as diffSocketPlane, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, U as __resetCapUsageRegistryForTests, Un as createEventPlaneReader, Ut as hubMainRssBudget, V as CapUsageRegistry, Vn as EVENT_PLANE_TOP_N, Vt as createV8Reclaimer, W as getCapUsageRegistry, Wn as diffEventPlane, Wt as nextRssBudgetState, X as FrameDecoder, Xt as strandedMb, Y as localEndpointPath, Yt as startRunnerHeapWatch, Z as encodeFrame, Zt as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, _ as AddonDepsManager, _n as MESH_QUEUE_TOP_N, _t as CapabilityUnavailableError, a as createUdsAddonContext, an as DECISIVE_HEAP_SPACES, at as createAddonService, b as brokerCallForCap, bn as EMPTY_SOCKET_PLANE_BASELINE, bt as resolveAddonClass, c as getOrInitReadinessRegistryForClient, cn as formatHeapSpaces, ct as capActionName, d as clusterEventTopic, dn as selectReportedSpaces, dt as capServiceName, en as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, et as createBrokerDeviceManagerApi, f as getBrokerEventBus, fn as OUTBOUND_QUEUE_COOLDOWN_MS, ft as parseCapAction, g as subscribePassthrough, gn as formatOutboundQueueVerdict, gt as CapabilityHandle, h as setNodeEventInterest, hn as createOutboundQueueGate, ht as DeviceRegistry, i as createAddonContext, in as isArrayBufferCensusReport, j as AGENT_CAP_FWD_ACTION, jn as sampleSocketDirection, jt as HUB_MAIN_RSS_BUDGET_MB, k as createUdsLogger, kn as createSocketDirectionCounters, kt as HEAP_WATCH_WARN_RATIO, l as setHubConnected, ln as heapSpaceField, lt as capActionSuffix, m as registerEventBusService, mn as OUTBOUND_QUEUE_THRESHOLD_BYTES, mt as serializeTypedArrays, n as installManifestPythonDeps, nn as createInspectorArrayBufferCensus, o as getWorkerDeviceRegistry, on as HEAP_SPACE_REPORT_MIN_MB, ot as validateProviderRegistrations, p as getMoleculerEventStats, pn as OUTBOUND_QUEUE_SUSTAIN_MS, pt as deserializeTypedArrays, q as UdsLocalTransportServer, qt as shouldReclaim, r as adaptBrokerToCluster, rn as formatArrayBufferCensus, rt as mountNativeCapService, s as getOrInitReadinessRegistry, sn as emitHeapDiagnosticReport, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestSystemDeps, tn as createArrayBufferCensusGate, u as EVENT_TOPIC_PREFIX, un as readHeapSpaces, ut as capBareAction, v as createKernelHwAccel, vn as createMeshQueueReader, vt as copyBundledNativeModules, w as localProviderLink, wn as createSocketPlaneReader, wt as createAddonDataPlaneFacility, x as brokerTransportLink, xn as SOCKET_PLANE_TOP_N, xt as buildNpmChildEnv, y as resolveHwAccel, yn as formatMeshQueue, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX, zn as createCapActionCensus, zt as createDeferredHeapWatchSink } from "./manifest-system-deps-BEj9Dpu1.mjs";
30
30
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Vp0bPV3U.mjs";
31
- import { t as CustomActionRegistry } from "./custom-action-registry-zTQXMaNb.mjs";
31
+ import { t as CustomActionRegistry } from "./custom-action-registry-gaO1AlT2.mjs";
32
32
  import { z } from "zod";
33
33
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
34
34
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
@@ -1,4 +1,4 @@
1
- import { an as asJsonObject } from "./dist-C0M6CBhV.mjs";
1
+ import { an as asJsonObject } from "./dist-BX6SpI0i.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?
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-zr6q3Aad.js");
1
+ const require_dist = require("./dist-CEIkHEdy.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?
@@ -0,0 +1,140 @@
1
+ import sharp from "sharp";
2
+ //#region src/builtins/snapshot/snapshot-courtesy.ts
3
+ /**
4
+ * State treatment for a camera that cannot produce a current frame.
5
+ *
6
+ * ── Why this exists ───────────────────────────────────────────────────────
7
+ * A disabled, offline or sleeping camera has no frame, and until 2026-08-11 the
8
+ * snapshot service simply had nothing to say about it: the media route answered
9
+ * **404** and the client painted "Unavailable". Measured on the live grid that
10
+ * day, 10 of 26 tiles were 404s — one genuinely disabled camera plus nine dead
11
+ * legacy rows. A 404 is indistinguishable from a broken camera, so an operator
12
+ * who deliberately switched a camera off saw the same thing as a fault ([D62]:
13
+ * "an off switch is REPORTED off; disabled must never look like broken").
14
+ *
15
+ * So the service answers with an image that SAYS what is going on, carrying
16
+ * the camera's own name. When a real cached frame exists it remains visible
17
+ * under the treatment; otherwise the generated courtesy background is used.
18
+ *
19
+ * ── Why sharp and not ffmpeg ──────────────────────────────────────────────
20
+ * The first draft of this file shelled out to `ffmpeg` with a `drawtext`
21
+ * filter. That was written before the resize path was measured, and it was the
22
+ * wrong call for the same reason: this runs in the snapshot wrapper, a system
23
+ * builtin loaded in the hub's ROOT process, so every render was a fork + exec
24
+ * on the loop that serves the API — 52 ms against sharp's 4.8 ms in that
25
+ * container, to draw two lines of text.
26
+ *
27
+ * `terminal-frame-renderer.ts` already renders text this way (SVG → sharp), so
28
+ * this is the house pattern rather than a new one. It also drops the font-PATH
29
+ * probing the ffmpeg version needed: an SVG names a font FAMILY and fontconfig
30
+ * resolves it — verified in the hub image, where `fc-match "DejaVu Sans Mono"`
31
+ * answers with the real file.
32
+ *
33
+ * Rendering is pure input → bytes with no I/O of its own, and the geometry and
34
+ * escaping are separated out so they stay testable without rasterizing.
35
+ */
36
+ /** Default tile size when the caller asks for no particular width. */
37
+ var COURTESY_DEFAULT_WIDTH = 640;
38
+ /**
39
+ * Font stack for the rendered text. Family names, not paths — librsvg resolves
40
+ * them through fontconfig, and the trailing generics keep a host without DejaVu
41
+ * rendering something legible instead of nothing.
42
+ */
43
+ var COURTESY_FONT_STACK = "DejaVu Sans,DejaVu Sans Mono,Helvetica,Arial,sans-serif";
44
+ /** The word the frame carries. Deliberately the operator's vocabulary. */
45
+ function courtesyLabel(reason) {
46
+ switch (reason) {
47
+ case "disabled": return "Disabled";
48
+ case "offline": return "Offline";
49
+ case "sleeping": return "Sleeping";
50
+ case "unreachable": return "Unreachable";
51
+ case "waking": return "Waking";
52
+ }
53
+ }
54
+ /**
55
+ * Background per reason. A disabled camera is a DELIBERATE state and must not
56
+ * read as an alarm, so it is neutral grey; offline is a fault and is warmer.
57
+ */
58
+ function courtesyBackground(reason) {
59
+ switch (reason) {
60
+ case "disabled": return "#2b2b31";
61
+ case "offline": return "#3a2b2b";
62
+ case "sleeping": return "#232b3a";
63
+ case "waking": return "#243247";
64
+ case "unreachable": return "#3a2b2b";
65
+ }
66
+ }
67
+ /** XML escaping for text placed inside an SVG `<text>` node. */
68
+ function escapeCourtesyText(value) {
69
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
70
+ }
71
+ /**
72
+ * The SVG the frame is rasterized from: the state large and centred, the
73
+ * camera's name under it, smaller and dimmer.
74
+ *
75
+ * Sizes derive from the width so a 240 px grid tile and a 1920 px full-bleed
76
+ * frame read the same, with a floor so a thumbnail stays legible.
77
+ */
78
+ function buildCourtesySvg(spec) {
79
+ return buildStateSvg(spec, false);
80
+ }
81
+ /**
82
+ * The same state treatment as a courtesy frame, but translucent so the last
83
+ * real camera frame remains visible underneath it.
84
+ */
85
+ function buildCourtesyOverlaySvg(spec) {
86
+ return buildStateSvg(spec, true);
87
+ }
88
+ function buildStateSvg(spec, overlay) {
89
+ const stateSize = Math.max(12, Math.round(spec.width / 12));
90
+ const nameSize = Math.max(9, Math.round(spec.width / 26));
91
+ const state = escapeCourtesyText(courtesyLabel(spec.reason));
92
+ const name = escapeCourtesyText(spec.deviceName);
93
+ const midY = spec.height / 2;
94
+ const washOpacity = overlay ? "0.62" : "1";
95
+ const textStroke = Math.max(1, Math.round(spec.width / 320));
96
+ return [
97
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
98
+ `<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="${washOpacity}"/>`,
99
+ `<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
100
+ `<text x="50%" y="${String(Math.round(midY))}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}</text>`,
101
+ `<text x="50%" y="${String(Math.round(midY + stateSize))}" font-size="${String(nameSize)}" fill="#9a9aa8">${name}</text>`,
102
+ `</g></svg>`
103
+ ].join("");
104
+ }
105
+ /** Cache key — a courtesy frame is a pure function of these four. */
106
+ function courtesyCacheKey(spec) {
107
+ return `${spec.reason}:${String(spec.width)}x${String(spec.height)}:${spec.deviceName}`;
108
+ }
109
+ /**
110
+ * Render the frame.
111
+ *
112
+ * Rejects on failure — the caller decides what to do, exactly as `resizeJpeg`
113
+ * does, so a broken courtesy path is never mistaken for a broken camera.
114
+ */
115
+ function renderCourtesyJpeg(spec) {
116
+ return sharp(Buffer.from(buildCourtesySvg(spec))).jpeg({ quality: 82 }).toBuffer().then((bytes) => {
117
+ if (bytes.length === 0) throw new Error("courtesy frame produced no bytes");
118
+ return bytes;
119
+ });
120
+ }
121
+ /**
122
+ * Paint the state treatment over a real cached frame.
123
+ *
124
+ * The source dimensions are authoritative: overlays preserve the original
125
+ * frame rather than resizing it to the generated courtesy-frame default.
126
+ */
127
+ async function renderCourtesyOverlayJpeg(background, spec) {
128
+ const metadata = await sharp(background).metadata();
129
+ if (!metadata.width || !metadata.height) throw new Error("cached snapshot has no renderable dimensions");
130
+ const overlay = Buffer.from(buildCourtesyOverlaySvg({
131
+ ...spec,
132
+ width: metadata.width,
133
+ height: metadata.height
134
+ }));
135
+ const bytes = await sharp(background).composite([{ input: overlay }]).jpeg({ quality: 82 }).toBuffer();
136
+ if (bytes.length === 0) throw new Error("courtesy overlay produced no bytes");
137
+ return bytes;
138
+ }
139
+ //#endregion
140
+ export { renderCourtesyOverlayJpeg as i, courtesyCacheKey as n, renderCourtesyJpeg as r, COURTESY_DEFAULT_WIDTH as t };