@camstack/addon-provider-homeassistant 1.2.30 → 1.2.32

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.
@@ -11103,6 +11103,8 @@ var QueryFilterSchema = object({
11103
11103
  where: record(string(), unknown()).optional(),
11104
11104
  whereIn: record(string(), array(unknown())).optional(),
11105
11105
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11106
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
11107
+ whereNot: record(string(), unknown()).optional(),
11106
11108
  orderBy: object({
11107
11109
  field: string(),
11108
11110
  direction: _enum(["asc", "desc"])
@@ -11122,7 +11124,8 @@ var QueryFilterSchema = object({
11122
11124
  var MutationFilterSchema = object({
11123
11125
  where: record(string(), unknown()).optional(),
11124
11126
  whereIn: record(string(), array(unknown())).optional(),
11125
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
11127
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
11128
+ whereNot: record(string(), unknown()).optional()
11126
11129
  });
11127
11130
  /** A single stored record: `{ id, data }`. */
11128
11131
  var SettingsRecordSchema = object({
@@ -12787,6 +12790,18 @@ var LlmGenerateBaseInputSchema = object({
12787
12790
  * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
12788
12791
  * watchdog — operator decision #3).
12789
12792
  */
12793
+ /**
12794
+ * A companion artifact that MUST land beside the main GGUF: the `mmproj`
12795
+ * projector of a vision model, or shards 2..N of a split GGUF. Carried on the
12796
+ * REF rather than looked up at install time, so what the operator approved in
12797
+ * the preview is exactly what the node downloads.
12798
+ */
12799
+ var ManagedModelExtraFileSchema = object({
12800
+ url: string(),
12801
+ filename: string(),
12802
+ sizeBytes: number(),
12803
+ sha256: string().optional()
12804
+ });
12790
12805
  var ManagedModelRefSchema = discriminatedUnion("kind", [
12791
12806
  object({
12792
12807
  kind: literal("catalog"),
@@ -12795,7 +12810,11 @@ var ManagedModelRefSchema = discriminatedUnion("kind", [
12795
12810
  object({
12796
12811
  kind: literal("url"),
12797
12812
  url: string(),
12798
- sha256: string().optional()
12813
+ sha256: string().optional(),
12814
+ /** Picker/status label; the file basename when absent. */
12815
+ label: string().optional(),
12816
+ sizeBytes: number().optional(),
12817
+ extraFiles: array(ManagedModelExtraFileSchema).optional()
12799
12818
  }),
12800
12819
  object({
12801
12820
  kind: literal("path"),
@@ -12856,11 +12875,39 @@ var ManagedRuntimeConfigSchema = object({
12856
12875
  "q4_1",
12857
12876
  "q4_0"
12858
12877
  ]).optional(),
12878
+ /**
12879
+ * Escape hatch for llama-server flags this schema does NOT model — `--jinja`
12880
+ * (which most vision chat templates need and some language-only models
12881
+ * dislike), `--cont-batching`, `--rope-scaling`, …
12882
+ *
12883
+ * It is NOT a second place to set the flags above. A token that collides
12884
+ * with a typed field is REJECTED at start, naming the field that owns it
12885
+ * (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
12886
+ * the "two switches that disagree" failure this repo has already shipped
12887
+ * twice (D62).
12888
+ */
12889
+ extraArgs: array(string()).default([]),
12859
12890
  /** Else lazy: first generate boots it. */
12860
12891
  autoStart: boolean().default(false),
12861
12892
  /** 0 = never; frees RAM after quiet periods. */
12862
12893
  idleStopMinutes: number().int().default(30)
12863
12894
  });
12895
+ /**
12896
+ * Where a multi-GB install currently is. A single 0..1 fraction cannot answer
12897
+ * "is it stuck?" for an install that is three files (shards + mmproj) followed
12898
+ * by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
12899
+ * node looked hung. Phase + file + bytes is the smallest shape that does.
12900
+ */
12901
+ var LlmDownloadProgressSchema = object({
12902
+ phase: _enum(["downloading", "verifying"]),
12903
+ /** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
12904
+ file: string(),
12905
+ fileIndex: number().int(),
12906
+ fileCount: number().int(),
12907
+ /** Across the WHOLE install, not the current file. */
12908
+ downloadedBytes: number(),
12909
+ totalBytes: number().optional()
12910
+ });
12864
12911
  var LlmRuntimeStatusSchema = object({
12865
12912
  /** Status is ALWAYS node-qualified. */
12866
12913
  nodeId: string(),
@@ -12877,6 +12924,8 @@ var LlmRuntimeStatusSchema = object({
12877
12924
  modelPath: string().optional(),
12878
12925
  modelId: string().optional(),
12879
12926
  downloadProgress: number().min(0).max(1).optional(),
12927
+ /** Detail behind `downloadProgress`; present for the same lifetime. */
12928
+ download: LlmDownloadProgressSchema.optional(),
12880
12929
  lastError: string().optional(),
12881
12930
  crashesInWindow: number(),
12882
12931
  /** Child RSS (sampled best-effort). */
@@ -12887,7 +12936,14 @@ var LlmNodeModelSchema = object({
12887
12936
  file: string(),
12888
12937
  sizeBytes: number(),
12889
12938
  catalogId: string().optional(),
12890
- installedAt: number().optional()
12939
+ installedAt: number().optional(),
12940
+ /**
12941
+ * Absolute path on the node. Present so a file that is on disk but matches
12942
+ * no catalog entry — a custom Hugging Face install, or a GGUF the operator
12943
+ * copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
12944
+ * it the picker could list such a file and do nothing with it.
12945
+ */
12946
+ path: string().optional()
12891
12947
  });
12892
12948
  var LlmRuntimeDiskUsageSchema = object({
12893
12949
  nodeId: string(),
@@ -12965,9 +13021,12 @@ var LlmProfileSchema = object({
12965
13021
  systemPrompt: string().optional(),
12966
13022
  /** Total generation bound — the only one a unary call has. */
12967
13023
  timeoutMs: number().int().positive().default(6e4),
12968
- /** Wait for response headers only. */
13024
+ /** The TCP handshake only — "is the port even open". NOT the wait for
13025
+ * response headers: on the LM Studio / llama-server wire those are written
13026
+ * once the model has finished loading, so they belong to the bound below. */
12969
13027
  connectTimeoutMs: number().int().positive().default(1e4),
12970
- /** Accepted, but no output yet — a cold GPU load lives here. */
13028
+ /** Accepted, but no output yet — response headers included, because a cold
13029
+ * GPU load is exactly what happens before them. */
12971
13030
  firstTokenTimeoutMs: number().int().positive().default(12e4),
12972
13031
  /** Output started then stopped. */
12973
13032
  idleTimeoutMs: number().int().positive().default(6e4),
@@ -13030,6 +13089,36 @@ var ManagedModelCatalogEntrySchema = object({
13030
13089
  /** Vision models: companion projector file. */
13031
13090
  mmprojUrl: string().optional()
13032
13091
  });
13092
+ /**
13093
+ * The outcome of turning one operator-typed Hugging Face reference into a
13094
+ * download plan. A RESULT, never a throw: "this repo has 24 quantizations and
13095
+ * I will not pick for you" is a normal answer the UI has to render, not an
13096
+ * exception.
13097
+ *
13098
+ * `candidates` is the whole reason the refusal is usable — every string in it
13099
+ * is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
13100
+ */
13101
+ var HfModelResolutionSchema = discriminatedUnion("ok", [object({
13102
+ ok: literal(true),
13103
+ /** Ready to hand to `installModel` unchanged. */
13104
+ model: ManagedModelRefSchema,
13105
+ label: string(),
13106
+ repo: string(),
13107
+ quantization: string(),
13108
+ purpose: _enum(["text", "vision"]),
13109
+ totalBytes: number(),
13110
+ /** mmproj + shards, for the preview: an operator approving 23 GB should
13111
+ * see that 0.9 GB of it is a projector they did not name. */
13112
+ extraFilenames: array(string())
13113
+ }), object({
13114
+ ok: literal(false),
13115
+ code: string(),
13116
+ message: string(),
13117
+ candidates: array(string()).optional(),
13118
+ /** Set when the refusal was only the ceiling: re-calling with
13119
+ * `maxBytes: requiredBytes` is the operator's explicit override. */
13120
+ requiredBytes: number().optional()
13121
+ })]);
13033
13122
  var LlmRuntimeNodeSchema = object({
13034
13123
  nodeId: string(),
13035
13124
  reachable: boolean(),
@@ -13066,6 +13155,15 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13066
13155
  consumer: string().optional(),
13067
13156
  profileId: string().optional()
13068
13157
  }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
13158
+ /** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
13159
+ * `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
13160
+ ref: string(),
13161
+ /** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
13162
+ maxBytes: number().positive().optional()
13163
+ }), HfModelResolutionSchema, {
13164
+ kind: "mutation",
13165
+ auth: "admin"
13166
+ }), method(object({
13069
13167
  nodeId: string(),
13070
13168
  model: ManagedModelRefSchema
13071
13169
  }), _void(), {
@@ -15014,13 +15112,81 @@ var NcRuleActionsSchema = object({
15014
15112
  */
15015
15113
  buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
15016
15114
  });
15115
+ /**
15116
+ * "This rule applies only while `deviceId` is in one of `states`."
15117
+ *
15118
+ * The states are the DEVICE's own vocabulary — `AlarmState` for a panel,
15119
+ * `on`/`off` for a switch — not a normalised set, because normalising would
15120
+ * make the condition lie about devices whose states have no equivalent.
15121
+ *
15122
+ * An unreadable state does NOT match: see the engine's fail-closed gate. A
15123
+ * condition that fired on "I could not read it" would be worse than no gate.
15124
+ */
15125
+ var NcDeviceStateConditionSchema = object({
15126
+ deviceId: number().int(),
15127
+ /** Any of these matches. */
15128
+ states: array(string().min(1)).min(1)
15129
+ });
15130
+ /**
15131
+ * "This rule applies only while scene `sceneId` is `matched` / `diverged`."
15132
+ *
15133
+ * A GATE, not a trigger. `occupancy` and `audio` each DISCRIMINATE their rule —
15134
+ * carrying one makes the rule fire on that subject and nothing else. Scene is
15135
+ * the other shape entirely, the `deviceState` shape: it narrows a rule that
15136
+ * already has a trigger ("tell me about a person at the front door, but only
15137
+ * while the bin is still out"). That is why it composes with every delivery
15138
+ * instead of owning one, and why no new `NcDelivery` member and no new subject
15139
+ * kind exist for it — see D159.
15140
+ *
15141
+ * ── Identity ───────────────────────────────────────────────────────────────
15142
+ * `sceneId` is `SceneMonitor.id`, a `randomUUID()` minted by `createScene` —
15143
+ * globally unique, so it needs no device to disambiguate it. `deviceId` is
15144
+ * carried as a HINT for the editor and for the log line, never as part of the
15145
+ * lookup key: a rule whose hint drifted must still gate correctly.
15146
+ *
15147
+ * ── Which boolean ──────────────────────────────────────────────────────────
15148
+ * `latched` ABSENT means "whatever the scene itself says" — `SceneMonitor.emit`
15149
+ * already declares which boolean drives notification rules, and a second knob
15150
+ * that could disagree with it is exactly the D62 failure. Set it only to
15151
+ * override one rule against the scene's own default.
15152
+ *
15153
+ * - LIVE reading (`emit`/`latched` resolve to live): passes iff
15154
+ * `verdict === requiredState`. `unknown` — no reference for this light, view
15155
+ * shifted, no snapshot — passes NEITHER. A scene that cannot judge is not
15156
+ * evidence, in either direction.
15157
+ * - LATCHED reading: passes iff `latched === (requiredState === 'diverged')`.
15158
+ * The latch is a durable fact about the past ("it has diverged since I armed
15159
+ * it"), so a camera that has gone dark does not clear it — that is the whole
15160
+ * reason the operator asked for a latch.
15161
+ *
15162
+ * The gate reads an in-memory mirror (`NcSceneStateCache`) refreshed OFF the
15163
+ * event path, never the cap: D49. A mirror that has never loaded, or a scene it
15164
+ * does not carry, reads absent and the rule does NOT fire — fail closed, and
15165
+ * said out loud in the log rather than dropped in silence.
15166
+ */
15167
+ var NcSceneConditionSchema = object({
15168
+ /** `SceneMonitor.id` — the uuid the cap mints. The whole lookup key. */
15169
+ sceneId: string().min(1),
15170
+ /** The camera the scene lives on. A hint for the editor and the log line. */
15171
+ deviceId: number().int().optional(),
15172
+ /** The state the scene must be in for the rule to fire. */
15173
+ requiredState: _enum(["matched", "diverged"]),
15174
+ /**
15175
+ * Read the LATCH (`true`) or the LIVE verdict (`false`). Absent = follow the
15176
+ * scene's own `emit` field, which is the only place that decision belongs.
15177
+ */
15178
+ latched: boolean().optional()
15179
+ });
15017
15180
  var NcConditionsSchema = object({
15018
15181
  /** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
15019
- deviceState: object({
15020
- deviceId: number().int(),
15021
- /** Any of these matches. */
15022
- states: array(string().min(1)).min(1)
15023
- }).optional(),
15182
+ deviceState: NcDeviceStateConditionSchema.optional(),
15183
+ /**
15184
+ * Gate on a SCENE's state — "only while the bin is still out". Composes with
15185
+ * every trigger (detection, occupancy, audio, sensor, package, track-end);
15186
+ * unlike `occupancy`/`audio` it discriminates nothing. See
15187
+ * {@link NcSceneCondition} and D159.
15188
+ */
15189
+ scene: NcSceneConditionSchema.optional(),
15024
15190
  /** Device scope — absent = all devices. */
15025
15191
  devices: array(number()).optional(),
15026
15192
  /** Detector class names (any overlap with the record's class set). */
@@ -15658,6 +15824,7 @@ var NcConditionDescriptorSchema = object({
15658
15824
  "occupancy",
15659
15825
  "audio",
15660
15826
  "deviceState",
15827
+ "scene",
15661
15828
  "systemEvent"
15662
15829
  ]),
15663
15830
  operator: _enum([
@@ -17141,7 +17308,10 @@ var RecentTracksQueryInput = object({
17141
17308
  * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
17142
17309
  cursor: string().optional(),
17143
17310
  /** See {@link TrackProjectionSchema}. Default `full`. */
17144
- projection: TrackProjectionSchema.optional()
17311
+ projection: TrackProjectionSchema.optional(),
17312
+ /** Include stationary-promoted rows (parked objects). Default false: the
17313
+ * feed lists passages; parking records live on the stationary registry. */
17314
+ includeStationary: boolean().optional()
17145
17315
  });
17146
17316
  var RecentTracksPageSchema = object({
17147
17317
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -17359,7 +17529,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17359
17529
  zone: TrackZoneFilterSchema.optional(),
17360
17530
  /** See {@link TrackProjectionSchema}. Default `full` (backward
17361
17531
  * compatible — omitting the field keeps today's exact behaviour). */
17362
- projection: TrackProjectionSchema.optional()
17532
+ projection: TrackProjectionSchema.optional(),
17533
+ /** Include stationary-promoted rows (parked objects handed to the
17534
+ * stationary registry). Default false: the timeline lists passages,
17535
+ * not parking records (operator decision, 2026-08-15). */
17536
+ includeStationary: boolean().optional()
17363
17537
  }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17364
17538
  kind: "mutation",
17365
17539
  auth: "admin"
@@ -17523,11 +17697,16 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17523
17697
  auth: "admin"
17524
17698
  }), method(object({
17525
17699
  eventId: string(),
17526
- kind: MediaFileKindEnum.optional()
17700
+ kind: MediaFileKindEnum.optional(),
17701
+ deviceId: number()
17527
17702
  }), array(MediaFileSchema).readonly()), method(object({
17528
17703
  trackId: string(),
17529
- kinds: array(MediaFileKindEnum).optional()
17530
- }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
17704
+ kinds: array(MediaFileKindEnum).optional(),
17705
+ deviceId: number()
17706
+ }), array(MediaFileSchema).readonly()), method(object({
17707
+ trackId: string(),
17708
+ deviceId: number()
17709
+ }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
17531
17710
  kind: "mutation",
17532
17711
  auth: "admin"
17533
17712
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -18227,6 +18406,17 @@ var maxSessionHoldMsField = {
18227
18406
  default: 12e4,
18228
18407
  step: 5e3
18229
18408
  };
18409
+ /**
18410
+ * Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
18411
+ * 5s so a rearm can never degenerate into per-event stream churn; default 90s
18412
+ * comfortably outlives the gap between two PIR wakes on a battery camera.
18413
+ */
18414
+ var audioMotionWindowMsField = {
18415
+ min: 5e3,
18416
+ max: 6e5,
18417
+ default: 9e4,
18418
+ step: 5e3
18419
+ };
18230
18420
  var motionFpsField = {
18231
18421
  min: 1,
18232
18422
  max: 30,
@@ -18403,6 +18593,27 @@ var RunnerCameraConfigSchema = object({
18403
18593
  * resolved `CameraDetectionConfig`.
18404
18594
  */
18405
18595
  maxSessionHoldMs: number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
18596
+ /**
18597
+ * Orchestrator-side quiet period (ms) that closes an `audioMode:
18598
+ * 'on-motion'` audio window, measured from the LAST motion event.
18599
+ *
18600
+ * This exists because the falling edge cannot be relied on. Camera-native
18601
+ * providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
18602
+ * its email-push SMTP path both emit `detected: true` and never the
18603
+ * counterpart); only the frame-diff analyzer emits falls. So on an
18604
+ * onboard-only camera a window that closed only on `detected: false` never
18605
+ * closed at all, and `on-motion` silently behaved as `always-on` — on a
18606
+ * battery camera, the one failure mode the mode exists to prevent.
18607
+ *
18608
+ * Every motion event rearms this timer WITHOUT restarting the stream, so a
18609
+ * burst of re-fires costs nothing. A falling edge, when one does arrive,
18610
+ * still closes earlier via `motionCooldownMs` — whichever comes first wins.
18611
+ *
18612
+ * Not consumed by the runner: carried here so it shares the per-camera
18613
+ * device-settings surface with `motionCooldownMs`, exactly like
18614
+ * `maxSessionHoldMs`.
18615
+ */
18616
+ audioMotionWindowMs: number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
18406
18617
  motionFps: number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
18407
18618
  detectionFps: number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
18408
18619
  motionStreamId: string(),
@@ -18498,7 +18709,7 @@ var RunnerCameraConfigSchema = object({
18498
18709
  */
18499
18710
  inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
18500
18711
  });
18501
- 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;
18712
+ 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;
18502
18713
  /**
18503
18714
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
18504
18715
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -19484,7 +19695,31 @@ DeviceType.Camera, method(object({
19484
19695
  lastCapturedAt: number().nullable(),
19485
19696
  cacheAgeMs: number().nullable(),
19486
19697
  etag: string().nullable()
19487
- }))), systemMethod(object({
19698
+ }))), systemMethod(object({ deviceId: number() }), object({
19699
+ /** The battery slice as read, or null when the device has none. */
19700
+ battery: object({
19701
+ sleeping: boolean(),
19702
+ lastUpdated: number(),
19703
+ lastContactAt: number().optional()
19704
+ }).nullable(),
19705
+ /** The resolved snapshot state (what the overlay decision used). */
19706
+ state: object({
19707
+ isBattery: boolean(),
19708
+ reason: _enum([
19709
+ "disabled",
19710
+ "sleeping",
19711
+ "unreachable",
19712
+ "waking"
19713
+ ]).nullable()
19714
+ }),
19715
+ /** The cached frame behind the next paint. */
19716
+ frame: object({
19717
+ capturedAt: number().nullable(),
19718
+ ageMs: number().nullable()
19719
+ }),
19720
+ /** A wake window is currently open (the Waking overlay's source). */
19721
+ waking: boolean()
19722
+ })), systemMethod(object({
19488
19723
  /** The tiles a surface is actually rendering. One entry per (device,
19489
19724
  * width) the caller will paint — the width is snapped to the server's
19490
19725
  * ladder and becomes part of the link's SIGNED identity. */
@@ -19514,7 +19749,16 @@ targets: array(object({
19514
19749
  /** A sleeping battery camera: the frame is deliberately stale and will
19515
19750
  * NOT refresh in the background. A surface should say so rather than
19516
19751
  * present it as current. */
19517
- sleeping: boolean()
19752
+ sleeping: boolean(),
19753
+ /** Current device state rendered over the cached frame. State images
19754
+ * remain authoritative even when their photographic background is
19755
+ * old; null means the link must carry a current camera frame. */
19756
+ stateReason: _enum([
19757
+ "disabled",
19758
+ "sleeping",
19759
+ "unreachable",
19760
+ "waking"
19761
+ ]).nullable()
19518
19762
  })));
19519
19763
  /**
19520
19764
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -21240,6 +21484,25 @@ var BatteryStatusSchema = object({
21240
21484
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
21241
21485
  lastUpdated: number(),
21242
21486
  /**
21487
+ * Ms epoch of the last time the device PROVED it was reachable — a
21488
+ * completed firmware round-trip, an observed wake, or an inbound push
21489
+ * (firmware event, email). `0`/absent = never since this slice was born.
21490
+ *
21491
+ * This is the ONLY input that separates "asleep" from "gone", and it is
21492
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
21493
+ * for the radio, because a poll that confirms reachability is the same
21494
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
21495
+ * single derivation every consumer must use; no surface computes its own.
21496
+ *
21497
+ * It is deliberately NOT a clock in the
21498
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
21499
+ * observation itself, and it is the only thing a 30-hour silence is
21500
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
21501
+ * Reolink provider) so a value that means "recently" cannot cost a
21502
+ * SQLite commit per round-trip.
21503
+ */
21504
+ lastContactAt: number().optional(),
21505
+ /**
21243
21506
  * True when the source is a BINARY low-battery indicator (HA
21244
21507
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
21245
21508
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -26681,6 +26944,33 @@ method(object({
26681
26944
  * as `unknown`, never guessed. A day reference scored against an IR frame
26682
26945
  * collapses the cosine and would latch a false alarm every single night. */
26683
26946
  var SceneConditionSchema = string();
26947
+ /**
26948
+ * What a scene does when the CURRENT light has no reference of its own.
26949
+ *
26950
+ * The lighting variants are not equally likely to exist. Almost every operator
26951
+ * captures daylight and then never stands outside at 22:00 to capture IR, and a
26952
+ * scene that is only ever going to be asked about a daytime question ("is the
26953
+ * bin still on the kerb at 08:00") does not need a night reference at all. The
26954
+ * night half must therefore be OPTIONAL, and optional means the scene keeps
26955
+ * working without it rather than degrading into a permanent complaint.
26956
+ *
26957
+ * - `skip` (default) — the check in that light is not made. Not a verdict, not
26958
+ * an alarm, not even an `unknown`: the live state simply stays whatever the
26959
+ * last covered light left it at, the latch is untouched, and the hysteresis
26960
+ * run is neither spent nor cleared. The scene resumes by itself at first
26961
+ * light. This is the only behaviour under which "I never captured IR" is a
26962
+ * configuration choice instead of a nightly fault.
26963
+ * - `judge-anyway` — score against the OTHER conditions' references. Available
26964
+ * for cameras whose IR frame is close enough to daylight (a floodlit
26965
+ * driveway, an always-white-light doorbell), and wrong for everything else:
26966
+ * cross-condition cosines are not comparable, so a day reference against a
26967
+ * true IR frame collapses and the scene reports a theft at 21:40.
26968
+ *
26969
+ * Never applies when the scene has NO comparable reference at all — that is
26970
+ * "not armed yet", it is reported as `no-reference-for-condition`, and silence
26971
+ * there would hide a scene the operator never finished setting up.
26972
+ */
26973
+ var SceneUncoveredPolicySchema = _enum(["skip", "judge-anyway"]);
26684
26974
  /** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
26685
26975
  * `unknown` = we cannot judge (no reference for this condition, encoder model
26686
26976
  * changed, view shifted, no snapshot). `unknown` is a real value, not a null,
@@ -26736,6 +27026,9 @@ var SceneCheckSchema = discriminatedUnion("mode", [object({
26736
27026
  hysteresisCount: number().int().positive()
26737
27027
  })]);
26738
27028
  var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
27029
+ /** Night is OPTIONAL. A scene with only a daylight reference sits the IR hours
27030
+ * out in silence rather than reporting a fault every night. */
27031
+ var SCENE_DEFAULT_UNCOVERED_POLICY = "skip";
26739
27032
  /**
26740
27033
  * Vision-model adjudication of a candidate flip. Field names deliberately
26741
27034
  * mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
@@ -26802,6 +27095,21 @@ var SceneMonitorSchema = object({
26802
27095
  * automation can react to the bin coming back without the operator's own
26803
27096
  * alarm silently clearing itself. */
26804
27097
  autoRestore: boolean().default(false),
27098
+ /** What to do when the current light has no reference of its own. See
27099
+ * {@link SceneUncoveredPolicySchema} — the default makes night OPTIONAL. */
27100
+ onUncoveredCondition: SceneUncoveredPolicySchema.default(SCENE_DEFAULT_UNCOVERED_POLICY),
27101
+ /**
27102
+ * The light whose checks are currently being SAT OUT under
27103
+ * `onUncoveredCondition: 'skip'` — `null` when the scene is checking normally.
27104
+ *
27105
+ * Engine-reported and advisory only: it moves no verdict, no latch and no
27106
+ * hysteresis. It exists so the card can say *"night (IR) — checks paused,
27107
+ * nothing captured in this light"* in the same calm voice as the coverage
27108
+ * line, because the alternative is a scene that silently stops answering
27109
+ * after sunset with nothing anywhere saying why. A skipped check must never
27110
+ * read as a broken one.
27111
+ */
27112
+ suspendedCondition: SceneConditionSchema.nullable().default(null),
26805
27113
  /** Named cause when `verdict === 'unknown'`. */
26806
27114
  unavailable: SceneUnavailableSchema.nullable(),
26807
27115
  /** Conditions that have at least one comparable reference — the coverage line
@@ -26855,6 +27163,7 @@ var sceneMonitorCapability = {
26855
27163
  minObservationSpacingSec: number().int().min(0).max(3600).optional(),
26856
27164
  anchorThreshold: number().min(0).max(1).optional(),
26857
27165
  autoRestore: boolean().optional(),
27166
+ onUncoveredCondition: SceneUncoveredPolicySchema.optional(),
26858
27167
  /** `null` clears the vision-model adjudicator. */
26859
27168
  confirm: SceneConfirmSchema.nullable().optional()
26860
27169
  })
@@ -27153,7 +27462,70 @@ var CamStreamDescriptorSchema = object({
27153
27462
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
27154
27463
  metadata: record(string(), unknown()).optional()
27155
27464
  });
27156
- DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
27465
+ /**
27466
+ * `stream-catalog` — device-scoped, provider-implemented. The pull counterpart
27467
+ * of the removed `publishCameraStream` push: a camera provider returns the full
27468
+ * set of stream descriptors it can offer for the device, synchronously, so the
27469
+ * broker can reconcile its registry against the authoritative provider state.
27470
+ */
27471
+ /**
27472
+ * The catalog as a DURABLE fact rather than a live answer.
27473
+ *
27474
+ * A battery camera's descriptors are profile-stable — they change when the
27475
+ * operator rewrites an encoder profile, not minute to minute — but building
27476
+ * them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
27477
+ * provider is allowed to build them exactly once per profile and must serve
27478
+ * every later pull from a cache.
27479
+ *
27480
+ * Holding that cache only in RAM is what turned a restart into an outage. The
27481
+ * runner comes back with the camera asleep, `buildStreamCatalogUncached`
27482
+ * correctly refuses to wake it, the pull answers `[]`, the broker has no
27483
+ * cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
27484
+ * fails with a flat "No broker for stream" — for as long as the camera sleeps,
27485
+ * which on a battery cam is most of the day. The camera was fine. The stream
27486
+ * was unreachable because the process had forgotten what the camera offers.
27487
+ *
27488
+ * Declaring it here puts it in `device-runtime-state`, the kernel's canonical
27489
+ * declared collection, with the same `restored` durability `battery` uses for
27490
+ * the same reason: the last known value is the only value there is while the
27491
+ * device is asleep. The broker's brokers are therefore always DEFINABLE — it
27492
+ * is the DIAL that wakes a camera, never the catalog (D173).
27493
+ */
27494
+ var StreamCatalogStateSchema = object({
27495
+ /** The descriptors as last built from a real camera response. Never a guess:
27496
+ * a failed or refused build writes NOTHING, so a restored catalog is always
27497
+ * one the camera itself once produced. */
27498
+ descriptors: array(CamStreamDescriptorSchema),
27499
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
27500
+ * path decide whether the camera's own awake window is worth spending on a
27501
+ * re-read. */
27502
+ lastFetchedAt: number()
27503
+ });
27504
+ var streamCatalogCapability = {
27505
+ name: "stream-catalog",
27506
+ scope: "device",
27507
+ deviceNative: true,
27508
+ mode: "singleton",
27509
+ deviceTypes: [DeviceType.Camera],
27510
+ methods: { getCatalog: method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly()) },
27511
+ runtimeState: StreamCatalogStateSchema,
27512
+ /**
27513
+ * Runtime-state durability: **restored** — see the schema doc. A cold
27514
+ * catalog on a sleeping battery camera is not a slow first frame, it is a
27515
+ * camera that cannot be watched at all until it happens to wake.
27516
+ *
27517
+ * Churn is nil by construction: the slice is written only by a SUCCESSFUL
27518
+ * build, and a build only runs when there is no cached copy (or the copy is
27519
+ * a day old and the camera is awake anyway).
27520
+ *
27521
+ * See `RuntimeStateDurability`. Enforced by
27522
+ * `scripts/check-runtime-state-durability.ts`.
27523
+ */
27524
+ durability: "restored",
27525
+ /** Clock field: written, but excluded from the compare that decides whether
27526
+ * persisting is worth a SQLite commit — the descriptors are the value. */
27527
+ volatileStateFields: ["lastFetchedAt"]
27528
+ };
27157
27529
  /** One of the camera's stream profiles. */
27158
27530
  var StreamProfileSchema = _enum([
27159
27531
  "main",
@@ -28803,6 +29175,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
28803
29175
  sceneMonitor: sceneMonitorCapability,
28804
29176
  scriptRunner: scriptRunnerCapability,
28805
29177
  smoke: smokeCapability,
29178
+ streamCatalog: streamCatalogCapability,
28806
29179
  streamParams: streamParamsCapability,
28807
29180
  switch: switchCapability,
28808
29181
  tamper: tamperCapability,
@@ -29456,6 +29829,15 @@ var BaseDeviceProvider = class extends BaseAddon {
29456
29829
  labels: ["probe not implemented"]
29457
29830
  };
29458
29831
  }
29832
+ /**
29833
+ * Top-level devices restored at once in {@link onRestoreDevices}.
29834
+ *
29835
+ * Four covers the fleets this ships to without turning a boot into a burst a
29836
+ * camera NVR answers with a refusal. A provider whose upstream is a single
29837
+ * session with a serial command channel (a Baichuan hub, an NVR that
29838
+ * serialises ISAPI) should lower it; nothing needs to raise it.
29839
+ */
29840
+ restoreConcurrency = 4;
29459
29841
  async restoreDevices(savedDevices) {
29460
29842
  await this.onRestoreDevices(savedDevices);
29461
29843
  if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
@@ -29487,15 +29869,15 @@ var BaseDeviceProvider = class extends BaseAddon {
29487
29869
  */
29488
29870
  async onRestoreDevices(savedDevices) {
29489
29871
  const restored = /* @__PURE__ */ new Set();
29490
- for (const saved of savedDevices) {
29491
- if (saved.parentDeviceId !== null) continue;
29872
+ const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
29873
+ const restoreOne = async (saved) => {
29492
29874
  const Class = this.deviceClasses[saved.type];
29493
29875
  if (!Class) {
29494
29876
  this.ctx.logger.warn("No device class registered for restored type — skipping", {
29495
29877
  tags: { stableId: saved.stableId },
29496
29878
  meta: { type: saved.type }
29497
29879
  });
29498
- continue;
29880
+ return;
29499
29881
  }
29500
29882
  try {
29501
29883
  await this.ctx.kernel.devices.create(saved.stableId, Class, {});
@@ -29509,7 +29891,15 @@ var BaseDeviceProvider = class extends BaseAddon {
29509
29891
  }
29510
29892
  });
29511
29893
  }
29512
- }
29894
+ };
29895
+ let nextTopLevel = 0;
29896
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
29897
+ for (;;) {
29898
+ const saved = topLevel[nextTopLevel++];
29899
+ if (saved === void 0) return;
29900
+ await restoreOne(saved);
29901
+ }
29902
+ }));
29513
29903
  const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
29514
29904
  for (const saved of childRows) {
29515
29905
  const Class = this.deviceClasses[saved.type];
@@ -31690,6 +32080,12 @@ Object.freeze({
31690
32080
  addonId: null,
31691
32081
  access: "view"
31692
32082
  },
32083
+ "llm.resolveModelRef": {
32084
+ capName: "llm",
32085
+ capScope: "system",
32086
+ addonId: null,
32087
+ access: "create"
32088
+ },
31693
32089
  "llm.setDefault": {
31694
32090
  capName: "llm",
31695
32091
  capScope: "system",
@@ -34000,6 +34396,12 @@ Object.freeze({
34000
34396
  addonId: null,
34001
34397
  access: "view"
34002
34398
  },
34399
+ "snapshot.getDebugState": {
34400
+ capName: "snapshot",
34401
+ capScope: "device",
34402
+ addonId: null,
34403
+ access: "view"
34404
+ },
34003
34405
  "snapshot.getSnapshot": {
34004
34406
  capName: "snapshot",
34005
34407
  capScope: "device",
@@ -36082,6 +36484,11 @@ Object.freeze({
36082
36484
  form: "single",
36083
36485
  optional: false
36084
36486
  }],
36487
+ "pipelineAnalytics.getEventMedia": [{
36488
+ name: "deviceId",
36489
+ form: "single",
36490
+ optional: false
36491
+ }],
36085
36492
  "pipelineAnalytics.getKeyEvents": [{
36086
36493
  name: "deviceId",
36087
36494
  form: "single",
@@ -36112,6 +36519,11 @@ Object.freeze({
36112
36519
  form: "single",
36113
36520
  optional: false
36114
36521
  }],
36522
+ "pipelineAnalytics.getTrackMedia": [{
36523
+ name: "deviceId",
36524
+ form: "single",
36525
+ optional: false
36526
+ }],
36115
36527
  "pipelineAnalytics.getTrainingExportSummary": [{
36116
36528
  name: "deviceIds",
36117
36529
  form: "array",
@@ -36147,6 +36559,11 @@ Object.freeze({
36147
36559
  form: "array",
36148
36560
  optional: true
36149
36561
  }],
36562
+ "pipelineAnalytics.listTrackMedia": [{
36563
+ name: "deviceId",
36564
+ form: "single",
36565
+ optional: false
36566
+ }],
36150
36567
  "pipelineAnalytics.listTracks": [{
36151
36568
  name: "deviceId",
36152
36569
  form: "single",
@@ -36582,11 +36999,22 @@ Object.freeze({
36582
36999
  form: "single",
36583
37000
  optional: false
36584
37001
  }],
37002
+ "snapshot.getDebugState": [{
37003
+ name: "deviceId",
37004
+ form: "single",
37005
+ optional: false
37006
+ }],
36585
37007
  "snapshot.getSnapshot": [{
36586
37008
  name: "deviceId",
36587
37009
  form: "single",
36588
37010
  optional: false
36589
37011
  }],
37012
+ "snapshot.getSnapshotLinks": [{
37013
+ name: "targets",
37014
+ form: "object-array",
37015
+ optional: false,
37016
+ itemField: "deviceId"
37017
+ }],
36590
37018
  "snapshot.getSnapshotOverview": [{
36591
37019
  name: "deviceIds",
36592
37020
  form: "array",