@camstack/addon-post-analysis 1.2.78 → 1.2.80

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.
@@ -12759,6 +12759,18 @@ var LlmGenerateBaseInputSchema = object({
12759
12759
  * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
12760
12760
  * watchdog — operator decision #3).
12761
12761
  */
12762
+ /**
12763
+ * A companion artifact that MUST land beside the main GGUF: the `mmproj`
12764
+ * projector of a vision model, or shards 2..N of a split GGUF. Carried on the
12765
+ * REF rather than looked up at install time, so what the operator approved in
12766
+ * the preview is exactly what the node downloads.
12767
+ */
12768
+ var ManagedModelExtraFileSchema = object({
12769
+ url: string(),
12770
+ filename: string(),
12771
+ sizeBytes: number(),
12772
+ sha256: string().optional()
12773
+ });
12762
12774
  var ManagedModelRefSchema = discriminatedUnion("kind", [
12763
12775
  object({
12764
12776
  kind: literal("catalog"),
@@ -12767,7 +12779,11 @@ var ManagedModelRefSchema = discriminatedUnion("kind", [
12767
12779
  object({
12768
12780
  kind: literal("url"),
12769
12781
  url: string(),
12770
- sha256: string().optional()
12782
+ sha256: string().optional(),
12783
+ /** Picker/status label; the file basename when absent. */
12784
+ label: string().optional(),
12785
+ sizeBytes: number().optional(),
12786
+ extraFiles: array(ManagedModelExtraFileSchema).optional()
12771
12787
  }),
12772
12788
  object({
12773
12789
  kind: literal("path"),
@@ -12828,11 +12844,39 @@ var ManagedRuntimeConfigSchema = object({
12828
12844
  "q4_1",
12829
12845
  "q4_0"
12830
12846
  ]).optional(),
12847
+ /**
12848
+ * Escape hatch for llama-server flags this schema does NOT model — `--jinja`
12849
+ * (which most vision chat templates need and some language-only models
12850
+ * dislike), `--cont-batching`, `--rope-scaling`, …
12851
+ *
12852
+ * It is NOT a second place to set the flags above. A token that collides
12853
+ * with a typed field is REJECTED at start, naming the field that owns it
12854
+ * (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
12855
+ * the "two switches that disagree" failure this repo has already shipped
12856
+ * twice (D62).
12857
+ */
12858
+ extraArgs: array(string()).default([]),
12831
12859
  /** Else lazy: first generate boots it. */
12832
12860
  autoStart: boolean().default(false),
12833
12861
  /** 0 = never; frees RAM after quiet periods. */
12834
12862
  idleStopMinutes: number().int().default(30)
12835
12863
  });
12864
+ /**
12865
+ * Where a multi-GB install currently is. A single 0..1 fraction cannot answer
12866
+ * "is it stuck?" for an install that is three files (shards + mmproj) followed
12867
+ * by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
12868
+ * node looked hung. Phase + file + bytes is the smallest shape that does.
12869
+ */
12870
+ var LlmDownloadProgressSchema = object({
12871
+ phase: _enum(["downloading", "verifying"]),
12872
+ /** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
12873
+ file: string(),
12874
+ fileIndex: number().int(),
12875
+ fileCount: number().int(),
12876
+ /** Across the WHOLE install, not the current file. */
12877
+ downloadedBytes: number(),
12878
+ totalBytes: number().optional()
12879
+ });
12836
12880
  var LlmRuntimeStatusSchema = object({
12837
12881
  /** Status is ALWAYS node-qualified. */
12838
12882
  nodeId: string(),
@@ -12849,6 +12893,8 @@ var LlmRuntimeStatusSchema = object({
12849
12893
  modelPath: string().optional(),
12850
12894
  modelId: string().optional(),
12851
12895
  downloadProgress: number().min(0).max(1).optional(),
12896
+ /** Detail behind `downloadProgress`; present for the same lifetime. */
12897
+ download: LlmDownloadProgressSchema.optional(),
12852
12898
  lastError: string().optional(),
12853
12899
  crashesInWindow: number(),
12854
12900
  /** Child RSS (sampled best-effort). */
@@ -12859,7 +12905,14 @@ var LlmNodeModelSchema = object({
12859
12905
  file: string(),
12860
12906
  sizeBytes: number(),
12861
12907
  catalogId: string().optional(),
12862
- installedAt: number().optional()
12908
+ installedAt: number().optional(),
12909
+ /**
12910
+ * Absolute path on the node. Present so a file that is on disk but matches
12911
+ * no catalog entry — a custom Hugging Face install, or a GGUF the operator
12912
+ * copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
12913
+ * it the picker could list such a file and do nothing with it.
12914
+ */
12915
+ path: string().optional()
12863
12916
  });
12864
12917
  var LlmRuntimeDiskUsageSchema = object({
12865
12918
  nodeId: string(),
@@ -13005,6 +13058,36 @@ var ManagedModelCatalogEntrySchema = object({
13005
13058
  /** Vision models: companion projector file. */
13006
13059
  mmprojUrl: string().optional()
13007
13060
  });
13061
+ /**
13062
+ * The outcome of turning one operator-typed Hugging Face reference into a
13063
+ * download plan. A RESULT, never a throw: "this repo has 24 quantizations and
13064
+ * I will not pick for you" is a normal answer the UI has to render, not an
13065
+ * exception.
13066
+ *
13067
+ * `candidates` is the whole reason the refusal is usable — every string in it
13068
+ * is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
13069
+ */
13070
+ var HfModelResolutionSchema = discriminatedUnion("ok", [object({
13071
+ ok: literal(true),
13072
+ /** Ready to hand to `installModel` unchanged. */
13073
+ model: ManagedModelRefSchema,
13074
+ label: string(),
13075
+ repo: string(),
13076
+ quantization: string(),
13077
+ purpose: _enum(["text", "vision"]),
13078
+ totalBytes: number(),
13079
+ /** mmproj + shards, for the preview: an operator approving 23 GB should
13080
+ * see that 0.9 GB of it is a projector they did not name. */
13081
+ extraFilenames: array(string())
13082
+ }), object({
13083
+ ok: literal(false),
13084
+ code: string(),
13085
+ message: string(),
13086
+ candidates: array(string()).optional(),
13087
+ /** Set when the refusal was only the ceiling: re-calling with
13088
+ * `maxBytes: requiredBytes` is the operator's explicit override. */
13089
+ requiredBytes: number().optional()
13090
+ })]);
13008
13091
  var LlmRuntimeNodeSchema = object({
13009
13092
  nodeId: string(),
13010
13093
  reachable: boolean(),
@@ -13041,6 +13124,15 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13041
13124
  consumer: string().optional(),
13042
13125
  profileId: string().optional()
13043
13126
  }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
13127
+ /** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
13128
+ * `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
13129
+ ref: string(),
13130
+ /** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
13131
+ maxBytes: number().positive().optional()
13132
+ }), HfModelResolutionSchema, {
13133
+ kind: "mutation",
13134
+ auth: "admin"
13135
+ }), method(object({
13044
13136
  nodeId: string(),
13045
13137
  model: ManagedModelRefSchema
13046
13138
  }), _void(), {
@@ -19242,6 +19334,17 @@ var maxSessionHoldMsField = {
19242
19334
  default: 12e4,
19243
19335
  step: 5e3
19244
19336
  };
19337
+ /**
19338
+ * Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
19339
+ * 5s so a rearm can never degenerate into per-event stream churn; default 90s
19340
+ * comfortably outlives the gap between two PIR wakes on a battery camera.
19341
+ */
19342
+ var audioMotionWindowMsField = {
19343
+ min: 5e3,
19344
+ max: 6e5,
19345
+ default: 9e4,
19346
+ step: 5e3
19347
+ };
19245
19348
  var motionFpsField = {
19246
19349
  min: 1,
19247
19350
  max: 30,
@@ -19418,6 +19521,27 @@ var RunnerCameraConfigSchema = object({
19418
19521
  * resolved `CameraDetectionConfig`.
19419
19522
  */
19420
19523
  maxSessionHoldMs: number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
19524
+ /**
19525
+ * Orchestrator-side quiet period (ms) that closes an `audioMode:
19526
+ * 'on-motion'` audio window, measured from the LAST motion event.
19527
+ *
19528
+ * This exists because the falling edge cannot be relied on. Camera-native
19529
+ * providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
19530
+ * its email-push SMTP path both emit `detected: true` and never the
19531
+ * counterpart); only the frame-diff analyzer emits falls. So on an
19532
+ * onboard-only camera a window that closed only on `detected: false` never
19533
+ * closed at all, and `on-motion` silently behaved as `always-on` — on a
19534
+ * battery camera, the one failure mode the mode exists to prevent.
19535
+ *
19536
+ * Every motion event rearms this timer WITHOUT restarting the stream, so a
19537
+ * burst of re-fires costs nothing. A falling edge, when one does arrive,
19538
+ * still closes earlier via `motionCooldownMs` — whichever comes first wins.
19539
+ *
19540
+ * Not consumed by the runner: carried here so it shares the per-camera
19541
+ * device-settings surface with `motionCooldownMs`, exactly like
19542
+ * `maxSessionHoldMs`.
19543
+ */
19544
+ audioMotionWindowMs: number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
19421
19545
  motionFps: number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
19422
19546
  detectionFps: number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
19423
19547
  motionStreamId: string(),
@@ -19513,7 +19637,7 @@ var RunnerCameraConfigSchema = object({
19513
19637
  */
19514
19638
  inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
19515
19639
  });
19516
- 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;
19640
+ 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;
19517
19641
  /**
19518
19642
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
19519
19643
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -20559,7 +20683,16 @@ targets: array(object({
20559
20683
  /** A sleeping battery camera: the frame is deliberately stale and will
20560
20684
  * NOT refresh in the background. A surface should say so rather than
20561
20685
  * present it as current. */
20562
- sleeping: boolean()
20686
+ sleeping: boolean(),
20687
+ /** Current device state rendered over the cached frame. State images
20688
+ * remain authoritative even when their photographic background is
20689
+ * old; null means the link must carry a current camera frame. */
20690
+ stateReason: _enum([
20691
+ "disabled",
20692
+ "sleeping",
20693
+ "unreachable",
20694
+ "waking"
20695
+ ]).nullable()
20563
20696
  })));
20564
20697
  /**
20565
20698
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -22227,6 +22360,25 @@ var BatteryStatusSchema = object({
22227
22360
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
22228
22361
  lastUpdated: number(),
22229
22362
  /**
22363
+ * Ms epoch of the last time the device PROVED it was reachable — a
22364
+ * completed firmware round-trip, an observed wake, or an inbound push
22365
+ * (firmware event, email). `0`/absent = never since this slice was born.
22366
+ *
22367
+ * This is the ONLY input that separates "asleep" from "gone", and it is
22368
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
22369
+ * for the radio, because a poll that confirms reachability is the same
22370
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
22371
+ * single derivation every consumer must use; no surface computes its own.
22372
+ *
22373
+ * It is deliberately NOT a clock in the
22374
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
22375
+ * observation itself, and it is the only thing a 30-hour silence is
22376
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
22377
+ * Reolink provider) so a value that means "recently" cannot cost a
22378
+ * SQLite commit per round-trip.
22379
+ */
22380
+ lastContactAt: number().optional(),
22381
+ /**
22230
22382
  * True when the source is a BINARY low-battery indicator (HA
22231
22383
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
22232
22384
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -28254,7 +28406,70 @@ var CamStreamDescriptorSchema = object({
28254
28406
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
28255
28407
  metadata: record(string(), unknown()).optional()
28256
28408
  });
28257
- DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
28409
+ /**
28410
+ * `stream-catalog` — device-scoped, provider-implemented. The pull counterpart
28411
+ * of the removed `publishCameraStream` push: a camera provider returns the full
28412
+ * set of stream descriptors it can offer for the device, synchronously, so the
28413
+ * broker can reconcile its registry against the authoritative provider state.
28414
+ */
28415
+ /**
28416
+ * The catalog as a DURABLE fact rather than a live answer.
28417
+ *
28418
+ * A battery camera's descriptors are profile-stable — they change when the
28419
+ * operator rewrites an encoder profile, not minute to minute — but building
28420
+ * them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
28421
+ * provider is allowed to build them exactly once per profile and must serve
28422
+ * every later pull from a cache.
28423
+ *
28424
+ * Holding that cache only in RAM is what turned a restart into an outage. The
28425
+ * runner comes back with the camera asleep, `buildStreamCatalogUncached`
28426
+ * correctly refuses to wake it, the pull answers `[]`, the broker has no
28427
+ * cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
28428
+ * fails with a flat "No broker for stream" — for as long as the camera sleeps,
28429
+ * which on a battery cam is most of the day. The camera was fine. The stream
28430
+ * was unreachable because the process had forgotten what the camera offers.
28431
+ *
28432
+ * Declaring it here puts it in `device-runtime-state`, the kernel's canonical
28433
+ * declared collection, with the same `restored` durability `battery` uses for
28434
+ * the same reason: the last known value is the only value there is while the
28435
+ * device is asleep. The broker's brokers are therefore always DEFINABLE — it
28436
+ * is the DIAL that wakes a camera, never the catalog (D173).
28437
+ */
28438
+ var StreamCatalogStateSchema = object({
28439
+ /** The descriptors as last built from a real camera response. Never a guess:
28440
+ * a failed or refused build writes NOTHING, so a restored catalog is always
28441
+ * one the camera itself once produced. */
28442
+ descriptors: array(CamStreamDescriptorSchema),
28443
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
28444
+ * path decide whether the camera's own awake window is worth spending on a
28445
+ * re-read. */
28446
+ lastFetchedAt: number()
28447
+ });
28448
+ var streamCatalogCapability = {
28449
+ name: "stream-catalog",
28450
+ scope: "device",
28451
+ deviceNative: true,
28452
+ mode: "singleton",
28453
+ deviceTypes: [DeviceType.Camera],
28454
+ methods: { getCatalog: method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly()) },
28455
+ runtimeState: StreamCatalogStateSchema,
28456
+ /**
28457
+ * Runtime-state durability: **restored** — see the schema doc. A cold
28458
+ * catalog on a sleeping battery camera is not a slow first frame, it is a
28459
+ * camera that cannot be watched at all until it happens to wake.
28460
+ *
28461
+ * Churn is nil by construction: the slice is written only by a SUCCESSFUL
28462
+ * build, and a build only runs when there is no cached copy (or the copy is
28463
+ * a day old and the camera is awake anyway).
28464
+ *
28465
+ * See `RuntimeStateDurability`. Enforced by
28466
+ * `scripts/check-runtime-state-durability.ts`.
28467
+ */
28468
+ durability: "restored",
28469
+ /** Clock field: written, but excluded from the compare that decides whether
28470
+ * persisting is worth a SQLite commit — the descriptors are the value. */
28471
+ volatileStateFields: ["lastFetchedAt"]
28472
+ };
28258
28473
  /** One of the camera's stream profiles. */
28259
28474
  var StreamProfileSchema = _enum([
28260
28475
  "main",
@@ -29958,6 +30173,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
29958
30173
  sceneMonitor: sceneMonitorCapability,
29959
30174
  scriptRunner: scriptRunnerCapability,
29960
30175
  smoke: smokeCapability,
30176
+ streamCatalog: streamCatalogCapability,
29961
30177
  streamParams: streamParamsCapability,
29962
30178
  switch: switchCapability,
29963
30179
  tamper: tamperCapability,
@@ -32844,6 +33060,12 @@ Object.freeze({
32844
33060
  addonId: null,
32845
33061
  access: "view"
32846
33062
  },
33063
+ "llm.resolveModelRef": {
33064
+ capName: "llm",
33065
+ capScope: "system",
33066
+ addonId: null,
33067
+ access: "create"
33068
+ },
32847
33069
  "llm.setDefault": {
32848
33070
  capName: "llm",
32849
33071
  capScope: "system",
@@ -12728,6 +12728,18 @@ var LlmGenerateBaseInputSchema = object({
12728
12728
  * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
12729
12729
  * watchdog — operator decision #3).
12730
12730
  */
12731
+ /**
12732
+ * A companion artifact that MUST land beside the main GGUF: the `mmproj`
12733
+ * projector of a vision model, or shards 2..N of a split GGUF. Carried on the
12734
+ * REF rather than looked up at install time, so what the operator approved in
12735
+ * the preview is exactly what the node downloads.
12736
+ */
12737
+ var ManagedModelExtraFileSchema = object({
12738
+ url: string(),
12739
+ filename: string(),
12740
+ sizeBytes: number(),
12741
+ sha256: string().optional()
12742
+ });
12731
12743
  var ManagedModelRefSchema = discriminatedUnion("kind", [
12732
12744
  object({
12733
12745
  kind: literal("catalog"),
@@ -12736,7 +12748,11 @@ var ManagedModelRefSchema = discriminatedUnion("kind", [
12736
12748
  object({
12737
12749
  kind: literal("url"),
12738
12750
  url: string(),
12739
- sha256: string().optional()
12751
+ sha256: string().optional(),
12752
+ /** Picker/status label; the file basename when absent. */
12753
+ label: string().optional(),
12754
+ sizeBytes: number().optional(),
12755
+ extraFiles: array(ManagedModelExtraFileSchema).optional()
12740
12756
  }),
12741
12757
  object({
12742
12758
  kind: literal("path"),
@@ -12797,11 +12813,39 @@ var ManagedRuntimeConfigSchema = object({
12797
12813
  "q4_1",
12798
12814
  "q4_0"
12799
12815
  ]).optional(),
12816
+ /**
12817
+ * Escape hatch for llama-server flags this schema does NOT model — `--jinja`
12818
+ * (which most vision chat templates need and some language-only models
12819
+ * dislike), `--cont-batching`, `--rope-scaling`, …
12820
+ *
12821
+ * It is NOT a second place to set the flags above. A token that collides
12822
+ * with a typed field is REJECTED at start, naming the field that owns it
12823
+ * (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
12824
+ * the "two switches that disagree" failure this repo has already shipped
12825
+ * twice (D62).
12826
+ */
12827
+ extraArgs: array(string()).default([]),
12800
12828
  /** Else lazy: first generate boots it. */
12801
12829
  autoStart: boolean().default(false),
12802
12830
  /** 0 = never; frees RAM after quiet periods. */
12803
12831
  idleStopMinutes: number().int().default(30)
12804
12832
  });
12833
+ /**
12834
+ * Where a multi-GB install currently is. A single 0..1 fraction cannot answer
12835
+ * "is it stuck?" for an install that is three files (shards + mmproj) followed
12836
+ * by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
12837
+ * node looked hung. Phase + file + bytes is the smallest shape that does.
12838
+ */
12839
+ var LlmDownloadProgressSchema = object({
12840
+ phase: _enum(["downloading", "verifying"]),
12841
+ /** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
12842
+ file: string(),
12843
+ fileIndex: number().int(),
12844
+ fileCount: number().int(),
12845
+ /** Across the WHOLE install, not the current file. */
12846
+ downloadedBytes: number(),
12847
+ totalBytes: number().optional()
12848
+ });
12805
12849
  var LlmRuntimeStatusSchema = object({
12806
12850
  /** Status is ALWAYS node-qualified. */
12807
12851
  nodeId: string(),
@@ -12818,6 +12862,8 @@ var LlmRuntimeStatusSchema = object({
12818
12862
  modelPath: string().optional(),
12819
12863
  modelId: string().optional(),
12820
12864
  downloadProgress: number().min(0).max(1).optional(),
12865
+ /** Detail behind `downloadProgress`; present for the same lifetime. */
12866
+ download: LlmDownloadProgressSchema.optional(),
12821
12867
  lastError: string().optional(),
12822
12868
  crashesInWindow: number(),
12823
12869
  /** Child RSS (sampled best-effort). */
@@ -12828,7 +12874,14 @@ var LlmNodeModelSchema = object({
12828
12874
  file: string(),
12829
12875
  sizeBytes: number(),
12830
12876
  catalogId: string().optional(),
12831
- installedAt: number().optional()
12877
+ installedAt: number().optional(),
12878
+ /**
12879
+ * Absolute path on the node. Present so a file that is on disk but matches
12880
+ * no catalog entry — a custom Hugging Face install, or a GGUF the operator
12881
+ * copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
12882
+ * it the picker could list such a file and do nothing with it.
12883
+ */
12884
+ path: string().optional()
12832
12885
  });
12833
12886
  var LlmRuntimeDiskUsageSchema = object({
12834
12887
  nodeId: string(),
@@ -12974,6 +13027,36 @@ var ManagedModelCatalogEntrySchema = object({
12974
13027
  /** Vision models: companion projector file. */
12975
13028
  mmprojUrl: string().optional()
12976
13029
  });
13030
+ /**
13031
+ * The outcome of turning one operator-typed Hugging Face reference into a
13032
+ * download plan. A RESULT, never a throw: "this repo has 24 quantizations and
13033
+ * I will not pick for you" is a normal answer the UI has to render, not an
13034
+ * exception.
13035
+ *
13036
+ * `candidates` is the whole reason the refusal is usable — every string in it
13037
+ * is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
13038
+ */
13039
+ var HfModelResolutionSchema = discriminatedUnion("ok", [object({
13040
+ ok: literal(true),
13041
+ /** Ready to hand to `installModel` unchanged. */
13042
+ model: ManagedModelRefSchema,
13043
+ label: string(),
13044
+ repo: string(),
13045
+ quantization: string(),
13046
+ purpose: _enum(["text", "vision"]),
13047
+ totalBytes: number(),
13048
+ /** mmproj + shards, for the preview: an operator approving 23 GB should
13049
+ * see that 0.9 GB of it is a projector they did not name. */
13050
+ extraFilenames: array(string())
13051
+ }), object({
13052
+ ok: literal(false),
13053
+ code: string(),
13054
+ message: string(),
13055
+ candidates: array(string()).optional(),
13056
+ /** Set when the refusal was only the ceiling: re-calling with
13057
+ * `maxBytes: requiredBytes` is the operator's explicit override. */
13058
+ requiredBytes: number().optional()
13059
+ })]);
12977
13060
  var LlmRuntimeNodeSchema = object({
12978
13061
  nodeId: string(),
12979
13062
  reachable: boolean(),
@@ -13010,6 +13093,15 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
13010
13093
  consumer: string().optional(),
13011
13094
  profileId: string().optional()
13012
13095
  }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
13096
+ /** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
13097
+ * `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
13098
+ ref: string(),
13099
+ /** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
13100
+ maxBytes: number().positive().optional()
13101
+ }), HfModelResolutionSchema, {
13102
+ kind: "mutation",
13103
+ auth: "admin"
13104
+ }), method(object({
13013
13105
  nodeId: string(),
13014
13106
  model: ManagedModelRefSchema
13015
13107
  }), _void(), {
@@ -19211,6 +19303,17 @@ var maxSessionHoldMsField = {
19211
19303
  default: 12e4,
19212
19304
  step: 5e3
19213
19305
  };
19306
+ /**
19307
+ * Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
19308
+ * 5s so a rearm can never degenerate into per-event stream churn; default 90s
19309
+ * comfortably outlives the gap between two PIR wakes on a battery camera.
19310
+ */
19311
+ var audioMotionWindowMsField = {
19312
+ min: 5e3,
19313
+ max: 6e5,
19314
+ default: 9e4,
19315
+ step: 5e3
19316
+ };
19214
19317
  var motionFpsField = {
19215
19318
  min: 1,
19216
19319
  max: 30,
@@ -19387,6 +19490,27 @@ var RunnerCameraConfigSchema = object({
19387
19490
  * resolved `CameraDetectionConfig`.
19388
19491
  */
19389
19492
  maxSessionHoldMs: number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
19493
+ /**
19494
+ * Orchestrator-side quiet period (ms) that closes an `audioMode:
19495
+ * 'on-motion'` audio window, measured from the LAST motion event.
19496
+ *
19497
+ * This exists because the falling edge cannot be relied on. Camera-native
19498
+ * providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
19499
+ * its email-push SMTP path both emit `detected: true` and never the
19500
+ * counterpart); only the frame-diff analyzer emits falls. So on an
19501
+ * onboard-only camera a window that closed only on `detected: false` never
19502
+ * closed at all, and `on-motion` silently behaved as `always-on` — on a
19503
+ * battery camera, the one failure mode the mode exists to prevent.
19504
+ *
19505
+ * Every motion event rearms this timer WITHOUT restarting the stream, so a
19506
+ * burst of re-fires costs nothing. A falling edge, when one does arrive,
19507
+ * still closes earlier via `motionCooldownMs` — whichever comes first wins.
19508
+ *
19509
+ * Not consumed by the runner: carried here so it shares the per-camera
19510
+ * device-settings surface with `motionCooldownMs`, exactly like
19511
+ * `maxSessionHoldMs`.
19512
+ */
19513
+ audioMotionWindowMs: number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
19390
19514
  motionFps: number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
19391
19515
  detectionFps: number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
19392
19516
  motionStreamId: string(),
@@ -19482,7 +19606,7 @@ var RunnerCameraConfigSchema = object({
19482
19606
  */
19483
19607
  inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
19484
19608
  });
19485
- 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;
19609
+ 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;
19486
19610
  /**
19487
19611
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
19488
19612
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -20528,7 +20652,16 @@ targets: array(object({
20528
20652
  /** A sleeping battery camera: the frame is deliberately stale and will
20529
20653
  * NOT refresh in the background. A surface should say so rather than
20530
20654
  * present it as current. */
20531
- sleeping: boolean()
20655
+ sleeping: boolean(),
20656
+ /** Current device state rendered over the cached frame. State images
20657
+ * remain authoritative even when their photographic background is
20658
+ * old; null means the link must carry a current camera frame. */
20659
+ stateReason: _enum([
20660
+ "disabled",
20661
+ "sleeping",
20662
+ "unreachable",
20663
+ "waking"
20664
+ ]).nullable()
20532
20665
  })));
20533
20666
  /**
20534
20667
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -22196,6 +22329,25 @@ var BatteryStatusSchema = object({
22196
22329
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
22197
22330
  lastUpdated: number(),
22198
22331
  /**
22332
+ * Ms epoch of the last time the device PROVED it was reachable — a
22333
+ * completed firmware round-trip, an observed wake, or an inbound push
22334
+ * (firmware event, email). `0`/absent = never since this slice was born.
22335
+ *
22336
+ * This is the ONLY input that separates "asleep" from "gone", and it is
22337
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
22338
+ * for the radio, because a poll that confirms reachability is the same
22339
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
22340
+ * single derivation every consumer must use; no surface computes its own.
22341
+ *
22342
+ * It is deliberately NOT a clock in the
22343
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
22344
+ * observation itself, and it is the only thing a 30-hour silence is
22345
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
22346
+ * Reolink provider) so a value that means "recently" cannot cost a
22347
+ * SQLite commit per round-trip.
22348
+ */
22349
+ lastContactAt: number().optional(),
22350
+ /**
22199
22351
  * True when the source is a BINARY low-battery indicator (HA
22200
22352
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
22201
22353
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -28223,7 +28375,70 @@ var CamStreamDescriptorSchema = object({
28223
28375
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
28224
28376
  metadata: record(string(), unknown()).optional()
28225
28377
  });
28226
- DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
28378
+ /**
28379
+ * `stream-catalog` — device-scoped, provider-implemented. The pull counterpart
28380
+ * of the removed `publishCameraStream` push: a camera provider returns the full
28381
+ * set of stream descriptors it can offer for the device, synchronously, so the
28382
+ * broker can reconcile its registry against the authoritative provider state.
28383
+ */
28384
+ /**
28385
+ * The catalog as a DURABLE fact rather than a live answer.
28386
+ *
28387
+ * A battery camera's descriptors are profile-stable — they change when the
28388
+ * operator rewrites an encoder profile, not minute to minute — but building
28389
+ * them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
28390
+ * provider is allowed to build them exactly once per profile and must serve
28391
+ * every later pull from a cache.
28392
+ *
28393
+ * Holding that cache only in RAM is what turned a restart into an outage. The
28394
+ * runner comes back with the camera asleep, `buildStreamCatalogUncached`
28395
+ * correctly refuses to wake it, the pull answers `[]`, the broker has no
28396
+ * cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
28397
+ * fails with a flat "No broker for stream" — for as long as the camera sleeps,
28398
+ * which on a battery cam is most of the day. The camera was fine. The stream
28399
+ * was unreachable because the process had forgotten what the camera offers.
28400
+ *
28401
+ * Declaring it here puts it in `device-runtime-state`, the kernel's canonical
28402
+ * declared collection, with the same `restored` durability `battery` uses for
28403
+ * the same reason: the last known value is the only value there is while the
28404
+ * device is asleep. The broker's brokers are therefore always DEFINABLE — it
28405
+ * is the DIAL that wakes a camera, never the catalog (D173).
28406
+ */
28407
+ var StreamCatalogStateSchema = object({
28408
+ /** The descriptors as last built from a real camera response. Never a guess:
28409
+ * a failed or refused build writes NOTHING, so a restored catalog is always
28410
+ * one the camera itself once produced. */
28411
+ descriptors: array(CamStreamDescriptorSchema),
28412
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
28413
+ * path decide whether the camera's own awake window is worth spending on a
28414
+ * re-read. */
28415
+ lastFetchedAt: number()
28416
+ });
28417
+ var streamCatalogCapability = {
28418
+ name: "stream-catalog",
28419
+ scope: "device",
28420
+ deviceNative: true,
28421
+ mode: "singleton",
28422
+ deviceTypes: [DeviceType.Camera],
28423
+ methods: { getCatalog: method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly()) },
28424
+ runtimeState: StreamCatalogStateSchema,
28425
+ /**
28426
+ * Runtime-state durability: **restored** — see the schema doc. A cold
28427
+ * catalog on a sleeping battery camera is not a slow first frame, it is a
28428
+ * camera that cannot be watched at all until it happens to wake.
28429
+ *
28430
+ * Churn is nil by construction: the slice is written only by a SUCCESSFUL
28431
+ * build, and a build only runs when there is no cached copy (or the copy is
28432
+ * a day old and the camera is awake anyway).
28433
+ *
28434
+ * See `RuntimeStateDurability`. Enforced by
28435
+ * `scripts/check-runtime-state-durability.ts`.
28436
+ */
28437
+ durability: "restored",
28438
+ /** Clock field: written, but excluded from the compare that decides whether
28439
+ * persisting is worth a SQLite commit — the descriptors are the value. */
28440
+ volatileStateFields: ["lastFetchedAt"]
28441
+ };
28227
28442
  /** One of the camera's stream profiles. */
28228
28443
  var StreamProfileSchema = _enum([
28229
28444
  "main",
@@ -29927,6 +30142,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
29927
30142
  sceneMonitor: sceneMonitorCapability,
29928
30143
  scriptRunner: scriptRunnerCapability,
29929
30144
  smoke: smokeCapability,
30145
+ streamCatalog: streamCatalogCapability,
29930
30146
  streamParams: streamParamsCapability,
29931
30147
  switch: switchCapability,
29932
30148
  tamper: tamperCapability,
@@ -32813,6 +33029,12 @@ Object.freeze({
32813
33029
  addonId: null,
32814
33030
  access: "view"
32815
33031
  },
33032
+ "llm.resolveModelRef": {
33033
+ capName: "llm",
33034
+ capScope: "system",
33035
+ addonId: null,
33036
+ access: "create"
33037
+ },
32816
33038
  "llm.setDefault": {
32817
33039
  capName: "llm",
32818
33040
  capScope: "system",