@camstack/addon-provider-homeassistant 1.2.30 → 1.2.31

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
@@ -19514,7 +19725,16 @@ targets: array(object({
19514
19725
  /** A sleeping battery camera: the frame is deliberately stale and will
19515
19726
  * NOT refresh in the background. A surface should say so rather than
19516
19727
  * present it as current. */
19517
- sleeping: boolean()
19728
+ sleeping: boolean(),
19729
+ /** Current device state rendered over the cached frame. State images
19730
+ * remain authoritative even when their photographic background is
19731
+ * old; null means the link must carry a current camera frame. */
19732
+ stateReason: _enum([
19733
+ "disabled",
19734
+ "sleeping",
19735
+ "unreachable",
19736
+ "waking"
19737
+ ]).nullable()
19518
19738
  })));
19519
19739
  /**
19520
19740
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -21240,6 +21460,25 @@ var BatteryStatusSchema = object({
21240
21460
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
21241
21461
  lastUpdated: number(),
21242
21462
  /**
21463
+ * Ms epoch of the last time the device PROVED it was reachable — a
21464
+ * completed firmware round-trip, an observed wake, or an inbound push
21465
+ * (firmware event, email). `0`/absent = never since this slice was born.
21466
+ *
21467
+ * This is the ONLY input that separates "asleep" from "gone", and it is
21468
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
21469
+ * for the radio, because a poll that confirms reachability is the same
21470
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
21471
+ * single derivation every consumer must use; no surface computes its own.
21472
+ *
21473
+ * It is deliberately NOT a clock in the
21474
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
21475
+ * observation itself, and it is the only thing a 30-hour silence is
21476
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
21477
+ * Reolink provider) so a value that means "recently" cannot cost a
21478
+ * SQLite commit per round-trip.
21479
+ */
21480
+ lastContactAt: number().optional(),
21481
+ /**
21243
21482
  * True when the source is a BINARY low-battery indicator (HA
21244
21483
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
21245
21484
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -26681,6 +26920,33 @@ method(object({
26681
26920
  * as `unknown`, never guessed. A day reference scored against an IR frame
26682
26921
  * collapses the cosine and would latch a false alarm every single night. */
26683
26922
  var SceneConditionSchema = string();
26923
+ /**
26924
+ * What a scene does when the CURRENT light has no reference of its own.
26925
+ *
26926
+ * The lighting variants are not equally likely to exist. Almost every operator
26927
+ * captures daylight and then never stands outside at 22:00 to capture IR, and a
26928
+ * scene that is only ever going to be asked about a daytime question ("is the
26929
+ * bin still on the kerb at 08:00") does not need a night reference at all. The
26930
+ * night half must therefore be OPTIONAL, and optional means the scene keeps
26931
+ * working without it rather than degrading into a permanent complaint.
26932
+ *
26933
+ * - `skip` (default) — the check in that light is not made. Not a verdict, not
26934
+ * an alarm, not even an `unknown`: the live state simply stays whatever the
26935
+ * last covered light left it at, the latch is untouched, and the hysteresis
26936
+ * run is neither spent nor cleared. The scene resumes by itself at first
26937
+ * light. This is the only behaviour under which "I never captured IR" is a
26938
+ * configuration choice instead of a nightly fault.
26939
+ * - `judge-anyway` — score against the OTHER conditions' references. Available
26940
+ * for cameras whose IR frame is close enough to daylight (a floodlit
26941
+ * driveway, an always-white-light doorbell), and wrong for everything else:
26942
+ * cross-condition cosines are not comparable, so a day reference against a
26943
+ * true IR frame collapses and the scene reports a theft at 21:40.
26944
+ *
26945
+ * Never applies when the scene has NO comparable reference at all — that is
26946
+ * "not armed yet", it is reported as `no-reference-for-condition`, and silence
26947
+ * there would hide a scene the operator never finished setting up.
26948
+ */
26949
+ var SceneUncoveredPolicySchema = _enum(["skip", "judge-anyway"]);
26684
26950
  /** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
26685
26951
  * `unknown` = we cannot judge (no reference for this condition, encoder model
26686
26952
  * changed, view shifted, no snapshot). `unknown` is a real value, not a null,
@@ -26736,6 +27002,9 @@ var SceneCheckSchema = discriminatedUnion("mode", [object({
26736
27002
  hysteresisCount: number().int().positive()
26737
27003
  })]);
26738
27004
  var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
27005
+ /** Night is OPTIONAL. A scene with only a daylight reference sits the IR hours
27006
+ * out in silence rather than reporting a fault every night. */
27007
+ var SCENE_DEFAULT_UNCOVERED_POLICY = "skip";
26739
27008
  /**
26740
27009
  * Vision-model adjudication of a candidate flip. Field names deliberately
26741
27010
  * mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
@@ -26802,6 +27071,21 @@ var SceneMonitorSchema = object({
26802
27071
  * automation can react to the bin coming back without the operator's own
26803
27072
  * alarm silently clearing itself. */
