@camstack/types 1.2.75 → 1.2.77

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 (48) hide show
  1. package/dist/addon.d.ts +19 -18
  2. package/dist/addon.js +59 -56
  3. package/dist/addon.mjs +59 -57
  4. package/dist/capabilities/alerts.cap.d.ts +5 -5
  5. package/dist/capabilities/battery.cap.d.ts +8 -4
  6. package/dist/capabilities/custom-model-registry.cap.d.ts +4 -4
  7. package/dist/capabilities/decoder.cap.d.ts +2 -2
  8. package/dist/capabilities/face-gallery.cap.d.ts +1 -1
  9. package/dist/capabilities/index.d.ts +3 -3
  10. package/dist/capabilities/lawn-mower-control.cap.d.ts +4 -4
  11. package/dist/capabilities/llm-runtime.cap.d.ts +121 -3
  12. package/dist/capabilities/llm.cap.d.ts +189 -4
  13. package/dist/capabilities/metrics-provider.cap.d.ts +2 -2
  14. package/dist/capabilities/model-convert.cap.d.ts +6 -6
  15. package/dist/capabilities/model-distributor.cap.d.ts +4 -4
  16. package/dist/capabilities/notification-output.cap.d.ts +9 -9
  17. package/dist/capabilities/notification-rules.cap.d.ts +11 -11
  18. package/dist/capabilities/oauth-integration.cap.d.ts +2 -2
  19. package/dist/capabilities/osd-manager.cap.d.ts +6 -6
  20. package/dist/capabilities/pipeline-analytics.cap.d.ts +39 -39
  21. package/dist/capabilities/pipeline-executor.cap.d.ts +7 -7
  22. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +6 -6
  23. package/dist/capabilities/pipeline-runner.cap.d.ts +2 -0
  24. package/dist/capabilities/platform-probe.cap.d.ts +2 -2
  25. package/dist/capabilities/recording.cap.d.ts +3 -3
  26. package/dist/capabilities/sso-bridge.cap.d.ts +3 -3
  27. package/dist/capabilities/stream-broker.cap.d.ts +6 -6
  28. package/dist/capabilities/stream-catalog.cap.d.ts +93 -0
  29. package/dist/capabilities/user-management.cap.d.ts +20 -20
  30. package/dist/device/base-device-provider.d.ts +9 -0
  31. package/dist/device/battery-presence.d.ts +84 -0
  32. package/dist/encode-profile.d.ts +2 -2
  33. package/dist/generated/addon-api.d.ts +15 -8
  34. package/dist/generated/device-local-state.d.ts +3 -0
  35. package/dist/generated/device-proxy.d.ts +1 -0
  36. package/dist/generated/method-access-map.d.ts +1 -1
  37. package/dist/generated/runtime-state-durability.d.ts +1 -1
  38. package/dist/generated/system-proxy.d.ts +1 -1
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.js +309 -9
  41. package/dist/index.mjs +303 -10
  42. package/dist/interfaces/recording-config.d.ts +2 -2
  43. package/dist/interfaces/stream-broker.d.ts +9 -1
  44. package/dist/{sleep-B-mRsuDp.js → sleep-CoL-9qhR.js} +43 -0
  45. package/dist/{sleep-CQayd2Su.mjs → sleep-DlC2kJYf.mjs} +38 -1
  46. package/dist/types/models.d.ts +7 -7
  47. package/dist/util/boot-recovery-backoff.d.ts +26 -0
  48. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_event_category = require("./event-category-D3gG7oil.js");
3
- const require_sleep = require("./sleep-B-mRsuDp.js");
3
+ const require_sleep = require("./sleep-CoL-9qhR.js");
4
4
  const require_canonical_hash = require("./canonical-hash-DNV8S5ET.js");
5
5
  const require_enums = require("./enums.js");
6
6
  const require_err_msg = require("./err-msg-COpsHMw2.js");
