@camstack/system 1.2.91 → 1.2.93

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 (58) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/blocks-integration.d.ts +34 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.js +69 -22
  15. package/dist/builtins/core-blocks/core-blocks.addon.mjs +69 -22
  16. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  17. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  19. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  20. package/dist/builtins/hub-forwarder/index.js +1 -1
  21. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  23. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  25. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  26. package/dist/builtins/local-network/local-network.addon.js +1 -1
  27. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  28. package/dist/builtins/loki-logging/index.js +1 -1
  29. package/dist/builtins/loki-logging/index.mjs +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  31. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  32. package/dist/builtins/platform-probe/index.js +1 -1
  33. package/dist/builtins/platform-probe/index.mjs +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  35. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  36. package/dist/builtins/snapshot/index.js +27 -6
  37. package/dist/builtins/snapshot/index.mjs +27 -6
  38. package/dist/builtins/snapshot/snapshot-courtesy.d.ts +12 -2
  39. package/dist/builtins/snapshot/snapshot.addon.d.ts +11 -0
  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 +1 -1
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  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/{dist-9Gfk4KOk.js → dist-BMus2E5R.js} +351 -7
  51. package/dist/{dist-DhrTd8LL.mjs → dist-F1XDQDE1.mjs} +346 -8
  52. package/dist/index.js +41 -6
  53. package/dist/index.mjs +41 -6
  54. package/dist/kernel/addon-health-monitor.d.ts +10 -1
  55. package/dist/logging/log-manager.d.ts +28 -0
  56. package/dist/{manifest-python-deps-C0zkl1Ae.js → manifest-python-deps-BZO_eMn-.js} +1 -7
  57. package/dist/{manifest-python-deps-fWzdEasI.mjs → manifest-python-deps-D3hP9W8D.mjs} +2 -8
  58. package/package.json +1 -1