26804
27073
  autoRestore: boolean().default(false),
27074
+ /** What to do when the current light has no reference of its own. See
27075
+ * {@link SceneUncoveredPolicySchema} — the default makes night OPTIONAL. */
27076
+ onUncoveredCondition: SceneUncoveredPolicySchema.default(SCENE_DEFAULT_UNCOVERED_POLICY),
27077
+ /**
27078
+ * The light whose checks are currently being SAT OUT under
27079
+ * `onUncoveredCondition: 'skip'` — `null` when the scene is checking normally.
27080
+ *
27081
+ * Engine-reported and advisory only: it moves no verdict, no latch and no
27082
+ * hysteresis. It exists so the card can say *"night (IR) — checks paused,
27083
+ * nothing captured in this light"* in the same calm voice as the coverage
27084
+ * line, because the alternative is a scene that silently stops answering
27085
+ * after sunset with nothing anywhere saying why. A skipped check must never
27086
+ * read as a broken one.
27087
+ */
27088
+ suspendedCondition: SceneConditionSchema.nullable().default(null),
26805
27089
  /** Named cause when `verdict === 'unknown'`. */
26806
27090
  unavailable: SceneUnavailableSchema.nullable(),
26807
27091
  /** Conditions that have at least one comparable reference — the coverage line
@@ -26855,6 +27139,7 @@ var sceneMonitorCapability = {
26855
27139
  minObservationSpacingSec: number().int().min(0).max(3600).optional(),
26856
27140
  anchorThreshold: number().min(0).max(1).optional(),
26857
27141
  autoRestore: boolean().optional(),
27142
+ onUncoveredCondition: SceneUncoveredPolicySchema.optional(),
26858
27143
  /** `null` clears the vision-model adjudicator. */
26859
27144
  confirm: SceneConfirmSchema.nullable().optional()
26860
27145
  })
@@ -27153,7 +27438,70 @@ var CamStreamDescriptorSchema = object({
27153
27438
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
27154
27439
  metadata: record(string(), unknown()).optional()
27155
27440
  });