@@ -9517,6 +9517,18 @@ var LlmGenerateBaseInputSchema = zod.z.object({
9517
9517
  * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
9518
9518
  * watchdog — operator decision #3).
9519
9519
  */
9520
+ /**
9521
+ * A companion artifact that MUST land beside the main GGUF: the `mmproj`
9522
+ * projector of a vision model, or shards 2..N of a split GGUF. Carried on the
9523
+ * REF rather than looked up at install time, so what the operator approved in
9524
+ * the preview is exactly what the node downloads.
9525
+ */
9526
+ var ManagedModelExtraFileSchema = zod.z.object({
9527
+ url: zod.z.string(),
9528
+ filename: zod.z.string(),
9529
+ sizeBytes: zod.z.number(),
9530
+ sha256: zod.z.string().optional()
9531
+ });
9520
9532
  var ManagedModelRefSchema = zod.z.discriminatedUnion("kind", [
9521
9533
  zod.z.object({
9522
9534
  kind: zod.z.literal("catalog"),
@@ -9525,7 +9537,11 @@ var ManagedModelRefSchema = zod.z.discriminatedUnion("kind", [
9525
9537
  zod.z.object({
9526
9538
  kind: zod.z.literal("url"),
9527
9539
  url: zod.z.string(),
9528
- sha256: zod.z.string().optional()
9540
+ sha256: zod.z.string().optional(),
9541
+ /** Picker/status label; the file basename when absent. */
9542
+ label: zod.z.string().optional(),
9543
+ sizeBytes: zod.z.number().optional(),
9544
+ extraFiles: zod.z.array(ManagedModelExtraFileSchema).optional()
9529
9545
  }),
9530
9546
  zod.z.object({
9531
9547
  kind: zod.z.literal("path"),
@@ -9586,11 +9602,39 @@ var ManagedRuntimeConfigSchema = zod.z.object({
9586
9602
  "q4_1",
9587
9603
  "q4_0"
9588
9604
  ]).optional(),
9605
+ /**
9606
+ * Escape hatch for llama-server flags this schema does NOT model — `--jinja`
9607
+ * (which most vision chat templates need and some language-only models
9608
+ * dislike), `--cont-batching`, `--rope-scaling`, …
9609
+ *
9610
+ * It is NOT a second place to set the flags above. A token that collides
9611
+ * with a typed field is REJECTED at start, naming the field that owns it
9612
+ * (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
9613
+ * the "two switches that disagree" failure this repo has already shipped
9614
+ * twice (D62).
9615
+ */
9616
+ extraArgs: zod.z.array(zod.z.string()).default([]),
9589
9617
  /** Else lazy: first generate boots it. */
9590
9618
  autoStart: zod.z.boolean().default(false),
9591
9619
  /** 0 = never; frees RAM after quiet periods. */
9592
9620
  idleStopMinutes: zod.z.number().int().default(30)
9593
9621
  });
9622
+ /**
9623
+ * Where a multi-GB install currently is. A single 0..1 fraction cannot answer
9624
+ * "is it stuck?" for an install that is three files (shards + mmproj) followed
9625
+ * by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
9626
+ * node looked hung. Phase + file + bytes is the smallest shape that does.
9627
+ */
9628
+ var LlmDownloadProgressSchema = zod.z.object({
9629
+ phase: zod.z.enum(["downloading", "verifying"]),
9630
+ /** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
9631
+ file: zod.z.string(),
9632
+ fileIndex: zod.z.number().int(),
9633
+ fileCount: zod.z.number().int(),
9634
+ /** Across the WHOLE install, not the current file. */
9635
+ downloadedBytes: zod.z.number(),
9636
+ totalBytes: zod.z.number().optional()
9637
+ });
9594
9638
  var LlmRuntimeStatusSchema = zod.z.object({
9595
9639
  /** Status is ALWAYS node-qualified. */
9596
9640
  nodeId: zod.z.string(),
@@ -9607,6 +9651,8 @@ var LlmRuntimeStatusSchema = zod.z.object({
9607
9651
  modelPath: zod.z.string().optional(),
9608
9652
  modelId: zod.z.string().optional(),
9609
9653
  downloadProgress: zod.z.number().min(0).max(1).optional(),
9654
+ /** Detail behind `downloadProgress`; present for the same lifetime. */
9655
+ download: LlmDownloadProgressSchema.optional(),
9610
9656
  lastError: zod.z.string().optional(),
9611
9657
  crashesInWindow: zod.z.number(),
9612
9658
  /** Child RSS (sampled best-effort). */
@@ -9617,7 +9663,14 @@ var LlmNodeModelSchema = zod.z.object({
9617
9663
  file: zod.z.string(),
9618
9664
  sizeBytes: zod.z.number(),
9619
9665
  catalogId: zod.z.string().optional(),
9620
- installedAt: zod.z.number().optional()
9666
+ installedAt: zod.z.number().optional(),
9667
+ /**
9668
+ * Absolute path on the node. Present so a file that is on disk but matches
9669
+ * no catalog entry — a custom Hugging Face install, or a GGUF the operator
9670
+ * copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
9671
+ * it the picker could list such a file and do nothing with it.
9672
+ */
9673
+ path: zod.z.string().optional()
9621
9674
  });
9622
9675
  var LlmRuntimeDiskUsageSchema = zod.z.object({
9623
9676
  nodeId: zod.z.string(),
@@ -9781,6 +9834,36 @@ var ManagedModelCatalogEntrySchema = zod.z.object({
9781
9834
  /** Vision models: companion projector file. */
9782
9835
  mmprojUrl: zod.z.string().optional()
9783
9836
  });
9837
+ /**
9838
+ * The outcome of turning one operator-typed Hugging Face reference into a
9839
+ * download plan. A RESULT, never a throw: "this repo has 24 quantizations and
9840
+ * I will not pick for you" is a normal answer the UI has to render, not an
9841
+ * exception.
9842
+ *
9843
+ * `candidates` is the whole reason the refusal is usable — every string in it
9844
+ * is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
9845
+ */
9846
+ var HfModelResolutionSchema = zod.z.discriminatedUnion("ok", [zod.z.object({
9847
+ ok: zod.z.literal(true),
9848
+ /** Ready to hand to `installModel` unchanged. */
9849
+ model: ManagedModelRefSchema,
9850
+ label: zod.z.string(),
9851
+ repo: zod.z.string(),
9852
+ quantization: zod.z.string(),
9853
+ purpose: zod.z.enum(["text", "vision"]),
9854
+ totalBytes: zod.z.number(),
9855
+ /** mmproj + shards, for the preview: an operator approving 23 GB should
9856
+ * see that 0.9 GB of it is a projector they did not name. */
9857
+ extraFilenames: zod.z.array(zod.z.string())
9858
+ }), zod.z.object({
9859
+ ok: zod.z.literal(false),
9860
+ code: zod.z.string(),
9861
+ message: zod.z.string(),
9862
+ candidates: zod.z.array(zod.z.string()).optional(),
9863
+ /** Set when the refusal was only the ceiling: re-calling with
9864
+ * `maxBytes: requiredBytes` is the operator's explicit override. */
9865
+ requiredBytes: zod.z.number().optional()
9866
+ })]);
9784
9867
  var LlmRuntimeNodeSchema = zod.z.object({
9785
9868
  nodeId: zod.z.string(),
9786
9869
  reachable: zod.z.boolean(),
@@ -9848,6 +9931,25 @@ var llmCapability = {
9848
9931
  listModelCatalog: require_sleep.method(zod.z.object({}), zod.z.array(ManagedModelCatalogEntrySchema)),
9849
9932
  listRuntimeNodes: require_sleep.method(zod.z.object({}), zod.z.array(LlmRuntimeNodeSchema)),
9850
9933
  listNodeModels: require_sleep.method(zod.z.object({ nodeId: zod.z.string() }), zod.z.array(LlmNodeModelSchema)),
9934
+ /**
9935
+ * One typed Hugging Face reference → a pinned, verified `ManagedModelRef`.
9936
+ *
9937
+ * Runs on the HUB, not on the target node: resolution needs egress to
9938
+ * huggingface.co, and an agent that cannot reach it still installs fine
9939
+ * through the model-distributor relay. Nothing is downloaded here — this is
9940
+ * a tree read plus a HEAD, so the operator sees the size, the quantization
9941
+ * and the mmproj BEFORE approving a multi-GB pull.
9942
+ */
9943
+ resolveModelRef: require_sleep.method(zod.z.object({
9944
+ /** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
9945
+ * `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
9946
+ ref: zod.z.string(),
9947
+ /** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
9948
+ maxBytes: zod.z.number().positive().optional()
9949
+ }), HfModelResolutionSchema, {
9950
+ kind: "mutation",
9951
+ auth: "admin"
9952
+ }),
9851
9953
  installModel: require_sleep.method(zod.z.object({
9852
9954
  nodeId: zod.z.string(),
9853
9955
  model: ManagedModelRefSchema
@@ -16846,6 +16948,17 @@ var maxSessionHoldMsField = {
16846
16948
  default: 12e4,
16847
16949
  step: 5e3
16848
16950
  };
16951
+ /**
16952
+ * Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
16953
+ * 5s so a rearm can never degenerate into per-event stream churn; default 90s
16954
+ * comfortably outlives the gap between two PIR wakes on a battery camera.
16955
+ */
16956
+ var audioMotionWindowMsField = {
16957
+ min: 5e3,
16958
+ max: 6e5,
16959
+ default: 9e4,
16960
+ step: 5e3
16961
+ };
16849
16962
  var motionFpsField = {
16850
16963
  min: 1,
16851
16964
  max: 30,
@@ -17022,6 +17135,27 @@ var RunnerCameraConfigSchema = zod.z.object({
17022
17135
  * resolved `CameraDetectionConfig`.
17023
17136
  */
17024
17137
  maxSessionHoldMs: zod.z.number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
17138
+ /**
17139
+ * Orchestrator-side quiet period (ms) that closes an `audioMode:
17140
+ * 'on-motion'` audio window, measured from the LAST motion event.
17141
+ *
17142
+ * This exists because the falling edge cannot be relied on. Camera-native
17143
+ * providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
17144
+ * its email-push SMTP path both emit `detected: true` and never the
17145
+ * counterpart); only the frame-diff analyzer emits falls. So on an
17146
+ * onboard-only camera a window that closed only on `detected: false` never
17147
+ * closed at all, and `on-motion` silently behaved as `always-on` — on a
17148
+ * battery camera, the one failure mode the mode exists to prevent.
17149
+ *
17150
+ * Every motion event rearms this timer WITHOUT restarting the stream, so a
17151
+ * burst of re-fires costs nothing. A falling edge, when one does arrive,
17152
+ * still closes earlier via `motionCooldownMs` — whichever comes first wins.
17153
+ *
17154
+ * Not consumed by the runner: carried here so it shares the per-camera
17155
+ * device-settings surface with `motionCooldownMs`, exactly like
17156
+ * `maxSessionHoldMs`.
17157
+ */
17158
+ audioMotionWindowMs: zod.z.number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
17025
17159
  motionFps: zod.z.number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
17026
17160
  detectionFps: zod.z.number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
17027
17161
  motionStreamId: zod.z.string(),
@@ -17206,6 +17340,25 @@ var RunnerCameraDeviceUIFields = [
17206
17340
  nullable: true,
17207
17341
  nullLabel: "Default"
17208
17342
  },
17343
+ {
17344
+ key: "audioMotionWindowMs",
17345
+ type: "slider",
17346
+ label: "Audio window after motion",
17347
+ description: "How long audio keeps being analysed after the last motion event. Each new motion event extends the window without restarting the audio stream.",
17348
+ min: audioMotionWindowMsField.min,
17349
+ max: audioMotionWindowMsField.max,
17350
+ step: audioMotionWindowMsField.step,
17351
+ default: audioMotionWindowMsField.default,
17352
+ showValue: true,
17353
+ unit: "s",
17354
+ displayScale: 1e3,
17355
+ nullable: true,
17356
+ nullLabel: "Default",
17357
+ showWhen: {
17358
+ field: "audioMode",
17359
+ equals: "on-motion"
17360
+ }
17361
+ },
17209
17362
  {
17210
17363
  key: "onboardMotionDrivesAnalyzer",
17211
17364
  type: "boolean",
@@ -21210,6 +21363,25 @@ var BatteryStatusSchema = zod.z.object({
21210
21363
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
21211
21364
  lastUpdated: zod.z.number(),
21212
21365
  /**
21366
+ * Ms epoch of the last time the device PROVED it was reachable — a
21367
+ * completed firmware round-trip, an observed wake, or an inbound push
21368
+ * (firmware event, email). `0`/absent = never since this slice was born.
21369
+ *
21370
+ * This is the ONLY input that separates "asleep" from "gone", and it is
21371
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
21372
+ * for the radio, because a poll that confirms reachability is the same
21373
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
21374
+ * single derivation every consumer must use; no surface computes its own.
21375
+ *
21376
+ * It is deliberately NOT a clock in the
21377
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
21378
+ * observation itself, and it is the only thing a 30-hour silence is
21379
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
21380
+ * Reolink provider) so a value that means "recently" cannot cost a
21381
+ * SQLite commit per round-trip.
21382
+ */
21383
+ lastContactAt: zod.z.number().optional(),
21384
+ /**
21213
21385
  * True when the source is a BINARY low-battery indicator (HA
21214
21386
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
21215
21387
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -28182,13 +28354,63 @@ var CamStreamDescriptorSchema = zod.z.object({
28182
28354
  * set of stream descriptors it can offer for the device, synchronously, so the
28183
28355
  * broker can reconcile its registry against the authoritative provider state.
28184
28356
  */
28357
+ /**
28358
+ * The catalog as a DURABLE fact rather than a live answer.
28359
+ *
28360
+ * A battery camera's descriptors are profile-stable — they change when the
28361
+ * operator rewrites an encoder profile, not minute to minute — but building
28362
+ * them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
28363
+ * provider is allowed to build them exactly once per profile and must serve
28364
+ * every later pull from a cache.
28365
+ *
28366
+ * Holding that cache only in RAM is what turned a restart into an outage. The
28367
+ * runner comes back with the camera asleep, `buildStreamCatalogUncached`
28368
+ * correctly refuses to wake it, the pull answers `[]`, the broker has no
28369
+ * cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
28370
+ * fails with a flat "No broker for stream" — for as long as the camera sleeps,
28371
+ * which on a battery cam is most of the day. The camera was fine. The stream
28372
+ * was unreachable because the process had forgotten what the camera offers.
28373
+ *
28374
+ * Declaring it here puts it in `device-runtime-state`, the kernel's canonical
28375
+ * declared collection, with the same `restored` durability `battery` uses for
28376
+ * the same reason: the last known value is the only value there is while the
28377
+ * device is asleep. The broker's brokers are therefore always DEFINABLE — it
28378
+ * is the DIAL that wakes a camera, never the catalog (D173).
28379
+ */
28380
+ var StreamCatalogStateSchema = zod.z.object({
28381
+ /** The descriptors as last built from a real camera response. Never a guess:
28382
+ * a failed or refused build writes NOTHING, so a restored catalog is always
28383
+ * one the camera itself once produced. */
28384
+ descriptors: zod.z.array(CamStreamDescriptorSchema),
28385
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
28386
+ * path decide whether the camera's own awake window is worth spending on a
28387
+ * re-read. */
28388
+ lastFetchedAt: zod.z.number()
28389
+ });
28185
28390
  var streamCatalogCapability = {
28186
28391
  name: "stream-catalog",
28187
28392
  scope: "device",
28188
28393
  deviceNative: true,
28189
28394
  mode: "singleton",
28190
28395
  deviceTypes: [require_sleep.DeviceType.Camera],
28191
- methods: { getCatalog: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.array(CamStreamDescriptorSchema).readonly()) }
28396
+ methods: { getCatalog: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.array(CamStreamDescriptorSchema).readonly()) },
28397
+ runtimeState: StreamCatalogStateSchema,
28398
+ /**
28399
+ * Runtime-state durability: **restored** — see the schema doc. A cold
28400
+ * catalog on a sleeping battery camera is not a slow first frame, it is a
28401
+ * camera that cannot be watched at all until it happens to wake.
28402
+ *
28403
+ * Churn is nil by construction: the slice is written only by a SUCCESSFUL
28404
+ * build, and a build only runs when there is no cached copy (or the copy is
28405
+ * a day old and the camera is awake anyway).
28406
+ *
28407
+ * See `RuntimeStateDurability`. Enforced by
28408
+ * `scripts/check-runtime-state-durability.ts`.
28409
+ */
28410
+ durability: "restored",
28411
+ /** Clock field: written, but excluded from the compare that decides whether
28412
+ * persisting is worth a SQLite commit — the descriptors are the value. */
28413
+ volatileStateFields: ["lastFetchedAt"]
28192
28414
  };
28193
28415
  //#endregion
28194
28416
  //#region src/capabilities/stream-params.cap.ts
@@ -30419,6 +30641,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
30419
30641
  sceneMonitor: sceneMonitorCapability,
30420
30642
  scriptRunner: scriptRunnerCapability,
30421
30643
  smoke: smokeCapability,
30644
+ streamCatalog: streamCatalogCapability,
30422
30645
  streamParams: streamParamsCapability,
30423
30646
  switch: switchCapability,
30424
30647
  tamper: tamperCapability,
@@ -31076,6 +31299,15 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
31076
31299
  labels: ["probe not implemented"]
31077
31300
  };
31078
31301
  }
31302
+ /**
31303
+ * Top-level devices restored at once in {@link onRestoreDevices}.
31304
+ *
31305
+ * Four covers the fleets this ships to without turning a boot into a burst a
31306
+ * camera NVR answers with a refusal. A provider whose upstream is a single
31307
+ * session with a serial command channel (a Baichuan hub, an NVR that
31308
+ * serialises ISAPI) should lower it; nothing needs to raise it.
31309
+ */
31310
+ restoreConcurrency = 4;
31079
31311
  async restoreDevices(savedDevices) {
31080
31312
  await this.onRestoreDevices(savedDevices);
31081
31313
  if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
@@ -31107,15 +31339,15 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
31107
31339
  */
31108
31340
  async onRestoreDevices(savedDevices) {
31109
31341
  const restored = /* @__PURE__ */ new Set();
31110
- for (const saved of savedDevices) {
31111
- if (saved.parentDeviceId !== null) continue;
31342
+ const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
31343
+ const restoreOne = async (saved) => {
31112
31344
  const Class = this.deviceClasses[saved.type];
31113
31345
  if (!Class) {
31114
31346
  this.ctx.logger.warn("No device class registered for restored type — skipping", {
31115
31347
  tags: { stableId: saved.stableId },
31116
31348
  meta: { type: saved.type }
31117
31349
  });
31118
- continue;
31350
+ return;
31119
31351
  }
31120
31352
  try {
31121
31353
  await this.ctx.kernel.devices.create(saved.stableId, Class, {});
@@ -31129,7 +31361,15 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
31129
31361
  }
31130
31362
  });
31131
31363
  }
31132
- }
31364
+ };
31365
+ let nextTopLevel = 0;
31366
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
31367
+ for (;;) {
31368
+ const saved = topLevel[nextTopLevel++];
31369
+ if (saved === void 0) return;
31370
+ await restoreOne(saved);
31371
+ }
31372
+ }));
31133
31373
  const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
31134
31374
  for (const saved of childRows) {
31135
31375
  const Class = this.deviceClasses[saved.type];
@@ -31625,6 +31865,47 @@ function resolveDeviceProfile(features) {
31625
31865
  return null;
31626
31866
  }
31627
31867
  //#endregion
31868
+ //#region src/device/battery-presence.ts
31869
+ /**
31870
+ * Default silence budget before a sleeping battery camera is called gone.
31871
+ *
31872
+ * Sized off the mechanism that produces contact, not off taste: a battery cam
31873
+ * on this deployment surfaces a firmware push (battery level, sleep/wake, or a
31874
+ * motion email) on the order of hours even with no visitors, and the snapshot
31875
+ * wrapper's own battery window is 1 hour. Six hours is therefore several
31876
+ * missed opportunities, not one — so a single quiet night does not raise a
31877
+ * fault, and a genuinely flat camera is named well inside a day.
31878
+ */
31879
+ var BATTERY_UNREACHABLE_AFTER_MS = 360 * 6e4;
31880
+ /**
31881
+ * THE derivation. One crop rectangle, one presence verdict — see D52 for why
31882
+ * this repo insists a derived value has exactly one implementation.
31883
+ *
31884
+ * `undefined` status ⇒ `sleeping`. A device with no slice has made no claim,
31885
+ * and the caller decides separately whether it is even battery-operated
31886
+ * (`DeviceFeature.BatteryOperated`); this function never answers that
31887
+ * question, only what a battery device is doing.
31888
+ */
31889
+ function deriveBatteryPresence(input) {
31890
+ const status = input.status;
31891
+ if (!status) return "sleeping";
31892
+ if (status.sleeping !== true) return "awake";
31893
+ const lastContactAt = status.lastContactAt;
31894
+ if (typeof lastContactAt !== "number" || lastContactAt <= 0) return "sleeping";
31895
+ const budget = input.unreachableAfterMs ?? 216e5;
31896
+ return input.nowMs - lastContactAt > budget ? "unreachable" : "sleeping";
31897
+ }
31898
+ /**
31899
+ * Is this presence a FAULT the operator should be told about?
31900
+ *
31901
+ * Exists so no surface has to re-decide it — the whole point of the third
31902
+ * state is that `sleeping` stops being rendered as breakage, and a caller that
31903
+ * writes `presence !== 'awake'` has silently undone that.
31904
+ */
31905
+ function isBatteryPresenceFault(presence) {
31906
+ return presence === "unreachable";
31907
+ }
31908
+ //#endregion
31628
31909
  //#region src/device/path-util.ts
31629
31910
  /** Returns true when `x` is a non-null, non-array plain object. */
31630
31911
  function isRecord(x) {
@@ -35809,6 +36090,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
35809
36090
  addonId: null,
35810
36091
  access: "view"
35811
36092
  },
36093
+ "llm.resolveModelRef": {
36094
+ capName: "llm",
36095
+ capScope: "system",
36096
+ addonId: null,
36097
+ access: "create"
36098
+ },
35812
36099
  "llm.setDefault": {
35813
36100
  capName: "llm",
35814
36101
  capScope: "system",
@@ -41386,7 +41673,7 @@ function getCapsByProviderKind(kind) {
41386
41673
  //#region src/generated/runtime-state-durability.ts
41387
41674
  /**
41388
41675
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
41389
- * `runtimeState`. 63 caps: 33 `restored`, 30 `session`.
41676
+ * `runtimeState`. 64 caps: 34 `restored`, 30 `session`.
41390
41677
  *
41391
41678
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
41392
41679
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -41592,6 +41879,10 @@ var RUNTIME_STATE_POLICY = {
41592
41879
  durability: "restored",
41593
41880
  volatileFields: ["lastChangedAt"]
41594
41881
  },
41882
+ "stream-catalog": {
41883
+ durability: "restored",
41884
+ volatileFields: ["lastFetchedAt"]
41885
+ },
41595
41886
  "stream-params": {
41596
41887
  durability: "restored",
41597
41888
  volatileFields: ["lastFetchedAt"]
@@ -41692,6 +41983,7 @@ var RESTORED_CAP_NAMES = [
41692
41983
  "pressure-sensor",
41693
41984
  "privacy-mask",
41694
41985
  "smoke",
41986
+ "stream-catalog",
41695
41987
  "stream-params",
41696
41988
  "switch",
41697
41989
  "tamper",
@@ -42052,6 +42344,7 @@ function createSystemProxy(api) {
42052
42344
  listModelCatalog: (input) => dispatch("llm", "listModelCatalog", "query", input),
42053
42345
  listRuntimeNodes: (input) => dispatch("llm", "listRuntimeNodes", "query", input),
42054
42346
  listNodeModels: (input) => dispatch("llm", "listNodeModels", "query", input),
42347
+ resolveModelRef: (input) => dispatch("llm", "resolveModelRef", "mutation", input),
42055
42348
  installModel: (input) => dispatch("llm", "installModel", "mutation", input),
42056
42349
  deleteModel: (input) => dispatch("llm", "deleteModel", "mutation", input),
42057
42350
  getRuntimeStatus: (input) => dispatch("llm", "getRuntimeStatus", "query", input),
@@ -45418,6 +45711,8 @@ exports.AvailableIntegrationTypeSchema = AvailableIntegrationTypeSchema;
45418
45711
  exports.BACKEND_TO_FORMAT = BACKEND_TO_FORMAT;
45419
45712
  exports.BASE_LIVE_EGRESS_PROFILE = BASE_LIVE_EGRESS_PROFILE;
45420
45713
  exports.BATTERY_DEVICE_PROFILE = BATTERY_DEVICE_PROFILE;
45714
+ exports.BATTERY_UNREACHABLE_AFTER_MS = BATTERY_UNREACHABLE_AFTER_MS;
45715
+ exports.BOOT_RECOVERY_BACKOFF_MS = require_sleep.BOOT_RECOVERY_BACKOFF_MS;
45421
45716
  exports.BacklightModeSchema = BacklightModeSchema;
45422
45717
  exports.BackupDestinationInfoSchema = BackupDestinationInfoSchema;
45423
45718
  exports.BackupEntrySchema = BackupEntrySchema;
@@ -45679,6 +45974,7 @@ exports.HF_BASE_URL = HF_BASE_URL;
45679
45974
  exports.HF_REPO = HF_REPO;
45680
45975
  exports.HWACCEL_OPTIONS = HWACCEL_OPTIONS;
45681
45976
  exports.HealthStatusSchema = HealthStatusSchema;
45977
+ exports.HfModelResolutionSchema = HfModelResolutionSchema;
45682
45978
  exports.HistoryPointSchema = HistoryPointSchema;
45683
45979
  exports.HistoryResolutionEnum = HistoryResolutionEnum;
45684
45980
  exports.HumidifierStatusSchema = HumidifierStatusSchema;
@@ -45709,6 +46005,7 @@ exports.LinkedDeviceSchema = LinkedDeviceSchema;
45709
46005
  exports.LinkedDevicesModeSchema = LinkedDevicesModeSchema;
45710
46006
  exports.LlmDefaultSchema = LlmDefaultSchema;
45711
46007
  exports.LlmDefaultSelectorSchema = LlmDefaultSelectorSchema;
46008
+ exports.LlmDownloadProgressSchema = LlmDownloadProgressSchema;
45712
46009
  exports.LlmErrorCodeSchema = LlmErrorCodeSchema;
45713
46010
  exports.LlmGenerateBaseInputSchema = LlmGenerateBaseInputSchema;
45714
46011
  exports.LlmGenerateErrSchema = LlmGenerateErrSchema;
@@ -45749,6 +46046,7 @@ exports.METHOD_DEVICE_SELECTORS = METHOD_DEVICE_SELECTORS;
45749
46046
  exports.MODEL_FORMATS = MODEL_FORMATS;
45750
46047
  exports.MOTION_TRIGGER_FEATURE = MOTION_TRIGGER_FEATURE;
45751
46048
  exports.ManagedModelCatalogEntrySchema = ManagedModelCatalogEntrySchema;
46049
+ exports.ManagedModelExtraFileSchema = ManagedModelExtraFileSchema;
45752
46050
  exports.ManagedModelRefSchema = ManagedModelRefSchema;
45753
46051
  exports.ManagedRuntimeConfigSchema = ManagedRuntimeConfigSchema;
45754
46052
  exports.MaskGridDimsSchema = MaskGridDimsSchema;
@@ -46341,6 +46639,7 @@ exports.decodeVectorBase64 = decodeVectorBase64;
46341
46639
  exports.decoderCapability = decoderCapability;
46342
46640
  exports.defaultDeviceFor = defaultDeviceFor;
46343
46641
  exports.defineCustomActions = defineCustomActions;
46642
+ exports.deriveBatteryPresence = deriveBatteryPresence;
46344
46643
  exports.deriveCameraSwitches = deriveCameraSwitches;
46345
46644
  exports.deriveDetailCropRect = deriveDetailCropRect;
46346
46645
  exports.deriveRecordingMode = deriveRecordingMode;
@@ -46410,6 +46709,7 @@ exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
46410
46709
  exports.isArrayOutputSchema = isArrayOutputSchema;
46411
46710
  exports.isAudioLabelSelected = isAudioLabelSelected;
46412
46711
  exports.isBaseConditionKey = isBaseConditionKey;
46712
+ exports.isBatteryPresenceFault = isBatteryPresenceFault;
46413
46713
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
46414
46714
  exports.isDeployableToAgent = isDeployableToAgent;
46415
46715
  exports.isDetectionMacroClass = isDetectionMacroClass;