@@ -10158,6 +10158,18 @@ var LlmGenerateBaseInputSchema = zod.z.object({
10158
10158
  * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
10159
10159
  * watchdog — operator decision #3).
10160
10160
  */
10161
+ /**
10162
+ * A companion artifact that MUST land beside the main GGUF: the `mmproj`
10163
+ * projector of a vision model, or shards 2..N of a split GGUF. Carried on the
10164
+ * REF rather than looked up at install time, so what the operator approved in
10165
+ * the preview is exactly what the node downloads.
10166
+ */
10167
+ var ManagedModelExtraFileSchema = zod.z.object({
10168
+ url: zod.z.string(),
10169
+ filename: zod.z.string(),
10170
+ sizeBytes: zod.z.number(),
10171
+ sha256: zod.z.string().optional()
10172
+ });
10161
10173
  var ManagedModelRefSchema = zod.z.discriminatedUnion("kind", [
10162
10174
  zod.z.object({
10163
10175
  kind: zod.z.literal("catalog"),
@@ -10166,7 +10178,11 @@ var ManagedModelRefSchema = zod.z.discriminatedUnion("kind", [
10166
10178
  zod.z.object({
10167
10179
  kind: zod.z.literal("url"),
10168
10180
  url: zod.z.string(),
10169
- sha256: zod.z.string().optional()
10181
+ sha256: zod.z.string().optional(),
10182
+ /** Picker/status label; the file basename when absent. */
10183
+ label: zod.z.string().optional(),
10184
+ sizeBytes: zod.z.number().optional(),
10185
+ extraFiles: zod.z.array(ManagedModelExtraFileSchema).optional()
10170
10186
  }),
10171
10187
  zod.z.object({
10172
10188
  kind: zod.z.literal("path"),
@@ -10227,11 +10243,39 @@ var ManagedRuntimeConfigSchema = zod.z.object({
10227
10243
  "q4_1",
10228
10244
  "q4_0"
10229
10245
  ]).optional(),
10246
+ /**
10247
+ * Escape hatch for llama-server flags this schema does NOT model — `--jinja`
10248
+ * (which most vision chat templates need and some language-only models
10249
+ * dislike), `--cont-batching`, `--rope-scaling`, …
10250
+ *
10251
+ * It is NOT a second place to set the flags above. A token that collides
10252
+ * with a typed field is REJECTED at start, naming the field that owns it
10253
+ * (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
10254
+ * the "two switches that disagree" failure this repo has already shipped
10255
+ * twice (D62).
10256
+ */
10257
+ extraArgs: zod.z.array(zod.z.string()).default([]),
10230
10258
  /** Else lazy: first generate boots it. */
10231
10259
  autoStart: zod.z.boolean().default(false),
10232
10260
  /** 0 = never; frees RAM after quiet periods. */
10233
10261
  idleStopMinutes: zod.z.number().int().default(30)
10234
10262
  });
10263
+ /**
10264
+ * Where a multi-GB install currently is. A single 0..1 fraction cannot answer
10265
+ * "is it stuck?" for an install that is three files (shards + mmproj) followed
10266
+ * by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
10267
+ * node looked hung. Phase + file + bytes is the smallest shape that does.
10268
+ */
10269
+ var LlmDownloadProgressSchema = zod.z.object({
10270
+ phase: zod.z.enum(["downloading", "verifying"]),
10271
+ /** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
10272
+ file: zod.z.string(),
10273
+ fileIndex: zod.z.number().int(),
10274
+ fileCount: zod.z.number().int(),
10275
+ /** Across the WHOLE install, not the current file. */
10276
+ downloadedBytes: zod.z.number(),
10277
+ totalBytes: zod.z.number().optional()
10278
+ });
10235
10279
  var LlmRuntimeStatusSchema = zod.z.object({
10236
10280
  /** Status is ALWAYS node-qualified. */
10237
10281
  nodeId: zod.z.string(),
@@ -10248,6 +10292,8 @@ var LlmRuntimeStatusSchema = zod.z.object({
10248
10292
  modelPath: zod.z.string().optional(),
10249
10293
  modelId: zod.z.string().optional(),
10250
10294
  downloadProgress: zod.z.number().min(0).max(1).optional(),
10295
+ /** Detail behind `downloadProgress`; present for the same lifetime. */
10296
+ download: LlmDownloadProgressSchema.optional(),
10251
10297
  lastError: zod.z.string().optional(),
10252
10298
  crashesInWindow: zod.z.number(),
10253
10299
  /** Child RSS (sampled best-effort). */
@@ -10258,7 +10304,14 @@ var LlmNodeModelSchema = zod.z.object({
10258
10304
  file: zod.z.string(),
10259
10305
  sizeBytes: zod.z.number(),
10260
10306
  catalogId: zod.z.string().optional(),
10261
- installedAt: zod.z.number().optional()
10307
+ installedAt: zod.z.number().optional(),
10308
+ /**
10309
+ * Absolute path on the node. Present so a file that is on disk but matches
10310
+ * no catalog entry — a custom Hugging Face install, or a GGUF the operator
10311
+ * copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
10312
+ * it the picker could list such a file and do nothing with it.
10313
+ */
10314
+ path: zod.z.string().optional()
10262
10315
  });
10263
10316
  var LlmRuntimeDiskUsageSchema = zod.z.object({
10264
10317
  nodeId: zod.z.string(),
@@ -10351,9 +10404,12 @@ var LlmProfileSchema = zod.z.object({
10351
10404
  systemPrompt: zod.z.string().optional(),
10352
10405
  /** Total generation bound — the only one a unary call has. */
10353
10406
  timeoutMs: zod.z.number().int().positive().default(6e4),
10354
- /** Wait for response headers only. */
10407
+ /** The TCP handshake only — "is the port even open". NOT the wait for
10408
+ * response headers: on the LM Studio / llama-server wire those are written
10409
+ * once the model has finished loading, so they belong to the bound below. */
10355
10410
  connectTimeoutMs: zod.z.number().int().positive().default(1e4),
10356
- /** Accepted, but no output yet — a cold GPU load lives here. */
10411
+ /** Accepted, but no output yet — response headers included, because a cold
10412
+ * GPU load is exactly what happens before them. */
10357
10413
  firstTokenTimeoutMs: zod.z.number().int().positive().default(12e4),
10358
10414
  /** Output started then stopped. */
10359
10415
  idleTimeoutMs: zod.z.number().int().positive().default(6e4),
@@ -10416,6 +10472,36 @@ var ManagedModelCatalogEntrySchema = zod.z.object({
10416
10472
  /** Vision models: companion projector file. */
10417
10473
  mmprojUrl: zod.z.string().optional()
10418
10474
  });
10475
+ /**
10476
+ * The outcome of turning one operator-typed Hugging Face reference into a
10477
+ * download plan. A RESULT, never a throw: "this repo has 24 quantizations and
10478
+ * I will not pick for you" is a normal answer the UI has to render, not an
10479
+ * exception.
10480
+ *
10481
+ * `candidates` is the whole reason the refusal is usable — every string in it
10482
+ * is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
10483
+ */
10484
+ var HfModelResolutionSchema = zod.z.discriminatedUnion("ok", [zod.z.object({
10485
+ ok: zod.z.literal(true),
10486
+ /** Ready to hand to `installModel` unchanged. */
10487
+ model: ManagedModelRefSchema,
10488
+ label: zod.z.string(),
10489
+ repo: zod.z.string(),
10490
+ quantization: zod.z.string(),
10491
+ purpose: zod.z.enum(["text", "vision"]),
10492
+ totalBytes: zod.z.number(),
10493
+ /** mmproj + shards, for the preview: an operator approving 23 GB should
10494
+ * see that 0.9 GB of it is a projector they did not name. */
10495
+ extraFilenames: zod.z.array(zod.z.string())
10496
+ }), zod.z.object({
10497
+ ok: zod.z.literal(false),
10498
+ code: zod.z.string(),
10499
+ message: zod.z.string(),
10500
+ candidates: zod.z.array(zod.z.string()).optional(),
10501
+ /** Set when the refusal was only the ceiling: re-calling with
10502
+ * `maxBytes: requiredBytes` is the operator's explicit override. */
10503
+ requiredBytes: zod.z.number().optional()
10504
+ })]);
10419
10505
  var LlmRuntimeNodeSchema = zod.z.object({
10420
10506
  nodeId: zod.z.string(),
10421
10507
  reachable: zod.z.boolean(),
@@ -10483,6 +10569,25 @@ var llmCapability = {
10483
10569
  listModelCatalog: method(zod.z.object({}), zod.z.array(ManagedModelCatalogEntrySchema)),
10484
10570
  listRuntimeNodes: method(zod.z.object({}), zod.z.array(LlmRuntimeNodeSchema)),
10485
10571
  listNodeModels: method(zod.z.object({ nodeId: zod.z.string() }), zod.z.array(LlmNodeModelSchema)),
10572
+ /**
10573
+ * One typed Hugging Face reference → a pinned, verified `ManagedModelRef`.
10574
+ *
10575
+ * Runs on the HUB, not on the target node: resolution needs egress to
10576
+ * huggingface.co, and an agent that cannot reach it still installs fine
10577
+ * through the model-distributor relay. Nothing is downloaded here — this is
10578
+ * a tree read plus a HEAD, so the operator sees the size, the quantization
10579
+ * and the mmproj BEFORE approving a multi-GB pull.
10580
+ */
10581
+ resolveModelRef: method(zod.z.object({
10582
+ /** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
10583
+ * `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
10584
+ ref: zod.z.string(),
10585
+ /** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
10586
+ maxBytes: zod.z.number().positive().optional()
10587
+ }), HfModelResolutionSchema, {
10588
+ kind: "mutation",
10589
+ auth: "admin"
10590
+ }),
10486
10591
  installModel: method(zod.z.object({
10487
10592
  nodeId: zod.z.string(),
10488
10593
  model: ManagedModelRefSchema
@@ -12620,9 +12725,66 @@ var NcDeviceStateConditionSchema = zod.z.object({
12620
12725
  /** Any of these matches. */
12621
12726
  states: zod.z.array(zod.z.string().min(1)).min(1)
12622
12727
  });
12728
+ /**
12729
+ * "This rule applies only while scene `sceneId` is `matched` / `diverged`."
12730
+ *
12731
+ * A GATE, not a trigger. `occupancy` and `audio` each DISCRIMINATE their rule —
12732
+ * carrying one makes the rule fire on that subject and nothing else. Scene is
12733
+ * the other shape entirely, the `deviceState` shape: it narrows a rule that
12734
+ * already has a trigger ("tell me about a person at the front door, but only
12735
+ * while the bin is still out"). That is why it composes with every delivery
12736
+ * instead of owning one, and why no new `NcDelivery` member and no new subject
12737
+ * kind exist for it — see D159.
12738
+ *
12739
+ * ── Identity ───────────────────────────────────────────────────────────────
12740
+ * `sceneId` is `SceneMonitor.id`, a `randomUUID()` minted by `createScene` —
12741
+ * globally unique, so it needs no device to disambiguate it. `deviceId` is
12742
+ * carried as a HINT for the editor and for the log line, never as part of the
12743
+ * lookup key: a rule whose hint drifted must still gate correctly.
12744
+ *
12745
+ * ── Which boolean ──────────────────────────────────────────────────────────
12746
+ * `latched` ABSENT means "whatever the scene itself says" — `SceneMonitor.emit`
12747
+ * already declares which boolean drives notification rules, and a second knob
12748
+ * that could disagree with it is exactly the D62 failure. Set it only to
12749
+ * override one rule against the scene's own default.
12750
+ *
12751
+ * - LIVE reading (`emit`/`latched` resolve to live): passes iff
12752
+ * `verdict === requiredState`. `unknown` — no reference for this light, view
12753
+ * shifted, no snapshot — passes NEITHER. A scene that cannot judge is not
12754
+ * evidence, in either direction.
12755
+ * - LATCHED reading: passes iff `latched === (requiredState === 'diverged')`.
12756
+ * The latch is a durable fact about the past ("it has diverged since I armed
12757
+ * it"), so a camera that has gone dark does not clear it — that is the whole
12758
+ * reason the operator asked for a latch.
12759
+ *
12760
+ * The gate reads an in-memory mirror (`NcSceneStateCache`) refreshed OFF the
12761
+ * event path, never the cap: D49. A mirror that has never loaded, or a scene it
12762
+ * does not carry, reads absent and the rule does NOT fire — fail closed, and
12763
+ * said out loud in the log rather than dropped in silence.
12764
+ */
12765
+ var NcSceneConditionSchema = zod.z.object({
12766
+ /** `SceneMonitor.id` — the uuid the cap mints. The whole lookup key. */
12767
+ sceneId: zod.z.string().min(1),
12768
+ /** The camera the scene lives on. A hint for the editor and the log line. */
12769
+ deviceId: zod.z.number().int().optional(),
12770
+ /** The state the scene must be in for the rule to fire. */
12771
+ requiredState: zod.z.enum(["matched", "diverged"]),
12772
+ /**
12773
+ * Read the LATCH (`true`) or the LIVE verdict (`false`). Absent = follow the
12774
+ * scene's own `emit` field, which is the only place that decision belongs.
12775
+ */
12776
+ latched: zod.z.boolean().optional()
12777
+ });
12623
12778
  var NcConditionsSchema = zod.z.object({
12624
12779
  /** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
12625
12780
  deviceState: NcDeviceStateConditionSchema.optional(),
12781
+ /**
12782
+ * Gate on a SCENE's state — "only while the bin is still out". Composes with
12783
+ * every trigger (detection, occupancy, audio, sensor, package, track-end);
12784
+ * unlike `occupancy`/`audio` it discriminates nothing. See
12785
+ * {@link NcSceneCondition} and D159.
12786
+ */
12787
+ scene: NcSceneConditionSchema.optional(),
12626
12788
  /** Device scope — absent = all devices. */
12627
12789
  devices: zod.z.array(zod.z.number()).optional(),
12628
12790
  /** Detector class names (any overlap with the record's class set). */
@@ -13260,6 +13422,7 @@ var NcConditionDescriptorSchema = zod.z.object({
13260
13422
  "occupancy",
13261
13423
  "audio",
13262
13424
  "deviceState",
13425
+ "scene",
13263
13426
  "systemEvent"
13264
13427
  ]),
13265
13428
  operator: zod.z.enum([
@@ -16485,6 +16648,17 @@ var maxSessionHoldMsField = {
16485
16648
  default: 12e4,
16486
16649
  step: 5e3
16487
16650
  };
16651
+ /**
16652
+ * Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
16653
+ * 5s so a rearm can never degenerate into per-event stream churn; default 90s
16654
+ * comfortably outlives the gap between two PIR wakes on a battery camera.
16655
+ */
16656
+ var audioMotionWindowMsField = {
16657
+ min: 5e3,
16658
+ max: 6e5,
16659
+ default: 9e4,
16660
+ step: 5e3
16661
+ };
16488
16662
  var motionFpsField = {
16489
16663
  min: 1,
16490
16664
  max: 30,
@@ -16661,6 +16835,27 @@ var RunnerCameraConfigSchema = zod.z.object({
16661
16835
  * resolved `CameraDetectionConfig`.
16662
16836
  */
16663
16837
  maxSessionHoldMs: zod.z.number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
16838
+ /**
16839
+ * Orchestrator-side quiet period (ms) that closes an `audioMode:
16840
+ * 'on-motion'` audio window, measured from the LAST motion event.
16841
+ *
16842
+ * This exists because the falling edge cannot be relied on. Camera-native
16843
+ * providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
16844
+ * its email-push SMTP path both emit `detected: true` and never the
16845
+ * counterpart); only the frame-diff analyzer emits falls. So on an
16846
+ * onboard-only camera a window that closed only on `detected: false` never
16847
+ * closed at all, and `on-motion` silently behaved as `always-on` — on a
16848
+ * battery camera, the one failure mode the mode exists to prevent.
16849
+ *
16850
+ * Every motion event rearms this timer WITHOUT restarting the stream, so a
16851
+ * burst of re-fires costs nothing. A falling edge, when one does arrive,
16852
+ * still closes earlier via `motionCooldownMs` — whichever comes first wins.
16853
+ *
16854
+ * Not consumed by the runner: carried here so it shares the per-camera
16855
+ * device-settings surface with `motionCooldownMs`, exactly like
16856
+ * `maxSessionHoldMs`.
16857
+ */
16858
+ audioMotionWindowMs: zod.z.number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
16664
16859
  motionFps: zod.z.number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
16665
16860
  detectionFps: zod.z.number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
16666
16861
  motionStreamId: zod.z.string(),
@@ -16756,7 +16951,7 @@ var RunnerCameraConfigSchema = zod.z.object({
16756
16951
  */
16757
16952
  inferenceDevices: zod.z.array(RunnerInferenceDeviceSchema).readonly().optional()
16758
16953
  });
16759
- motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
16954
+ motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
16760
16955
  /**
16761
16956
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
16762
16957
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -20630,6 +20825,25 @@ var BatteryStatusSchema = zod.z.object({
20630
20825
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
20631
20826
  lastUpdated: zod.z.number(),
20632
20827
  /**
20828
+ * Ms epoch of the last time the device PROVED it was reachable — a
20829
+ * completed firmware round-trip, an observed wake, or an inbound push
20830
+ * (firmware event, email). `0`/absent = never since this slice was born.
20831
+ *
20832
+ * This is the ONLY input that separates "asleep" from "gone", and it is
20833
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
20834
+ * for the radio, because a poll that confirms reachability is the same
20835
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
20836
+ * single derivation every consumer must use; no surface computes its own.
20837
+ *
20838
+ * It is deliberately NOT a clock in the
20839
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
20840
+ * observation itself, and it is the only thing a 30-hour silence is
20841
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
20842
+ * Reolink provider) so a value that means "recently" cannot cost a
20843
+ * SQLite commit per round-trip.
20844
+ */
20845
+ lastContactAt: zod.z.number().optional(),
20846
+ /**
20633
20847
  * True when the source is a BINARY low-battery indicator (HA
20634
20848
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
20635
20849
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -26788,6 +27002,33 @@ var recordingExportCapability = {
26788
27002
  * as `unknown`, never guessed. A day reference scored against an IR frame
26789
27003
  * collapses the cosine and would latch a false alarm every single night. */
26790
27004
  var SceneConditionSchema = zod.z.string();
27005
+ /**
27006
+ * What a scene does when the CURRENT light has no reference of its own.
27007
+ *
27008
+ * The lighting variants are not equally likely to exist. Almost every operator
27009
+ * captures daylight and then never stands outside at 22:00 to capture IR, and a
27010
+ * scene that is only ever going to be asked about a daytime question ("is the
27011
+ * bin still on the kerb at 08:00") does not need a night reference at all. The
27012
+ * night half must therefore be OPTIONAL, and optional means the scene keeps
27013
+ * working without it rather than degrading into a permanent complaint.
27014
+ *
27015
+ * - `skip` (default) — the check in that light is not made. Not a verdict, not
27016
+ * an alarm, not even an `unknown`: the live state simply stays whatever the
27017
+ * last covered light left it at, the latch is untouched, and the hysteresis
27018
+ * run is neither spent nor cleared. The scene resumes by itself at first
27019
+ * light. This is the only behaviour under which "I never captured IR" is a
27020
+ * configuration choice instead of a nightly fault.
27021
+ * - `judge-anyway` — score against the OTHER conditions' references. Available
27022
+ * for cameras whose IR frame is close enough to daylight (a floodlit
27023
+ * driveway, an always-white-light doorbell), and wrong for everything else:
27024
+ * cross-condition cosines are not comparable, so a day reference against a
27025
+ * true IR frame collapses and the scene reports a theft at 21:40.
27026
+ *
27027
+ * Never applies when the scene has NO comparable reference at all — that is
27028
+ * "not armed yet", it is reported as `no-reference-for-condition`, and silence
27029
+ * there would hide a scene the operator never finished setting up.
27030
+ */
27031
+ var SceneUncoveredPolicySchema = zod.z.enum(["skip", "judge-anyway"]);
26791
27032
  /** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
26792
27033
  * `unknown` = we cannot judge (no reference for this condition, encoder model
26793
27034
  * changed, view shifted, no snapshot). `unknown` is a real value, not a null,
@@ -26843,6 +27084,9 @@ var SceneCheckSchema = zod.z.discriminatedUnion("mode", [zod.z.object({
26843
27084
  hysteresisCount: zod.z.number().int().positive()
26844
27085
  })]);
26845
27086
  var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
27087
+ /** Night is OPTIONAL. A scene with only a daylight reference sits the IR hours
27088
+ * out in silence rather than reporting a fault every night. */
27089
+ var SCENE_DEFAULT_UNCOVERED_POLICY = "skip";
26846
27090
  /**
26847
27091
  * Vision-model adjudication of a candidate flip. Field names deliberately
26848
27092
  * mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
@@ -26909,6 +27153,21 @@ var SceneMonitorSchema = zod.z.object({
26909
27153
  * automation can react to the bin coming back without the operator's own
26910
27154
  * alarm silently clearing itself. */
26911
27155
  autoRestore: zod.z.boolean().default(false),
27156
+ /** What to do when the current light has no reference of its own. See
27157
+ * {@link SceneUncoveredPolicySchema} — the default makes night OPTIONAL. */
27158
+ onUncoveredCondition: SceneUncoveredPolicySchema.default(SCENE_DEFAULT_UNCOVERED_POLICY),
27159
+ /**
27160
+ * The light whose checks are currently being SAT OUT under
27161
+ * `onUncoveredCondition: 'skip'` — `null` when the scene is checking normally.
27162
+ *
27163
+ * Engine-reported and advisory only: it moves no verdict, no latch and no
27164
+ * hysteresis. It exists so the card can say *"night (IR) — checks paused,
27165
+ * nothing captured in this light"* in the same calm voice as the coverage
27166
+ * line, because the alternative is a scene that silently stops answering
27167
+ * after sunset with nothing anywhere saying why. A skipped check must never
27168
+ * read as a broken one.
27169
+ */
27170
+ suspendedCondition: SceneConditionSchema.nullable().default(null),
26912
27171
  /** Named cause when `verdict === 'unknown'`. */
26913
27172
  unavailable: SceneUnavailableSchema.nullable(),
26914
27173
  /** Conditions that have at least one comparable reference — the coverage line
@@ -26962,6 +27221,7 @@ var sceneMonitorCapability = {
26962
27221
  minObservationSpacingSec: zod.z.number().int().min(0).max(3600).optional(),
26963
27222
  anchorThreshold: zod.z.number().min(0).max(1).optional(),
26964
27223
  autoRestore: zod.z.boolean().optional(),
27224
+ onUncoveredCondition: SceneUncoveredPolicySchema.optional(),
26965
27225
  /** `null` clears the vision-model adjudicator. */
26966
27226
  confirm: SceneConfirmSchema.nullable().optional()
26967
27227
  })
@@ -27266,13 +27526,63 @@ var CamStreamDescriptorSchema = zod.z.object({
27266
27526
  * set of stream descriptors it can offer for the device, synchronously, so the
27267
27527
  * broker can reconcile its registry against the authoritative provider state.
27268
27528
  */
27529
+ /**
27530
+ * The catalog as a DURABLE fact rather than a live answer.
27531
+ *
27532
+ * A battery camera's descriptors are profile-stable — they change when the
27533
+ * operator rewrites an encoder profile, not minute to minute — but building
27534
+ * them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
27535
+ * provider is allowed to build them exactly once per profile and must serve
27536
+ * every later pull from a cache.
27537
+ *
27538
+ * Holding that cache only in RAM is what turned a restart into an outage. The
27539
+ * runner comes back with the camera asleep, `buildStreamCatalogUncached`
27540
+ * correctly refuses to wake it, the pull answers `[]`, the broker has no
27541
+ * cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
27542
+ * fails with a flat "No broker for stream" — for as long as the camera sleeps,
27543
+ * which on a battery cam is most of the day. The camera was fine. The stream
27544
+ * was unreachable because the process had forgotten what the camera offers.
27545
+ *
27546
+ * Declaring it here puts it in `device-runtime-state`, the kernel's canonical
27547
+ * declared collection, with the same `restored` durability `battery` uses for
27548
+ * the same reason: the last known value is the only value there is while the
27549
+ * device is asleep. The broker's brokers are therefore always DEFINABLE — it
27550
+ * is the DIAL that wakes a camera, never the catalog (D173).
27551
+ */
27552
+ var StreamCatalogStateSchema = zod.z.object({
27553
+ /** The descriptors as last built from a real camera response. Never a guess:
27554
+ * a failed or refused build writes NOTHING, so a restored catalog is always
27555
+ * one the camera itself once produced. */
27556
+ descriptors: zod.z.array(CamStreamDescriptorSchema),
27557
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
27558
+ * path decide whether the camera's own awake window is worth spending on a
27559
+ * re-read. */
27560
+ lastFetchedAt: zod.z.number()
27561
+ });
27269
27562
  var streamCatalogCapability = {
27270
27563
  name: "stream-catalog",
27271
27564
  scope: "device",
27272
27565
  deviceNative: true,
27273
27566
  mode: "singleton",
27274
27567
  deviceTypes: [DeviceType.Camera],
27275
- methods: { getCatalog: method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.array(CamStreamDescriptorSchema).readonly()) }
27568
+ methods: { getCatalog: method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.array(CamStreamDescriptorSchema).readonly()) },
27569
+ runtimeState: StreamCatalogStateSchema,
27570
+ /**
27571
+ * Runtime-state durability: **restored** — see the schema doc. A cold
27572
+ * catalog on a sleeping battery camera is not a slow first frame, it is a
27573
+ * camera that cannot be watched at all until it happens to wake.
27574
+ *
27575
+ * Churn is nil by construction: the slice is written only by a SUCCESSFUL
27576
+ * build, and a build only runs when there is no cached copy (or the copy is
27577
+ * a day old and the camera is awake anyway).
27578
+ *
27579
+ * See `RuntimeStateDurability`. Enforced by
27580
+ * `scripts/check-runtime-state-durability.ts`.
27581
+ */
27582
+ durability: "restored",
27583
+ /** Clock field: written, but excluded from the compare that decides whether
27584
+ * persisting is worth a SQLite commit — the descriptors are the value. */
27585
+ volatileStateFields: ["lastFetchedAt"]
27276
27586
  };
27277
27587
  /** One of the camera's stream profiles. */
27278
27588
  var StreamProfileSchema = zod.z.enum([
@@ -29269,6 +29579,24 @@ var DeclaredDevices = class {
29269
29579
  DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
29270
29580
  new Set(Object.values(DeviceType));
29271
29581
  DeviceFeature.BatteryOperated;
29582
+ /**
29583
+ * THE derivation. One crop rectangle, one presence verdict — see D52 for why
29584
+ * this repo insists a derived value has exactly one implementation.
29585
+ *
29586
+ * `undefined` status ⇒ `sleeping`. A device with no slice has made no claim,
29587
+ * and the caller decides separately whether it is even battery-operated
29588
+ * (`DeviceFeature.BatteryOperated`); this function never answers that
29589
+ * question, only what a battery device is doing.
29590
+ */
29591
+ function deriveBatteryPresence(input) {
29592
+ const status = input.status;
29593
+ if (!status) return "sleeping";
29594
+ if (status.sleeping !== true) return "awake";
29595
+ const lastContactAt = status.lastContactAt;
29596
+ if (typeof lastContactAt !== "number" || lastContactAt <= 0) return "sleeping";
29597
+ const budget = input.unreachableAfterMs ?? 216e5;
29598
+ return input.nowMs - lastContactAt > budget ? "unreachable" : "sleeping";
29599
+ }
29272
29600
  /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
29273
29601
  * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
29274
29602
  function unwrap(schema) {
@@ -31707,6 +32035,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
31707
32035
  addonId: null,
31708
32036
  access: "view"
31709
32037
  },
32038
+ "llm.resolveModelRef": {
32039
+ capName: "llm",
32040
+ capScope: "system",
32041
+ addonId: null,
32042
+ access: "create"
32043
+ },
31710
32044
  "llm.setDefault": {
31711
32045
  capName: "llm",
31712
32046
  capScope: "system",
@@ -36876,7 +37210,7 @@ Object.freeze({
36876
37210
  });
36877
37211
  /**
36878
37212
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
36879
- * `runtimeState`. 63 caps: 33 `restored`, 30 `session`.
37213
+ * `runtimeState`. 64 caps: 34 `restored`, 30 `session`.
36880
37214
  *
36881
37215
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
36882
37216
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -37082,6 +37416,10 @@ var RUNTIME_STATE_POLICY = {
37082
37416
  durability: "restored",
37083
37417
  volatileFields: ["lastChangedAt"]
37084
37418
  },
37419
+ "stream-catalog": {
37420
+ durability: "restored",
37421
+ volatileFields: ["lastFetchedAt"]
37422
+ },
37085
37423
  "stream-params": {
37086
37424
  durability: "restored",
37087
37425
  volatileFields: ["lastFetchedAt"]
@@ -37957,6 +38295,12 @@ Object.defineProperty(exports, "decodeVectorBase64", {
37957
38295
  return decodeVectorBase64;
37958
38296
  }
37959
38297
  });
38298
+ Object.defineProperty(exports, "deriveBatteryPresence", {
38299
+ enumerable: true,
38300
+ get: function() {
38301
+ return deriveBatteryPresence;
38302
+ }
38303
+ });
37960
38304
  Object.defineProperty(exports, "deviceManagerCapability", {
37961
38305
  enumerable: true,
37962
38306
  get: function() {