27156
- DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
27441
+ /**
27442
+ * `stream-catalog` — device-scoped, provider-implemented. The pull counterpart
27443
+ * of the removed `publishCameraStream` push: a camera provider returns the full
27444
+ * set of stream descriptors it can offer for the device, synchronously, so the
27445
+ * broker can reconcile its registry against the authoritative provider state.
27446
+ */
27447
+ /**
27448
+ * The catalog as a DURABLE fact rather than a live answer.
27449
+ *
27450
+ * A battery camera's descriptors are profile-stable — they change when the
27451
+ * operator rewrites an encoder profile, not minute to minute — but building
27452
+ * them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
27453
+ * provider is allowed to build them exactly once per profile and must serve
27454
+ * every later pull from a cache.
27455
+ *
27456
+ * Holding that cache only in RAM is what turned a restart into an outage. The
27457
+ * runner comes back with the camera asleep, `buildStreamCatalogUncached`
27458
+ * correctly refuses to wake it, the pull answers `[]`, the broker has no
27459
+ * cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
27460
+ * fails with a flat "No broker for stream" — for as long as the camera sleeps,
27461
+ * which on a battery cam is most of the day. The camera was fine. The stream
27462
+ * was unreachable because the process had forgotten what the camera offers.
27463
+ *
27464
+ * Declaring it here puts it in `device-runtime-state`, the kernel's canonical
27465
+ * declared collection, with the same `restored` durability `battery` uses for
27466
+ * the same reason: the last known value is the only value there is while the
27467
+ * device is asleep. The broker's brokers are therefore always DEFINABLE — it
27468
+ * is the DIAL that wakes a camera, never the catalog (D173).
27469
+ */
27470
+ var StreamCatalogStateSchema = object({
27471
+ /** The descriptors as last built from a real camera response. Never a guess:
27472
+ * a failed or refused build writes NOTHING, so a restored catalog is always
27473
+ * one the camera itself once produced. */
27474
+ descriptors: array(CamStreamDescriptorSchema),
27475
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
27476
+ * path decide whether the camera's own awake window is worth spending on a
27477
+ * re-read. */
27478
+ lastFetchedAt: number()
27479
+ });
27480
+ var streamCatalogCapability = {
27481
+ name: "stream-catalog",
27482
+ scope: "device",
27483
+ deviceNative: true,
27484
+ mode: "singleton",
27485
+ deviceTypes: [DeviceType.Camera],
27486
+ methods: { getCatalog: method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly()) },
27487
+ runtimeState: StreamCatalogStateSchema,
27488
+ /**
27489
+ * Runtime-state durability: **restored** — see the schema doc. A cold
27490
+ * catalog on a sleeping battery camera is not a slow first frame, it is a
27491
+ * camera that cannot be watched at all until it happens to wake.
27492
+ *
27493
+ * Churn is nil by construction: the slice is written only by a SUCCESSFUL
27494
+ * build, and a build only runs when there is no cached copy (or the copy is
27495
+ * a day old and the camera is awake anyway).
27496
+ *
27497
+ * See `RuntimeStateDurability`. Enforced by
27498
+ * `scripts/check-runtime-state-durability.ts`.
27499
+ */
27500
+ durability: "restored",
27501
+ /** Clock field: written, but excluded from the compare that decides whether
27502
+ * persisting is worth a SQLite commit — the descriptors are the value. */
27503
+ volatileStateFields: ["lastFetchedAt"]
27504
+ };
27157
27505
  /** One of the camera's stream profiles. */
27158
27506
  var StreamProfileSchema = _enum([
27159
27507
  "main",
@@ -28803,6 +29151,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
28803
29151
  sceneMonitor: sceneMonitorCapability,
28804
29152
  scriptRunner: scriptRunnerCapability,
28805
29153
  smoke: smokeCapability,
29154
+ streamCatalog: streamCatalogCapability,
28806
29155
  streamParams: streamParamsCapability,
28807
29156
  switch: switchCapability,
28808
29157
  tamper: tamperCapability,
@@ -29456,6 +29805,15 @@ var BaseDeviceProvider = class extends BaseAddon {
29456
29805
  labels: ["probe not implemented"]
29457
29806
  };
29458
29807
  }
29808
+ /**
29809
+ * Top-level devices restored at once in {@link onRestoreDevices}.
29810
+ *
29811
+ * Four covers the fleets this ships to without turning a boot into a burst a
29812
+ * camera NVR answers with a refusal. A provider whose upstream is a single
29813
+ * session with a serial command channel (a Baichuan hub, an NVR that
29814
+ * serialises ISAPI) should lower it; nothing needs to raise it.
29815
+ */
29816
+ restoreConcurrency = 4;
29459
29817
  async restoreDevices(savedDevices) {
29460
29818
  await this.onRestoreDevices(savedDevices);
29461
29819
  if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
@@ -29487,15 +29845,15 @@ var BaseDeviceProvider = class extends BaseAddon {
29487
29845
  */
29488
29846
  async onRestoreDevices(savedDevices) {
29489
29847
  const restored = /* @__PURE__ */ new Set();
29490
- for (const saved of savedDevices) {
29491
- if (saved.parentDeviceId !== null) continue;
29848
+ const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
29849
+ const restoreOne = async (saved) => {
29492
29850
  const Class = this.deviceClasses[saved.type];
29493
29851
  if (!Class) {
29494
29852
  this.ctx.logger.warn("No device class registered for restored type — skipping", {
29495
29853
  tags: { stableId: saved.stableId },
29496
29854
  meta: { type: saved.type }
29497
29855
  });
29498
- continue;
29856
+ return;
29499
29857
  }
29500
29858
  try {
29501
29859
  await this.ctx.kernel.devices.create(saved.stableId, Class, {});
@@ -29509,7 +29867,15 @@ var BaseDeviceProvider = class extends BaseAddon {
29509
29867
  }
29510
29868
  });
29511
29869
  }
29512
- }
29870
+ };
29871
+ let nextTopLevel = 0;
29872
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
29873
+ for (;;) {
29874
+ const saved = topLevel[nextTopLevel++];
29875
+ if (saved === void 0) return;
29876
+ await restoreOne(saved);
29877
+ }
29878
+ }));
29513
29879
  const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
29514
29880
  for (const saved of childRows) {
29515
29881
  const Class = this.deviceClasses[saved.type];
@@ -31690,6 +32056,12 @@ Object.freeze({
31690
32056
  addonId: null,
31691
32057
  access: "view"
31692
32058
  },
32059
+ "llm.resolveModelRef": {
32060
+ capName: "llm",
32061
+ capScope: "system",
32062
+ addonId: null,
32063
+ access: "create"
32064
+ },
31693
32065
  "llm.setDefault": {
31694
32066
  capName: "llm",
31695
32067
  capScope: "system",
@@ -36082,6 +36454,11 @@ Object.freeze({
36082
36454
  form: "single",
36083
36455
  optional: false
36084
36456
  }],
36457
+ "pipelineAnalytics.getEventMedia": [{
36458
+ name: "deviceId",
36459
+ form: "single",
36460
+ optional: false
36461
+ }],
36085
36462
  "pipelineAnalytics.getKeyEvents": [{
36086
36463
  name: "deviceId",
36087
36464
  form: "single",
@@ -36112,6 +36489,11 @@ Object.freeze({
36112
36489
  form: "single",
36113
36490
  optional: false
36114
36491
  }],
36492
+ "pipelineAnalytics.getTrackMedia": [{
36493
+ name: "deviceId",
36494
+ form: "single",
36495
+ optional: false
36496
+ }],
36115
36497
  "pipelineAnalytics.getTrainingExportSummary": [{
36116
36498
  name: "deviceIds",
36117
36499
  form: "array",
@@ -36147,6 +36529,11 @@ Object.freeze({
36147
36529
  form: "array",
36148
36530
  optional: true
36149
36531
  }],
36532
+ "pipelineAnalytics.listTrackMedia": [{
36533
+ name: "deviceId",
36534
+ form: "single",
36535
+ optional: false
36536
+ }],
36150
36537
  "pipelineAnalytics.listTracks": [{
36151
36538
  name: "deviceId",
36152
36539
  form: "single",
@@ -36587,6 +36974,12 @@ Object.freeze({
36587
36974
  form: "single",
36588
36975
  optional: false
36589
36976
  }],
36977
+ "snapshot.getSnapshotLinks": [{
36978
+ name: "targets",
36979
+ form: "object-array",
36980
+ optional: false,
36981
+ itemField: "deviceId"
36982
+ }],
36590
36983
  "snapshot.getSnapshotOverview": [{
36591
36984
  name: "deviceIds",
36592
36985
  form: "array",