@camstack/addon-provider-dreame 0.2.16 → 0.2.18

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 (3) hide show
  1. package/dist/addon.js +2934 -147
  2. package/dist/addon.mjs +2934 -147
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -50504,7 +50504,7 @@ objectType({
50504
50504
  })
50505
50505
  });
50506
50506
  //#endregion
50507
- //#region ../types/dist/event-category-Cv9dO26A.mjs
50507
+ //#region ../types/dist/event-category-Bxo5yJjt.mjs
50508
50508
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
50509
50509
  EventCategory["SystemBoot"] = "system.boot";
50510
50510
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -50711,6 +50711,33 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
50711
50711
  EventCategory["PipelineCameraAssigned"] = "pipeline.camera-assigned";
50712
50712
  EventCategory["PipelineCameraUnassigned"] = "pipeline.camera-unassigned";
50713
50713
  /**
50714
+ * A node the orchestrator would otherwise place cameras on has NO usable
50715
+ * inference device: the operator enabled one or more accelerators there and
50716
+ * the live probe reports every one of them unavailable. Emitted once per
50717
+ * TRANSITION into that state (never per dispatch), and the node is dropped
50718
+ * from the placement candidate set for as long as it holds.
50719
+ *
50720
+ * This exists because the state was previously invisible: little-unraid
50721
+ * absorbed 283k inference errors in a day while still being handed cameras,
50722
+ * and nothing in the system said so.
50723
+ *
50724
+ * A node with no accelerators configured at all is NOT this — its devices
50725
+ * are `disabled`, not `unavailable`, and the runner's default CPU pool
50726
+ * serves it exactly as before.
50727
+ */
50728
+ EventCategory["PipelineNodeInferenceUnavailable"] = "pipeline.node-inference-unavailable";
50729
+ /**
50730
+ * A camera has an OPEN detection session and has produced no detection at
50731
+ * all for longer than the blind threshold — the camera is being decoded and
50732
+ * inferred and is returning nothing. Emitted once per transition into blind,
50733
+ * per camera.
50734
+ *
50735
+ * The failure it reports: a 1h43 detection blackout on the entrance camera
50736
+ * that nobody noticed, because "a camera that detects nothing" and "a quiet
50737
+ * camera" produce byte-identical silence.
50738
+ */
50739
+ EventCategory["PipelineDetectionBlind"] = "pipeline.detection-blind";
50740
+ /**
50714
50741
  * Per-camera pipeline config was mutated by the orchestrator
50715
50742
  * (3-level settings change via `setAgentAddonDefaults` /
50716
50743
  * `setCameraStepToggle` / `setCameraPipelineForAgent` or a
@@ -61395,6 +61422,8 @@ var QueryFilterSchema = object({
61395
61422
  where: record(string(), unknown()).optional(),
61396
61423
  whereIn: record(string(), array(unknown())).optional(),
61397
61424
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
61425
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
61426
+ whereNot: record(string(), unknown()).optional(),
61398
61427
  orderBy: object({
61399
61428
  field: string(),
61400
61429
  direction: _enum(["asc", "desc"])
@@ -61414,7 +61443,8 @@ var QueryFilterSchema = object({
61414
61443
  var MutationFilterSchema = object({
61415
61444
  where: record(string(), unknown()).optional(),
61416
61445
  whereIn: record(string(), array(unknown())).optional(),
61417
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
61446
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
61447
+ whereNot: record(string(), unknown()).optional()
61418
61448
  });
61419
61449
  /** A single stored record: `{ id, data }`. */
61420
61450
  var SettingsRecordSchema = object({
@@ -62950,6 +62980,17 @@ var LlmImageSchema = object({
62950
62980
  bytes: _instanceof(Uint8Array),
62951
62981
  mimeType: string()
62952
62982
  });
62983
+ /**
62984
+ * Retry policy. `enabled: false` is NOT the same as `maxAttempts: 1` in intent —
62985
+ * the flag is what a consumer table flips, the count is what the operator tunes.
62986
+ * A retry doubles the wall time of a call, so the two gates that run inside a
62987
+ * notification's budget keep it off (see `CONSUMER_RETRY_POLICY` in addon-ai).
62988
+ */
62989
+ var LlmRetryPolicySchema = object({
62990
+ enabled: boolean().default(false),
62991
+ /** Total attempts INCLUDING the first. 1 = no retry. */
62992
+ maxAttempts: number().int().min(1).max(5).default(1)
62993
+ });
62953
62994
  var LlmGenerateBaseInputSchema = object({
62954
62995
  /** Collection routing (the notification-output posture). */
62955
62996
  addonId: string().optional(),
@@ -62964,7 +63005,28 @@ var LlmGenerateBaseInputSchema = object({
62964
63005
  jsonSchema: record(string(), unknown()).optional(),
62965
63006
  /** Per-call override of the profile default. */
62966
63007
  maxTokens: number().int().positive().optional(),
62967
- temperature: number().optional()
63008
+ temperature: number().optional(),
63009
+ /** Per-call override of the profile default (nucleus sampling). */
63010
+ topP: number().min(0).max(1).optional(),
63011
+ /** Per-call override of the profile default (top-k sampling). */
63012
+ topK: number().int().positive().optional(),
63013
+ /** Per-call override of `profile.timeoutMs` — the total generation bound. */
63014
+ timeoutMs: number().int().positive().optional(),
63015
+ /** Per-call override; beats both the consumer table and the profile. */
63016
+ retry: LlmRetryPolicySchema.optional(),
63017
+ /**
63018
+ * Caller-minted id that makes this generation CANCELLABLE.
63019
+ *
63020
+ * Without it a caller that stops waiting cannot stop the work: the gates race
63021
+ * the call against 8 s and free their own slot when the timer wins, while the
63022
+ * generation upstream keeps running to `profile.timeoutMs` — 60 s by default,
63023
+ * on a single-threaded local model. The per-camera bound then counts WAITS,
63024
+ * not generations, and the real load is unbounded.
63025
+ *
63026
+ * `AbortSignal` cannot cross a process boundary; an id can. Pass one here and
63027
+ * `llm.cancel({ requestId })` tears the socket down.
63028
+ */
63029
+ requestId: string().optional()
62968
63030
  });
62969
63031
  /**
62970
63032
  * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
@@ -62977,6 +63039,18 @@ var LlmGenerateBaseInputSchema = object({
62977
63039
  * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
62978
63040
  * watchdog — operator decision #3).
62979
63041
  */
63042
+ /**
63043
+ * A companion artifact that MUST land beside the main GGUF: the `mmproj`
63044
+ * projector of a vision model, or shards 2..N of a split GGUF. Carried on the
63045
+ * REF rather than looked up at install time, so what the operator approved in
63046
+ * the preview is exactly what the node downloads.
63047
+ */
63048
+ var ManagedModelExtraFileSchema = object({
63049
+ url: string(),
63050
+ filename: string(),
63051
+ sizeBytes: number(),
63052
+ sha256: string().optional()
63053
+ });
62980
63054
  var ManagedModelRefSchema = discriminatedUnion("kind", [
62981
63055
  object({
62982
63056
  kind: literal("catalog"),
@@ -62985,7 +63059,11 @@ var ManagedModelRefSchema = discriminatedUnion("kind", [
62985
63059
  object({
62986
63060
  kind: literal("url"),
62987
63061
  url: string(),
62988
- sha256: string().optional()
63062
+ sha256: string().optional(),
63063
+ /** Picker/status label; the file basename when absent. */
63064
+ label: string().optional(),
63065
+ sizeBytes: number().optional(),
63066
+ extraFiles: array(ManagedModelExtraFileSchema).optional()
62989
63067
  }),
62990
63068
  object({
62991
63069
  kind: literal("path"),
@@ -63003,13 +63081,82 @@ var ManagedRuntimeConfigSchema = object({
63003
63081
  gpuLayers: number().int().default(0),
63004
63082
  /** Default: cpus-2, clamped ≥1 (resolved node-side). */
63005
63083
  threads: number().int().optional(),
63006
- /** Concurrent slots. */
63084
+ /** Concurrent slots (`--parallel`). */
63007
63085
  parallel: number().int().default(1),
63086
+ /** Logical batch size (`-b`). Larger = faster prompt ingest, more RAM. */
63087
+ batchSize: number().int().positive().optional(),
63088
+ /** Physical batch / micro-batch (`-ub`). */
63089
+ ubatchSize: number().int().positive().optional(),
63090
+ /**
63091
+ * `--flash-attn`. Cuts KV-cache memory on the backends that implement it and
63092
+ * is a no-op elsewhere, so it is offered rather than assumed.
63093
+ */
63094
+ flashAttention: boolean().default(false),
63095
+ /**
63096
+ * `--mlock`. Pins the weights in RAM so the OS cannot page them out mid
63097
+ * inference. Costs the full model size in resident memory — which is exactly
63098
+ * what the RAM budget is counting.
63099
+ */
63100
+ mlock: boolean().default(false),
63101
+ /**
63102
+ * `--no-mmap`. Reads the whole GGUF up front instead of mapping it. Slower to
63103
+ * start, but avoids the page-fault stalls a network or spinning-disk model
63104
+ * store produces on every first token.
63105
+ */
63106
+ noMmap: boolean().default(false),
63107
+ /** `--cache-type-k` / `--cache-type-v` — quantising the KV cache is the
63108
+ * cheapest way to fit a longer context in the same RAM. */
63109
+ cacheTypeK: _enum([
63110
+ "f32",
63111
+ "f16",
63112
+ "q8_0",
63113
+ "q5_1",
63114
+ "q5_0",
63115
+ "q4_1",
63116
+ "q4_0"
63117
+ ]).optional(),
63118
+ cacheTypeV: _enum([
63119
+ "f32",
63120
+ "f16",
63121
+ "q8_0",
63122
+ "q5_1",
63123
+ "q5_0",
63124
+ "q4_1",
63125
+ "q4_0"
63126
+ ]).optional(),
63127
+ /**
63128
+ * Escape hatch for llama-server flags this schema does NOT model — `--jinja`
63129
+ * (which most vision chat templates need and some language-only models
63130
+ * dislike), `--cont-batching`, `--rope-scaling`, …
63131
+ *
63132
+ * It is NOT a second place to set the flags above. A token that collides
63133
+ * with a typed field is REJECTED at start, naming the field that owns it
63134
+ * (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
63135
+ * the "two switches that disagree" failure this repo has already shipped
63136
+ * twice (D62).
63137
+ */
63138
+ extraArgs: array(string()).default([]),
63008
63139
  /** Else lazy: first generate boots it. */
63009
63140
  autoStart: boolean().default(false),
63010
63141
  /** 0 = never; frees RAM after quiet periods. */
63011
63142
  idleStopMinutes: number().int().default(30)
63012
63143
  });
63144
+ /**
63145
+ * Where a multi-GB install currently is. A single 0..1 fraction cannot answer
63146
+ * "is it stuck?" for an install that is three files (shards + mmproj) followed
63147
+ * by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
63148
+ * node looked hung. Phase + file + bytes is the smallest shape that does.
63149
+ */
63150
+ var LlmDownloadProgressSchema = object({
63151
+ phase: _enum(["downloading", "verifying"]),
63152
+ /** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
63153
+ file: string(),
63154
+ fileIndex: number().int(),
63155
+ fileCount: number().int(),
63156
+ /** Across the WHOLE install, not the current file. */
63157
+ downloadedBytes: number(),
63158
+ totalBytes: number().optional()
63159
+ });
63013
63160
  var LlmRuntimeStatusSchema = object({
63014
63161
  /** Status is ALWAYS node-qualified. */
63015
63162
  nodeId: string(),
@@ -63026,6 +63173,8 @@ var LlmRuntimeStatusSchema = object({
63026
63173
  modelPath: string().optional(),
63027
63174
  modelId: string().optional(),
63028
63175
  downloadProgress: number().min(0).max(1).optional(),
63176
+ /** Detail behind `downloadProgress`; present for the same lifetime. */
63177
+ download: LlmDownloadProgressSchema.optional(),
63029
63178
  lastError: string().optional(),
63030
63179
  crashesInWindow: number(),
63031
63180
  /** Child RSS (sampled best-effort). */
@@ -63036,7 +63185,14 @@ var LlmNodeModelSchema = object({
63036
63185
  file: string(),
63037
63186
  sizeBytes: number(),
63038
63187
  catalogId: string().optional(),
63039
- installedAt: number().optional()
63188
+ installedAt: number().optional(),
63189
+ /**
63190
+ * Absolute path on the node. Present so a file that is on disk but matches
63191
+ * no catalog entry — a custom Hugging Face install, or a GGUF the operator
63192
+ * copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
63193
+ * it the picker could list such a file and do nothing with it.
63194
+ */
63195
+ path: string().optional()
63040
63196
  });
63041
63197
  var LlmRuntimeDiskUsageSchema = object({
63042
63198
  nodeId: string(),
@@ -63092,10 +63248,47 @@ var LlmProfileSchema = object({
63092
63248
  baseUrl: string().optional(),
63093
63249
  /** ConfigUISchema type:'password' — never round-trips (spec §5). */
63094
63250
  apiKey: string().optional(),
63251
+ /** Vision on/off. A vision call against a `false` profile is REFUSED, never
63252
+ * degraded to text — that shipped once and produced a confident answer to a
63253
+ * question about a picture nobody sent. */
63095
63254
  supportsVision: boolean(),
63096
63255
  temperature: number().min(0).max(2).optional(),
63256
+ /** Nucleus sampling. Every wire we speak has it. */
63257
+ topP: number().min(0).max(1).optional(),
63258
+ /** Top-k sampling. Carried only by the wires that have it — NEITHER OpenAI
63259
+ * wire does, and the client drops it there (measured: the request body gets
63260
+ * `top_p` and no `top_k`). The profile editor hides the field wherever it
63261
+ * would change nothing; `KINDS_WITH_TOP_K` is the single owner of that list. */
63262
+ topK: number().int().positive().optional(),
63097
63263
  maxTokens: number().int().positive().optional(),
63264
+ /** Prompt context window. Advisory for cloud kinds (they enforce their own);
63265
+ * for `managed-local` it is the llama.cpp `--ctx-size` the runtime starts
63266
+ * the model with, so it is the one field that changes a PROCESS. */
63267
+ contextLength: number().int().positive().optional(),
63268
+ /** Default system prompt. A caller's `system` REPLACES it (never appends —
63269
+ * two system prompts fighting is worse than either alone). */
63270
+ systemPrompt: string().optional(),
63271
+ /** Total generation bound — the only one a unary call has. */
63098
63272
  timeoutMs: number().int().positive().default(6e4),
63273
+ /** The TCP handshake only — "is the port even open". NOT the wait for
63274
+ * response headers: on the LM Studio / llama-server wire those are written
63275
+ * once the model has finished loading, so they belong to the bound below. */
63276
+ connectTimeoutMs: number().int().positive().default(1e4),
63277
+ /** Accepted, but no output yet — response headers included, because a cold
63278
+ * GPU load is exactly what happens before them. */
63279
+ firstTokenTimeoutMs: number().int().positive().default(12e4),
63280
+ /** Output started then stopped. */
63281
+ idleTimeoutMs: number().int().positive().default(6e4),
63282
+ /** Profile-level default. The per-consumer table and a per-call override
63283
+ * both beat it — see `resolveRetryPolicy`. */
63284
+ retry: LlmRetryPolicySchema.default({
63285
+ enabled: false,
63286
+ maxAttempts: 1
63287
+ }),
63288
+ /** Whether this profile may use tools. The tool-call plumbing rides the
63289
+ * library; the REGISTRY of callable tools is ours and is empty in v1, so a
63290
+ * `true` here buys the wiring, not behaviour, until tools are registered. */
63291
+ toolsEnabled: boolean().default(false),
63099
63292
  extraHeaders: record(string(), string()).optional(),
63100
63293
  /** kind === 'managed-local' only (spec §4). */
63101
63294
  runtime: ManagedRuntimeConfigSchema.optional()
@@ -63145,6 +63338,36 @@ var ManagedModelCatalogEntrySchema = object({
63145
63338
  /** Vision models: companion projector file. */
63146
63339
  mmprojUrl: string().optional()
63147
63340
  });
63341
+ /**
63342
+ * The outcome of turning one operator-typed Hugging Face reference into a
63343
+ * download plan. A RESULT, never a throw: "this repo has 24 quantizations and
63344
+ * I will not pick for you" is a normal answer the UI has to render, not an
63345
+ * exception.
63346
+ *
63347
+ * `candidates` is the whole reason the refusal is usable — every string in it
63348
+ * is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
63349
+ */
63350
+ var HfModelResolutionSchema = discriminatedUnion("ok", [object({
63351
+ ok: literal(true),
63352
+ /** Ready to hand to `installModel` unchanged. */
63353
+ model: ManagedModelRefSchema,
63354
+ label: string(),
63355
+ repo: string(),
63356
+ quantization: string(),
63357
+ purpose: _enum(["text", "vision"]),
63358
+ totalBytes: number(),
63359
+ /** mmproj + shards, for the preview: an operator approving 23 GB should
63360
+ * see that 0.9 GB of it is a projector they did not name. */
63361
+ extraFilenames: array(string())
63362
+ }), object({
63363
+ ok: literal(false),
63364
+ code: string(),
63365
+ message: string(),
63366
+ candidates: array(string()).optional(),
63367
+ /** Set when the refusal was only the ceiling: re-calling with
63368
+ * `maxBytes: requiredBytes` is the operator's explicit override. */
63369
+ requiredBytes: number().optional()
63370
+ })]);
63148
63371
  var LlmRuntimeNodeSchema = object({
63149
63372
  nodeId: string(),
63150
63373
  reachable: boolean(),
@@ -63157,7 +63380,10 @@ var ProfileRefInputSchema = object({
63157
63380
  addonId: string(),
63158
63381
  profileId: string()
63159
63382
  });
63160
- method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
63383
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({
63384
+ addonId: string().optional(),
63385
+ requestId: string()
63386
+ }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
63161
63387
  kind: "mutation",
63162
63388
  auth: "admin"
63163
63389
  }), method(ProfileRefInputSchema, _void(), {
@@ -63178,6 +63404,15 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
63178
63404
  consumer: string().optional(),
63179
63405
  profileId: string().optional()
63180
63406
  }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
63407
+ /** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
63408
+ * `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
63409
+ ref: string(),
63410
+ /** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
63411
+ maxBytes: number().positive().optional()
63412
+ }), HfModelResolutionSchema, {
63413
+ kind: "mutation",
63414
+ auth: "admin"
63415
+ }), method(object({
63181
63416
  nodeId: string(),
63182
63417
  model: ManagedModelRefSchema
63183
63418
  }), _void(), {
@@ -63737,11 +63972,33 @@ var NotificationFormatSchema = _enum([
63737
63972
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
63738
63973
  * renderer's icon set; "acknowledge" survives an adapter that draws it
63739
63974
  * differently.
63975
+ *
63976
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
63977
+ *
63978
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
63979
+ * the whole set onto its own renderer's vocabulary through a
63980
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
63981
+ * fallback, so adding a member here fails every adapter's build until someone
63982
+ * decides its glyph, which is the only place that decision can be made
63983
+ * honestly.
63984
+ *
63985
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
63986
+ * `disarm` straight through; iOS feeds that string to
63987
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
63988
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
63989
+ * mapping, and "the field is documented" is not "the value renders".
63990
+ *
63991
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
63992
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
63993
+ * so an addon that emits a token the running hub does not know does not lose an
63994
+ * icon — its whole `send` fails Zod validation and the notification never
63995
+ * arrives. Never emit a new token from an addon before the train carrying it.
63740
63996
  */
63741
63997
  var NotificationActionIconSchema = _enum([
63742
63998
  "acknowledge",
63743
63999
  "dismiss",
63744
64000
  "silence",
64001
+ "snooze",
63745
64002
  "view",
63746
64003
  "play",
63747
64004
  "open",
@@ -63749,9 +64006,13 @@ var NotificationActionIconSchema = _enum([
63749
64006
  "lock",
63750
64007
  "unlock",
63751
64008
  "arm",
64009
+ "arm-home",
64010
+ "arm-away",
64011
+ "arm-night",
63752
64012
  "disarm",
63753
64013
  "light",
63754
- "alert"
64014
+ "alert",
64015
+ "camera"
63755
64016
  ]);
63756
64017
  /** A single tap-through action button. */
63757
64018
  var NotificationActionSchema = object({
@@ -63951,6 +64212,24 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
63951
64212
  targetId: string(),
63952
64213
  enabled: boolean()
63953
64214
  }), _void(), { kind: "mutation" });
64215
+ new Set([
64216
+ {
64217
+ id: "person",
64218
+ name: "Person"
64219
+ },
64220
+ {
64221
+ id: "vehicle",
64222
+ name: "Vehicle"
64223
+ },
64224
+ {
64225
+ id: "animal",
64226
+ name: "Animal"
64227
+ },
64228
+ {
64229
+ id: "package",
64230
+ name: "Package"
64231
+ }
64232
+ ].map((l) => l.id));
63954
64233
  var COCO_TO_MACRO = {
63955
64234
  mapping: {
63956
64235
  person: "person",
@@ -64781,11 +65060,15 @@ var NcSystemEventKindSchema = _enum([
64781
65060
  "stream-offline",
64782
65061
  "node-online",
64783
65062
  "node-offline",
65063
+ "node-inference-unavailable",
65064
+ "detection-blind",
64784
65065
  "addon-update-available",
64785
65066
  "server-update-available",
64786
65067
  "alarm-triggered",
64787
65068
  "alarm-armed",
64788
65069
  "alarm-disarmed",
65070
+ "alarm-arming",
65071
+ "alarm-arm-refused",
64789
65072
  "camera-online",
64790
65073
  "camera-offline",
64791
65074
  "camera-disabled",
@@ -64840,7 +65123,16 @@ var NcScheduleSchema = object({
64840
65123
  });
64841
65124
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
64842
65125
  var NcPlateMatcherSchema = object({
64843
- values: array(string().min(1)).min(1),
65126
+ /**
65127
+ * Plate texts (or gallery vehicle names) to match. EMPTY = **any plate the
65128
+ * pipeline could read** — the plate half of "no selection = no narrowing",
65129
+ * and the switch that says this rule is about vehicles that were IDENTIFIED
65130
+ * rather than merely seen. A subject carrying no plate still fails.
65131
+ *
65132
+ * The `.min(1)` this used to carry made that state unauthorable; nothing has
65133
+ * ever persisted an empty list, so widening it cannot change an existing rule.
65134
+ */
65135
+ values: array(string().min(1)),
64844
65136
  /** Max Levenshtein distance after normalization (uppercase alphanumeric). */
64845
65137
  maxDistance: number().int().min(0).max(3).default(1)
64846
65138
  });
@@ -64874,28 +65166,36 @@ var NcOccupancyConditionSchema = object({
64874
65166
  /**
64875
65167
  * Audio condition (IMMEDIATE trigger) — a rule on SOUND, not on a picture.
64876
65168
  *
64877
- * Operator-approved vocabulary (2026-08-12, option A — the same one the
64878
- * reference notifier uses, so an operator moving between them re-uses what
64879
- * they already know): a rule matches when, over a sampling window of
64880
- * `samplingSeconds`, at least `hitPercent`% of the audio samples in that
64881
- * window are HITS. A sample is a hit when it satisfies BOTH present filters:
65169
+ * **TWO EXCLUSIVE MODES** (operator decision 2026-08-14, D157). Which one a
65170
+ * rule is in is not a stored field it is WHICH FILTER the rule carries, so
65171
+ * there is no second switch that can disagree with the first and every rule
65172
+ * authored before the decision migrates for free (`audioModeOf`):
64882
65173
  *
64883
- * - `dbThreshold`its level is at or above this many dBFS (see
64884
- * {@link NC_AUDIO_DBFS_FLOOR}: negative-going, `0` = full scale);
64885
- * - `labels` the classifier put at least one of these labels on it.
65174
+ * - **LABEL mode `labels` present.** The rule fires on the FIRST frame the
65175
+ * classifier labels with one of them. No window, no percentage:
65176
+ * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
65177
+ * `throttle` cooldown is the only brake. The per-label confidence floor is
65178
+ * the analyzer's (`classificationMinScore`, per device) — a label only
65179
+ * reaches this condition if the classifier was already confident enough.
65180
+ * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
65181
+ * the condition: at least `hitPercent`% of the samples over
65182
+ * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
65183
+ * {@link NC_AUDIO_DBFS_FLOOR}: negative-going, `0` = full scale). The window
65184
+ * must be FULL before it can match — a window open for two of its ten
65185
+ * seconds is 100% of nothing.
64886
65186
  *
64887
- * Both are OPTIONAL and independent, which is the point of the shape: a
64888
- * loudness rule ("something loud at 3am") needs no model to be right, and a
64889
- * label rule ("a dog barked") needs no threshold. **Fail-closed when NEITHER
64890
- * is given** a window in which every sample is trivially a hit would fire on
64891
- * silence, so the engine refuses such a condition rather than notifying on
64892
- * nothing (the schema cannot express "at least one of" without becoming a
64893
- * ZodEffects the cap path would have to special-case).
65187
+ * **Why label mode has no window.** It had one, and it never fired: the
65188
+ * analyzer emits ~1 audio frame per second but YAMNet only LABELS one to three
65189
+ * of them per episode, even through continuous crying. The measured maximum
65190
+ * `hitPercent` over the whole live history was 40 under the shipped default
65191
+ * of 60, so a label rule could not fire at all, ever. A percentage of frames is
65192
+ * the wrong question to ask of a sparse classifier.
64894
65193
  *
64895
- * `hitPercent` is over the samples the window actually HOLDS, and the window
64896
- * must be FULL before it can match a window that has been open for two
64897
- * seconds of its ten is 100% of nothing, and firing on it would make
64898
- * `samplingSeconds` decorative.
65194
+ * **Fail-closed when NEITHER is given** every sample would be a trivial hit
65195
+ * and the rule would fire on silence. The schema cannot express "exactly one
65196
+ * of" without becoming a ZodEffects the cap path would have to special-case, so
65197
+ * the exclusivity is enforced where every editor writes (`patchAudio`) and a
65198
+ * legacy rule carrying both resolves to LABEL (the mode that fires).
64899
65199
  *
64900
65200
  * Labels are the audio macro classes (`AUDIO_MACRO_LABELS` / the NC taxonomy's
64901
65201
  * `audio-*` ids). Both spellings are accepted — the matcher normalizes the
@@ -64903,13 +65203,13 @@ var NcOccupancyConditionSchema = object({
64903
65203
  * an operator who typed `dog` mean the same thing.
64904
65204
  */
64905
65205
  var NcAudioConditionSchema = object({
64906
- /** Audio macro labels; absent = any sound (level-only rule). */
65206
+ /** LABEL MODE: audio macro labels. Present fires on the first labelled frame. */
64907
65207
  labels: array(string().min(1)).min(1).optional(),
64908
- /** Level floor in dBFS (negative-going, `0` = full scale); absent = any level. */
65208
+ /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
64909
65209
  dbThreshold: number().min(-96).max(0).optional(),
64910
- /** Percentage of the window's samples that must be hits (1–100). */
65210
+ /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
64911
65211
  hitPercent: number().int().min(1).max(100).default(60),
64912
- /** Length of the sampling window in seconds. */
65212
+ /** LEVEL MODE ONLY: length of the sampling window in seconds. */
64913
65213
  samplingSeconds: number().int().min(1).max(300).default(10)
64914
65214
  });
64915
65215
  /**
@@ -65047,13 +65347,81 @@ var NcRuleActionsSchema = object({
65047
65347
  */
65048
65348
  buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
65049
65349
  });
65350
+ /**
65351
+ * "This rule applies only while `deviceId` is in one of `states`."
65352
+ *
65353
+ * The states are the DEVICE's own vocabulary — `AlarmState` for a panel,
65354
+ * `on`/`off` for a switch — not a normalised set, because normalising would
65355
+ * make the condition lie about devices whose states have no equivalent.
65356
+ *
65357
+ * An unreadable state does NOT match: see the engine's fail-closed gate. A
65358
+ * condition that fired on "I could not read it" would be worse than no gate.
65359
+ */
65360
+ var NcDeviceStateConditionSchema = object({
65361
+ deviceId: number().int(),
65362
+ /** Any of these matches. */
65363
+ states: array(string().min(1)).min(1)
65364
+ });
65365
+ /**
65366
+ * "This rule applies only while scene `sceneId` is `matched` / `diverged`."
65367
+ *
65368
+ * A GATE, not a trigger. `occupancy` and `audio` each DISCRIMINATE their rule —
65369
+ * carrying one makes the rule fire on that subject and nothing else. Scene is
65370
+ * the other shape entirely, the `deviceState` shape: it narrows a rule that
65371
+ * already has a trigger ("tell me about a person at the front door, but only
65372
+ * while the bin is still out"). That is why it composes with every delivery
65373
+ * instead of owning one, and why no new `NcDelivery` member and no new subject
65374
+ * kind exist for it — see D159.
65375
+ *
65376
+ * ── Identity ───────────────────────────────────────────────────────────────
65377
+ * `sceneId` is `SceneMonitor.id`, a `randomUUID()` minted by `createScene` —
65378
+ * globally unique, so it needs no device to disambiguate it. `deviceId` is
65379
+ * carried as a HINT for the editor and for the log line, never as part of the
65380
+ * lookup key: a rule whose hint drifted must still gate correctly.
65381
+ *
65382
+ * ── Which boolean ──────────────────────────────────────────────────────────
65383
+ * `latched` ABSENT means "whatever the scene itself says" — `SceneMonitor.emit`
65384
+ * already declares which boolean drives notification rules, and a second knob
65385
+ * that could disagree with it is exactly the D62 failure. Set it only to
65386
+ * override one rule against the scene's own default.
65387
+ *
65388
+ * - LIVE reading (`emit`/`latched` resolve to live): passes iff
65389
+ * `verdict === requiredState`. `unknown` — no reference for this light, view
65390
+ * shifted, no snapshot — passes NEITHER. A scene that cannot judge is not
65391
+ * evidence, in either direction.
65392
+ * - LATCHED reading: passes iff `latched === (requiredState === 'diverged')`.
65393
+ * The latch is a durable fact about the past ("it has diverged since I armed
65394
+ * it"), so a camera that has gone dark does not clear it — that is the whole
65395
+ * reason the operator asked for a latch.
65396
+ *
65397
+ * The gate reads an in-memory mirror (`NcSceneStateCache`) refreshed OFF the
65398
+ * event path, never the cap: D49. A mirror that has never loaded, or a scene it
65399
+ * does not carry, reads absent and the rule does NOT fire — fail closed, and
65400
+ * said out loud in the log rather than dropped in silence.
65401
+ */
65402
+ var NcSceneConditionSchema = object({
65403
+ /** `SceneMonitor.id` — the uuid the cap mints. The whole lookup key. */
65404
+ sceneId: string().min(1),
65405
+ /** The camera the scene lives on. A hint for the editor and the log line. */
65406
+ deviceId: number().int().optional(),
65407
+ /** The state the scene must be in for the rule to fire. */
65408
+ requiredState: _enum(["matched", "diverged"]),
65409
+ /**
65410
+ * Read the LATCH (`true`) or the LIVE verdict (`false`). Absent = follow the
65411
+ * scene's own `emit` field, which is the only place that decision belongs.
65412
+ */
65413
+ latched: boolean().optional()
65414
+ });
65050
65415
  var NcConditionsSchema = object({
65051
65416
  /** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
65052
- deviceState: object({
65053
- deviceId: number().int(),
65054
- /** Any of these matches. */
65055
- states: array(string().min(1)).min(1)
65056
- }).optional(),
65417
+ deviceState: NcDeviceStateConditionSchema.optional(),
65418
+ /**
65419
+ * Gate on a SCENE's state — "only while the bin is still out". Composes with
65420
+ * every trigger (detection, occupancy, audio, sensor, package, track-end);
65421
+ * unlike `occupancy`/`audio` it discriminates nothing. See
65422
+ * {@link NcSceneCondition} and D159.
65423
+ */
65424
+ scene: NcSceneConditionSchema.optional(),
65057
65425
  /** Device scope — absent = all devices. */
65058
65426
  devices: array(number()).optional(),
65059
65427
  /** Detector class names (any overlap with the record's class set). */
@@ -65079,18 +65447,47 @@ var NcConditionsSchema = object({
65079
65447
  */
65080
65448
  labelEquals: array(string().min(1)).optional(),
65081
65449
  /**
65082
- * Identity matcher. P1 boundary: matched against the record's collapsed
65083
- * `label` (the identity display name propagated by the face pipeline) —
65084
- * identity-ID matching rides in P2 when identity ids reach the record.
65450
+ * KNOWN FACES the rule's identity scope, and the switch that says the rule
65451
+ * is about recognised people at all.
65452
+ *
65453
+ * Three states, and the empty one is the point:
65454
+ *
65455
+ * | value | meaning |
65456
+ * | --- | --- |
65457
+ * | absent | the rule does not care who it is; an unrecognised person matches |
65458
+ * | `[]` | **only known faces** — any identity in the gallery, nobody in particular |
65459
+ * | a list | only these identities |
65460
+ *
65461
+ * `[]` is the repo-wide "no selection = no narrowing" reading (an absent
65462
+ * `devices` list is every device), applied one level down: the operator has
65463
+ * turned the face scope ON and narrowed it to nothing, which is every known
65464
+ * face. No second field states the same thing — a switch that can disagree
65465
+ * with the list under it is worse than no switch (D62).
65466
+ *
65467
+ * MEMBERS ARE FACE-GALLERY `Identity.id`s (uuid), not display names. A name is
65468
+ * renameable, and a rule authored on "Gianluca" went silently dark the moment
65469
+ * the operator fixed the spelling. The id reaches the record on
65470
+ * `LabelAttribution.identityId`; the name is what the editor shows and what
65471
+ * `{{label}}` renders.
65472
+ *
65473
+ * Rules written before this carry NAMES, and are resolved to ids lazily at
65474
+ * load (`NcRuleStore.load`) against the live gallery — a name nothing answers
65475
+ * for is left as it stands and reported, never dropped. The engine also
65476
+ * accepts a display-name hit as a compatibility leg, so a rule whose
65477
+ * migration could not resolve keeps matching exactly what it matched before.
65085
65478
  */
65086
65479
  identities: array(string().min(1)).optional(),
65087
- /** Fuzzy plate matcher against the record's `label` (plate text). */
65480
+ /**
65481
+ * KNOWN PLATES / VEHICLES — the plate mirror of {@link identities}, including
65482
+ * the empty-list reading: `values: []` is "any plate the OCR could read",
65483
+ * a non-empty list is those plates (fuzzily). See {@link NcPlateMatcherSchema}.
65484
+ */
65088
65485
  plates: NcPlateMatcherSchema.optional(),
65089
65486
  /**
65090
- * Identity EXCLUDE — mirror of {@link identities} with `notIn` semantics.
65091
- * Same P1 boundary: matched against the record's collapsed `label` (the
65092
- * identity display name). A record with NO label passes (nothing to
65093
- * exclude), unlike the include variant which fails on an absent label.
65487
+ * Identity EXCLUDE — mirror of {@link identities} with `notIn` semantics, and
65488
+ * the same id members and the same lazy name→id migration. A record with NO
65489
+ * identity passes (nothing to exclude), unlike the include variant which
65490
+ * fails on an unrecognised subject. An EMPTY list excludes nobody.
65094
65491
  */
65095
65492
  identitiesExclude: array(string().min(1)).optional(),
65096
65493
  /**
@@ -65482,7 +65879,80 @@ var NcRuleInputSchema = object({
65482
65879
  * a rule that predates the gate must keep delivering byte-for-byte as it
65483
65880
  * did, and absent is the only way to say that without a migration.
65484
65881
  */
65485
- confirm: NcConfirmSchema.optional()
65882
+ confirm: NcConfirmSchema.optional(),
65883
+ /**
65884
+ * WAIT for face/plate recognition before saying anything.
65885
+ *
65886
+ * A notification's TEXT is frozen at enqueue and its media is re-resolved at
65887
+ * send; the identity is neither. A face is confirmed after `confirmFrames`
65888
+ * agreeing observations — p50 **11.4 s** after the track was first seen,
65889
+ * measured on this hub — and an `immediate` rule enqueues on the first object
65890
+ * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
65891
+ * immediate path, and no amount of media re-resolution fixes a sentence.
65892
+ *
65893
+ * Only two honest answers exist, and this flag picks between them. It has
65894
+ * effect ONLY on a rule that declares a recognition scope
65895
+ * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
65896
+ * other rule there is nothing to wait for and the flag is inert.
65897
+ *
65898
+ * | value | what happens |
65899
+ * | --- | --- |
65900
+ * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
65901
+ * | absent / `false` | it fires at once WITHOUT the name, and if recognition lands before the track closes a SECOND, "…is Gianluca" notification follows (one per track, per rule, per target) |
65902
+ *
65903
+ * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
65904
+ * on the addon cap path, and absent has to keep meaning exactly what every
65905
+ * rule authored before this field meant.
65906
+ *
65907
+ * The cost of `true` is stated here because the editor states it too: a rule
65908
+ * that waits also inherits track-close SEMANTICS — its `zones` condition
65909
+ * tests every zone the track visited and a `crossing` condition can no longer
65910
+ * be satisfied, because a closed track carries no crossing.
65911
+ */
65912
+ waitForEnhancement: boolean().optional(),
65913
+ /**
65914
+ * GROUP a burst of subjects into ONE notification that grows.
65915
+ *
65916
+ * Seconds of quiet after the last matching subject before the burst is
65917
+ * considered over. While it is open, the first subject enqueues immediately —
65918
+ * **exactly as today, with no added latency** — and every real growth (a new
65919
+ * subject, or a name confirmed on one already in it) REPLACES that
65920
+ * notification with an updated one naming everybody. The push carries the
65921
+ * group's own coalescing tag, so the phone replaces rather than stacks.
65922
+ *
65923
+ * `0` / absent = off, and off is today's behaviour byte for byte.
65924
+ *
65925
+ * ### Why an idle cutoff and not a window
65926
+ *
65927
+ * The measured seven-person arrival on device 590 spans 110 s with every
65928
+ * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
65929
+ * idle cutoff holds it as one and ends it when the arrival actually ends.
65930
+ * 30 is Frigate's shipped value for the same decision.
65931
+ *
65932
+ * ### What it replaces
65933
+ *
65934
+ * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
65935
+ * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
65936
+ * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
65937
+ * track that did fire and 7 carrying a confirmed identity nobody heard about.
65938
+ * A group collapses the same volume by MERGING, so the cooldown becomes a
65939
+ * budget over GROUPS — which is what it always meant — and a growth is never
65940
+ * throttled by the window its own first member spent.
65941
+ *
65942
+ * ### Interaction with {@link waitForEnhancement}
65943
+ *
65944
+ * They compose, and the order matters. `waitForEnhancement` defers the rule to
65945
+ * TRACK CLOSE, so with both set the group is opened by the first member to
65946
+ * CLOSE — already carrying its name — and grows as later members close. That
65947
+ * is later, and complete. With grouping alone the group opens on the first
65948
+ * object event and picks up names as they are confirmed, through the growth
65949
+ * path. Neither combination fires twice for one subject.
65950
+ *
65951
+ * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
65952
+ * on the addon cap path, so absent must keep meaning what it meant before this
65953
+ * field existed.
65954
+ */
65955
+ groupIdleSec: number().int().min(0).max(600).optional()
65486
65956
  });
65487
65957
  /**
65488
65958
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -65589,6 +66059,7 @@ var NcConditionDescriptorSchema = object({
65589
66059
  "occupancy",
65590
66060
  "audio",
65591
66061
  "deviceState",
66062
+ "scene",
65592
66063
  "systemEvent"
65593
66064
  ]),
65594
66065
  operator: _enum([
@@ -65994,7 +66465,87 @@ var MethodAccessSchema = _enum([
65994
66465
  var AllowedProviderSchema = union([literal("*"), array(string())]);
65995
66466
  var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
65996
66467
  var CapScopeSchema = _enum(["device", "system"]);
65997
- var TokenScopeSchema = discriminatedUnion("type", [
66468
+ /**
66469
+ * DeviceSelector (scope model v3 — 2026-08-12).
66470
+ *
66471
+ * A `device` grant no longer carries a frozen list of deviceIds. It carries
66472
+ * a SELECTOR the matcher resolves against the live fleet, so the grant can be
66473
+ * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
66474
+ * AFTER the grant was minted — no re-grant, no re-login.
66475
+ *
66476
+ * - `all` — every device in the deployment. The broad viewer/operator
66477
+ * lever without a `category` grant (a `category` grant also covers device
66478
+ * caps that carry no deviceId; `all` is specifically the device set).
66479
+ * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
66480
+ * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
66481
+ * is NOT covered until the grant is edited.
66482
+ * - `types` — every device of a `DeviceType` (e.g. every `camera`).
66483
+ * DYNAMIC. A device that changes type, or a new device of the type,
66484
+ * re-resolves on the next request.
66485
+ * - `locations` — every device whose operator-assigned `location` label is
66486
+ * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
66487
+ * null/unset location matches NO `locations` selector.
66488
+ */
66489
+ var DeviceSelectorSchema = discriminatedUnion("kind", [
66490
+ object({ kind: literal("all") }),
66491
+ object({
66492
+ kind: literal("ids"),
66493
+ ids: array(number().int()).min(1)
66494
+ }),
66495
+ object({
66496
+ kind: literal("types"),
66497
+ types: array(_enum(DeviceType)).min(1)
66498
+ }),
66499
+ object({
66500
+ kind: literal("locations"),
66501
+ locations: array(string().min(1)).min(1)
66502
+ })
66503
+ ]);
66504
+ var DeviceTokenScopeSchema = object({
66505
+ type: literal("device"),
66506
+ /** The device SET this grant covers — resolved against the live fleet. */
66507
+ selector: DeviceSelectorSchema,
66508
+ access: array(MethodAccessSchema).min(1),
66509
+ /**
66510
+ * Whether a grant on a PARENT device transparently covers its accessory
66511
+ * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
66512
+ * Direction is parent → children ONLY.
66513
+ *
66514
+ * Absent → the matcher DERIVES it from the access flavour: `view`
66515
+ * inherits (a camera viewer sees the camera's accessories), `create` /
66516
+ * `delete` do NOT (actuating/removing a child is an explicit act the
66517
+ * operator must grant on the child, not inherit from the parent). Set it
66518
+ * explicitly to override that default per grant.
66519
+ */
66520
+ includeLinked: boolean().optional()
66521
+ });
66522
+ /**
66523
+ * v2 → v3 lazy migration. A pre-v3 `device` grant carried
66524
+ * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
66525
+ * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
66526
+ * EVERY parse path — stored records AND the JWT-carried scope arrays
66527
+ * normalised at the request boundary ({@link normalizeTokenScopes} in
66528
+ * `device-selector.ts`). Chosen over a one-time DB migration because a
66529
+ * migration cannot reach a JWT already in a client's hands; parse-time
66530
+ * migration covers both without a flag day. No cast — the raw object is read
66531
+ * through `Reflect.get` (its static type is `unknown`).
66532
+ */
66533
+ function migrateLegacyTokenScope(raw) {
66534
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
66535
+ if (Reflect.get(raw, "type") !== "device") return raw;
66536
+ if (Reflect.get(raw, "selector") !== void 0) return raw;
66537
+ const targets = Reflect.get(raw, "targets");
66538
+ if (!Array.isArray(targets)) return raw;
66539
+ return {
66540
+ type: "device",
66541
+ selector: {
66542
+ kind: "ids",
66543
+ ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
66544
+ },
66545
+ access: Reflect.get(raw, "access")
66546
+ };
66547
+ }
66548
+ var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
65998
66549
  object({
65999
66550
  type: literal("category"),
66000
66551
  target: CapScopeSchema,
@@ -66010,18 +66561,8 @@ var TokenScopeSchema = discriminatedUnion("type", [
66010
66561
  target: string(),
66011
66562
  access: array(MethodAccessSchema).min(1)
66012
66563
  }),
66013
- object({
66014
- type: literal("device"),
66015
- /**
66016
- * One or more deviceIds (serialised as strings for wire-format
66017
- * consistency with the rest of the union). Matcher accepts if
66018
- * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
66019
- * of one scope-per-device when granting access to a set of cameras.
66020
- */
66021
- targets: array(string()).min(1),
66022
- access: array(MethodAccessSchema).min(1)
66023
- })
66024
- ]);
66564
+ DeviceTokenScopeSchema
66565
+ ]));
66025
66566
  object({
66026
66567
  id: string(),
66027
66568
  username: string(),
@@ -66338,7 +66879,7 @@ var TrackEnvelopeSchema = object({
66338
66879
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
66339
66880
  * keeps every scalar the list surfaces actually render (ids, class(es),
66340
66881
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
66341
- * zonesVisited, bestEventId, envelope, hasFace) and returns `positions` /
66882
+ * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
66342
66883
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
66343
66884
  * `getTrack`. Mirrors the event-store `projection` convention
66344
66885
  * (`getObjectEvents` et al.).
@@ -66474,7 +67015,21 @@ union([literal(1), literal(2)]);
66474
67015
  var LabelAttributionSchema = object({
66475
67016
  stepId: string(),
66476
67017
  modelId: string().optional(),
66477
- decidedAt: number()
67018
+ decidedAt: number(),
67019
+ /**
67020
+ * The GALLERY id behind a recognised tier-2 label — a face-gallery
67021
+ * `Identity.id` or a plate-gallery `Vehicle.id` (both `randomUUID`).
67022
+ *
67023
+ * The text alone is a DISPLAY NAME, and a display name is renameable: a
67024
+ * notification rule authored on "Gianluca" stopped matching the moment the
67025
+ * operator fixed the spelling in the gallery, and nothing said so. The id is
67026
+ * the thing that does not move, so it is what a rule matches on
67027
+ * (`NcConditions.identities`) and the text is what a human is shown.
67028
+ *
67029
+ * Absent when the label names no gallery row — a plate the OCR read but no
67030
+ * vehicle claims, a sub-class, a species, any tier-1 value.
67031
+ */
67032
+ identityId: string().optional()
66478
67033
  });
66479
67034
  /**
66480
67035
  * The TIERED label model (roadmap 4g), spread into `TrackSchema` and
@@ -66611,6 +67166,28 @@ var TrackSchema = object({
66611
67166
  * `=== true` and render nothing otherwise, never infer "no face".
66612
67167
  */
66613
67168
  hasFace: boolean().optional(),
67169
+ /**
67170
+ * This subject CONTAINS a folded rider — a person the rider-pairing step
67171
+ * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
67172
+ * so the passage is tracked once and as a VEHICLE.
67173
+ *
67174
+ * It exists because the fold's record was dishonest. D34 and the code both
67175
+ * said "the person is not lost — it is reported so both entities stay on the
67176
+ * record"; in fact the pair went into a per-processor RAM field behind an
67177
+ * accessor nobody called, and every durable surface said `vehicle`, full
67178
+ * stop. This is the composition note that makes the row true.
67179
+ *
67180
+ * A COMPOSITION, never a class and never a label. "This vehicle contains a
67181
+ * person" is not an answer to "what is this" — both label tiers would refuse
67182
+ * a macro token anyway (D89), and correctly. Nothing here changes what the
67183
+ * subject IS: a cyclist stays one vehicle track, occupancy still counts one,
67184
+ * and a `person` rule still does not fire for someone cycling past.
67185
+ *
67186
+ * **Absent ≠ false**, exactly like {@link hasFace}: every row written before
67187
+ * the column, and every hub that predates the field, omits it. Test
67188
+ * `=== true` and render nothing otherwise — never infer "no rider".
67189
+ */
67190
+ hasRider: boolean().optional(),
66614
67191
  ...TrackFlagFields,
66615
67192
  ...TrackRetrainFields
66616
67193
  });
@@ -66960,7 +67537,10 @@ var RecentTracksQueryInput = object({
66960
67537
  * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
66961
67538
  cursor: string().optional(),
66962
67539
  /** See {@link TrackProjectionSchema}. Default `full`. */
66963
- projection: TrackProjectionSchema.optional()
67540
+ projection: TrackProjectionSchema.optional(),
67541
+ /** Include stationary-promoted rows (parked objects). Default false: the
67542
+ * feed lists passages; parking records live on the stationary registry. */
67543
+ includeStationary: boolean().optional()
66964
67544
  });
66965
67545
  var RecentTracksPageSchema = object({
66966
67546
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -67178,7 +67758,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
67178
67758
  zone: TrackZoneFilterSchema.optional(),
67179
67759
  /** See {@link TrackProjectionSchema}. Default `full` (backward
67180
67760
  * compatible — omitting the field keeps today's exact behaviour). */
67181
- projection: TrackProjectionSchema.optional()
67761
+ projection: TrackProjectionSchema.optional(),
67762
+ /** Include stationary-promoted rows (parked objects handed to the
67763
+ * stationary registry). Default false: the timeline lists passages,
67764
+ * not parking records (operator decision, 2026-08-15). */
67765
+ includeStationary: boolean().optional()
67182
67766
  }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
67183
67767
  kind: "mutation",
67184
67768
  auth: "admin"
@@ -67342,11 +67926,16 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
67342
67926
  auth: "admin"
67343
67927
  }), method(object({
67344
67928
  eventId: string(),
67345
- kind: MediaFileKindEnum.optional()
67929
+ kind: MediaFileKindEnum.optional(),
67930
+ deviceId: number()
67346
67931
  }), array(MediaFileSchema).readonly()), method(object({
67347
67932
  trackId: string(),
67348
- kinds: array(MediaFileKindEnum).optional()
67349
- }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
67933
+ kinds: array(MediaFileKindEnum).optional(),
67934
+ deviceId: number()
67935
+ }), array(MediaFileSchema).readonly()), method(object({
67936
+ trackId: string(),
67937
+ deviceId: number()
67938
+ }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
67350
67939
  kind: "mutation",
67351
67940
  auth: "admin"
67352
67941
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -68046,6 +68635,17 @@ var maxSessionHoldMsField = {
68046
68635
  default: 12e4,
68047
68636
  step: 5e3
68048
68637
  };
68638
+ /**
68639
+ * Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
68640
+ * 5s so a rearm can never degenerate into per-event stream churn; default 90s
68641
+ * comfortably outlives the gap between two PIR wakes on a battery camera.
68642
+ */
68643
+ var audioMotionWindowMsField = {
68644
+ min: 5e3,
68645
+ max: 6e5,
68646
+ default: 9e4,
68647
+ step: 5e3
68648
+ };
68049
68649
  var motionFpsField = {
68050
68650
  min: 1,
68051
68651
  max: 30,
@@ -68058,10 +68658,26 @@ var detectionFpsField = {
68058
68658
  default: 10,
68059
68659
  step: 1
68060
68660
  };
68661
+ /**
68662
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
68663
+ *
68664
+ * The recheck is now on by default (a parked car is invisible to occupancy
68665
+ * rules until the stationary registry has been rebuilt by motion, which after a
68666
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
68667
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
68668
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
68669
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
68670
+ *
68671
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
68672
+ * the host validates `attachCamera` against ITS copy of this schema, so a
68673
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
68674
+ * carrying the wider bound is installed everywhere. 300 is the widest value
68675
+ * that ships with an addon deploy.
68676
+ */
68061
68677
  var occupancyRecheckSecField = {
68062
68678
  min: 0,
68063
68679
  max: 300,
68064
- default: 30,
68680
+ default: 300,
68065
68681
  step: 5
68066
68682
  };
68067
68683
  var occupancyRecheckFramesField = {
@@ -68206,6 +68822,27 @@ var RunnerCameraConfigSchema = object({
68206
68822
  * resolved `CameraDetectionConfig`.
68207
68823
  */
68208
68824
  maxSessionHoldMs: number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
68825
+ /**
68826
+ * Orchestrator-side quiet period (ms) that closes an `audioMode:
68827
+ * 'on-motion'` audio window, measured from the LAST motion event.
68828
+ *
68829
+ * This exists because the falling edge cannot be relied on. Camera-native
68830
+ * providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
68831
+ * its email-push SMTP path both emit `detected: true` and never the
68832
+ * counterpart); only the frame-diff analyzer emits falls. So on an
68833
+ * onboard-only camera a window that closed only on `detected: false` never
68834
+ * closed at all, and `on-motion` silently behaved as `always-on` — on a
68835
+ * battery camera, the one failure mode the mode exists to prevent.
68836
+ *
68837
+ * Every motion event rearms this timer WITHOUT restarting the stream, so a
68838
+ * burst of re-fires costs nothing. A falling edge, when one does arrive,
68839
+ * still closes earlier via `motionCooldownMs` — whichever comes first wins.
68840
+ *
68841
+ * Not consumed by the runner: carried here so it shares the per-camera
68842
+ * device-settings surface with `motionCooldownMs`, exactly like
68843
+ * `maxSessionHoldMs`.
68844
+ */
68845
+ audioMotionWindowMs: number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
68209
68846
  motionFps: number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
68210
68847
  detectionFps: number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
68211
68848
  motionStreamId: string(),
@@ -68259,15 +68896,21 @@ var RunnerCameraConfigSchema = object({
68259
68896
  */
68260
68897
  onboardMotionDrivesAnalyzer: boolean().default(true),
68261
68898
  /**
68262
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
68263
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
68264
- * this is off by default because the recheck re-subscribes a detection session
68265
- * every N seconds while `watching`, a major source of pull-decoder re-dial
68266
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
68899
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
68900
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
68267
68901
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
68268
68902
  * (and only render) when this is enabled.
68269
- */
68270
- occupancyRecheckEnabled: boolean().default(false),
68903
+ *
68904
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
68905
+ * recheck re-subscribes a detection session every N seconds while `watching`
68906
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
68907
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
68908
+ * object is counted only while the stationary registry holds it, and the
68909
+ * registry rebuilds from motion, so after a restart a parked car was invisible
68910
+ * to every occupancy rule until something moved in front of it. The churn is
68911
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
68912
+ */
68913
+ occupancyRecheckEnabled: boolean().default(true),
68271
68914
  occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
68272
68915
  occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
68273
68916
  /**
@@ -68295,7 +68938,7 @@ var RunnerCameraConfigSchema = object({
68295
68938
  */
68296
68939
  inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
68297
68940
  });
68298
- 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;
68941
+ 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;
68299
68942
  /**
68300
68943
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
68301
68944
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -69311,7 +69954,16 @@ targets: array(object({
69311
69954
  /** A sleeping battery camera: the frame is deliberately stale and will
69312
69955
  * NOT refresh in the background. A surface should say so rather than
69313
69956
  * present it as current. */
69314
- sleeping: boolean()
69957
+ sleeping: boolean(),
69958
+ /** Current device state rendered over the cached frame. State images
69959
+ * remain authoritative even when their photographic background is
69960
+ * old; null means the link must carry a current camera frame. */
69961
+ stateReason: _enum([
69962
+ "disabled",
69963
+ "sleeping",
69964
+ "unreachable",
69965
+ "waking"
69966
+ ]).nullable()
69315
69967
  })));
69316
69968
  /**
69317
69969
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -70965,6 +71617,25 @@ var BatteryStatusSchema = object({
70965
71617
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
70966
71618
  lastUpdated: number(),
70967
71619
  /**
71620
+ * Ms epoch of the last time the device PROVED it was reachable — a
71621
+ * completed firmware round-trip, an observed wake, or an inbound push
71622
+ * (firmware event, email). `0`/absent = never since this slice was born.
71623
+ *
71624
+ * This is the ONLY input that separates "asleep" from "gone", and it is
71625
+ * fed exclusively by PASSIVE signals: nothing may write it by reaching
71626
+ * for the radio, because a poll that confirms reachability is the same
71627
+ * poll that drains the battery. See {@link deriveBatteryPresence} — the
71628
+ * single derivation every consumer must use; no surface computes its own.
71629
+ *
71630
+ * It is deliberately NOT a clock in the
71631
+ * `scripts/check-runtime-state-durability.ts` sense: it is the
71632
+ * observation itself, and it is the only thing a 30-hour silence is
71633
+ * visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
71634
+ * Reolink provider) so a value that means "recently" cannot cost a
71635
+ * SQLite commit per round-trip.
71636
+ */
71637
+ lastContactAt: number().optional(),
71638
+ /**
70968
71639
  * True when the source is a BINARY low-battery indicator (HA
70969
71640
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
70970
71641
  * charge level — `percentage` is then a coarse stand-in (100 = normal,
@@ -73254,54 +73925,139 @@ var TalkAudioCodecSchema = _enum([
73254
73925
  "g711ulaw",
73255
73926
  "g711alaw"
73256
73927
  ]);
73257
- DeviceType.Camera, method(object({ deviceId: number() }), object({
73258
- sessionId: string(),
73259
- sdpOffer: string()
73260
- }), {
73261
- kind: "mutation",
73262
- auth: "admin"
73263
- }), method(object({
73264
- deviceId: number(),
73265
- sessionId: string(),
73266
- sdpAnswer: string()
73267
- }), _void(), {
73268
- kind: "mutation",
73269
- auth: "admin"
73270
- }), method(object({
73271
- deviceId: number(),
73272
- sessionId: string()
73273
- }), _void(), {
73274
- kind: "mutation",
73275
- auth: "admin"
73276
- }), method(object({ deviceId: number() }), object({ sessionId: string() }), {
73277
- kind: "mutation",
73278
- auth: "admin"
73279
- }), method(object({
73280
- deviceId: number(),
73281
- /** Audio bytes for ONE frame, base64-encoded so the payload
73282
- * survives tRPC JSON serialization. */
73283
- audioBase64: string(),
73284
- /** Wire codec of the payload. Omit to let the provider default
73285
- * to its native expected format (s16le @ provider-native rate,
73286
- * mono). See {@link TalkAudioCodecSchema} for the supported set. */
73287
- codec: TalkAudioCodecSchema.optional(),
73288
- /** Sample rate (Hz). REQUIRED for `s16le`; advisory for
73289
- * `opus` (encoder clock); ignored for `g711*` (implied 8000). */
73290
- sampleRate: number().int().positive().optional(),
73291
- /** Channel count. Default 1. */
73292
- channels: number().int().positive().optional(),
73293
- /** Sequence number for ordering / dropping out-of-order frames. */
73294
- sequenceNumber: number().int()
73295
- }), object({ accepted: boolean() }), {
73296
- kind: "mutation",
73297
- auth: "admin"
73298
- }), method(object({ deviceId: number() }), _void(), {
73299
- kind: "mutation",
73300
- auth: "admin"
73301
- }), object({
73302
- deviceId: number(),
73303
- status: IntercomStatusSchema
73304
- });
73928
+ var intercomCapability = {
73929
+ name: "intercom",
73930
+ scope: "device",
73931
+ deviceNative: true,
73932
+ mode: "singleton",
73933
+ deviceTypes: [DeviceType.Camera],
73934
+ methods: {
73935
+ /**
73936
+ * Open a server-side WebRTC audio-only session. Returns an SDP
73937
+ * offer with a single sendonly audio m-line the client answers
73938
+ * (client → server direction). The server wakes battery cams
73939
+ * transparently before opening the upstream talk channel.
73940
+ */
73941
+ startSession: method(object({ deviceId: number() }), object({
73942
+ sessionId: string(),
73943
+ sdpOffer: string()
73944
+ }), {
73945
+ kind: "mutation",
73946
+ auth: "admin"
73947
+ }),
73948
+ handleAnswer: method(object({
73949
+ deviceId: number(),
73950
+ sessionId: string(),
73951
+ sdpAnswer: string()
73952
+ }), _void(), {
73953
+ kind: "mutation",
73954
+ auth: "admin"
73955
+ }),
73956
+ /** Close explicitly. Server also auto-closes on 30s idle. */
73957
+ stopSession: method(object({
73958
+ deviceId: number(),
73959
+ sessionId: string()
73960
+ }), _void(), {
73961
+ kind: "mutation",
73962
+ auth: "admin"
73963
+ }),
73964
+ /**
73965
+ * Open a raw-PCM talk session (no WebRTC SDP plumbing). Used by
73966
+ * non-WebRTC consumers (HomeKit export, Alexa raw audio, test
73967
+ * harnesses) that already have decoded PCM frames and just need a
73968
+ * direct path onto the camera's talk channel. Mutually exclusive
73969
+ * with `startSession` (an active WebRTC session must be stopped
73970
+ * before a raw-PCM session can be opened on the same device, and
73971
+ * vice versa).
73972
+ */
73973
+ startTalkSession: method(object({ deviceId: number() }), object({ sessionId: string() }), {
73974
+ kind: "mutation",
73975
+ auth: "admin"
73976
+ }),
73977
+ /**
73978
+ * Push one chunk of talk-back audio onto the active talk session.
73979
+ * The cap is codec-agnostic: the caller declares (or omits) the
73980
+ * wire format via `codec`; the provider decides between passthrough
73981
+ * (when the wire codec matches the camera's native talk channel),
73982
+ * transcoding via the `audio-codec` cap, or rejecting the call.
73983
+ *
73984
+ * Callers do NOT need to know the camera's wire format or sample
73985
+ * rate — that information lives entirely inside the provider.
73986
+ *
73987
+ * Sequence numbers MUST be monotonic per talk session; older frames
73988
+ * arriving after newer ones are dropped to avoid smearing the
73989
+ * downstream encoder state (G.711 is stateless but IMA ADPCM's
73990
+ * predictor would corrupt with re-ordering).
73991
+ */
73992
+ pushTalkAudio: method(object({
73993
+ deviceId: number(),
73994
+ /** Audio bytes for ONE frame, base64-encoded so the payload
73995
+ * survives tRPC JSON serialization. */
73996
+ audioBase64: string(),
73997
+ /** Wire codec of the payload. Omit to let the provider default
73998
+ * to its native expected format (s16le @ provider-native rate,
73999
+ * mono). See {@link TalkAudioCodecSchema} for the supported set. */
74000
+ codec: TalkAudioCodecSchema.optional(),
74001
+ /** Sample rate (Hz). REQUIRED for `s16le`; advisory for
74002
+ * `opus` (encoder clock); ignored for `g711*` (implied 8000). */
74003
+ sampleRate: number().int().positive().optional(),
74004
+ /** Channel count. Default 1. */
74005
+ channels: number().int().positive().optional(),
74006
+ /** Sequence number for ordering / dropping out-of-order frames. */
74007
+ sequenceNumber: number().int()
74008
+ }), object({ accepted: boolean() }), {
74009
+ kind: "mutation",
74010
+ auth: "admin"
74011
+ }),
74012
+ /** Close the raw-PCM talk session. Idempotent. */
74013
+ endTalkSession: method(object({ deviceId: number() }), _void(), {
74014
+ kind: "mutation",
74015
+ auth: "admin"
74016
+ })
74017
+ },
74018
+ events: { onStatusChanged: { data: object({
74019
+ deviceId: number(),
74020
+ status: IntercomStatusSchema
74021
+ }) } },
74022
+ status: {
74023
+ schema: IntercomStatusSchema,
74024
+ kind: "command-driven"
74025
+ },
74026
+ /**
74027
+ * Runtime-state slice — mirrored by the kernel.
74028
+ *
74029
+ * The cap declared `status` and nothing else, so the only two sources an
74030
+ * exporter has for a value — the `device.state-changed` slice event and the
74031
+ * `deviceState.getAllSnapshots` snapshot, both built from runtime state —
74032
+ * carried nothing for `intercom`. A talk-back entity in Home Assistant would
74033
+ * have been published and never received a value, which is the defect the
74034
+ * export's two classification tables exist to prevent (177 of them, once), so
74035
+ * `intercom` was excluded rather than exported.
74036
+ *
74037
+ * The shape is the status shape: there is exactly one truth about talk-back
74038
+ * and duplicating it into a second schema is how two halves of one capability
74039
+ * come to disagree. Providers write it through
74040
+ * `this.runtimeState.setCapState('intercom', …)` at the four points that open
74041
+ * and close a session, and seed it at registration so the slice exists before
74042
+ * the first session rather than after it.
74043
+ *
74044
+ * **Bound, named rather than hidden:** `talking` mirrors the provider's own
74045
+ * session handle, so a session torn down by a transport death that never
74046
+ * reaches `stopSession` / `endTalkSession` leaves it latched until the next
74047
+ * session or the next restart. That is why the slice is `session` and not
74048
+ * `restored` — a restart must never restore "talking".
74049
+ */
74050
+ runtimeState: IntercomStatusSchema,
74051
+ /**
74052
+ * Runtime-state durability: **session** — `talking` describes a live audio
74053
+ * session, which by definition does not survive the process that held it.
74054
+ * Restoring it would publish a camera as talking to nobody.
74055
+ *
74056
+ * See `RuntimeStateDurability`. Enforced by
74057
+ * `scripts/check-runtime-state-durability.ts`.
74058
+ */
74059
+ durability: "session"
74060
+ };
73305
74061
  /**
73306
74062
  * Robotic lawn-mower cap. Models HA `lawn_mower.*` entities — anything
73307
74063
  * with a mowing lifecycle plus a dock action.
@@ -75998,7 +76754,7 @@ method(object({
75998
76754
  toMs: number()
75999
76755
  }), RecordingAvailabilitySchema, {
76000
76756
  kind: "query",
76001
- auth: "admin"
76757
+ auth: "protected"
76002
76758
  }), method(object({
76003
76759
  deviceId: number(),
76004
76760
  fromMs: number(),
@@ -76006,14 +76762,14 @@ method(object({
76006
76762
  tzOffsetMinutes: number()
76007
76763
  }), RecordingDaysSchema, {
76008
76764
  kind: "query",
76009
- auth: "admin"
76765
+ auth: "protected"
76010
76766
  }), method(object({
76011
76767
  deviceId: number(),
76012
76768
  fromMs: number(),
76013
76769
  toMs: number()
76014
76770
  }), RecordingManifestSchema, {
76015
76771
  kind: "query",
76016
- auth: "admin"
76772
+ auth: "protected"
76017
76773
  }), method(object({}), RecordingStorageUsageSchema, {
76018
76774
  kind: "query",
76019
76775
  auth: "admin"
@@ -76303,14 +77059,77 @@ method(object({
76303
77059
  * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
76304
77060
  * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
76305
77061
  *
76306
- * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) the framework
76307
- * derives the device-detail contribution; the provider carries NO hand-written
76308
- * settings-contribution methods. `status.kind:'push'` the engine pushes on
76309
- * every hysteresis flip / availability change; consumers never poll.
77062
+ * **No `deviceConfig`, deliberately.** This shipped as the D14 widget archetype,
77063
+ * which put a "Scenes" tab on one camera's detail page. That is the wrong shape
77064
+ * for the thing: a scene is a standing question about the property ("is the bin
77065
+ * still out"), and the operator's question is "which of my scenes have tripped",
77066
+ * across every camera at once — not "what does camera 617 think". Buried one
77067
+ * camera deep it also could not be found. The surface is now a top-level admin
77068
+ * page (`/scenes`, `pages/Scenes.tsx`) that lists every scene on every camera and
77069
+ * picks the camera inside the create flow, the same shape Events and Faces have.
77070
+ *
77071
+ * The consequence to keep in mind: `host/scene-monitor-editor` is gone from
77072
+ * `HOST_WIDGETS` too. `scripts/check-host-widget-resolves.ts` asserts BOTH
77073
+ * directions, so a registration nobody declares fails exactly as loudly as a
77074
+ * declaration nobody registers. The editor is imported directly by the page.
77075
+ *
77076
+ * `status.kind:'push'` — the engine pushes on every hysteresis flip /
77077
+ * availability change; consumers never poll.
76310
77078
  */
76311
- /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
76312
- * be added without a wire break (matching falls back to any-condition refs). */
77079
+ /** Extensible condition tag. Seeded 'day' | 'ir' (the two variants the operator
77080
+ * captures) plus 'night' | 'dawn' | 'dusk' from the resolver's sun-times band.
77081
+ * Open by design so more can be added without a wire break.
77082
+ *
77083
+ * Matching does NOT fall back across conditions: cross-condition cosines are
77084
+ * not comparable, so "I have never seen this scene in this light" is reported
77085
+ * as `unknown`, never guessed. A day reference scored against an IR frame
77086
+ * collapses the cosine and would latch a false alarm every single night. */
76313
77087
  var SceneConditionSchema = string();
77088
+ /**
77089
+ * What a scene does when the CURRENT light has no reference of its own.
77090
+ *
77091
+ * The lighting variants are not equally likely to exist. Almost every operator
77092
+ * captures daylight and then never stands outside at 22:00 to capture IR, and a
77093
+ * scene that is only ever going to be asked about a daytime question ("is the
77094
+ * bin still on the kerb at 08:00") does not need a night reference at all. The
77095
+ * night half must therefore be OPTIONAL, and optional means the scene keeps
77096
+ * working without it rather than degrading into a permanent complaint.
77097
+ *
77098
+ * - `skip` (default) — the check in that light is not made. Not a verdict, not
77099
+ * an alarm, not even an `unknown`: the live state simply stays whatever the
77100
+ * last covered light left it at, the latch is untouched, and the hysteresis
77101
+ * run is neither spent nor cleared. The scene resumes by itself at first
77102
+ * light. This is the only behaviour under which "I never captured IR" is a
77103
+ * configuration choice instead of a nightly fault.
77104
+ * - `judge-anyway` — score against the OTHER conditions' references. Available
77105
+ * for cameras whose IR frame is close enough to daylight (a floodlit
77106
+ * driveway, an always-white-light doorbell), and wrong for everything else:
77107
+ * cross-condition cosines are not comparable, so a day reference against a
77108
+ * true IR frame collapses and the scene reports a theft at 21:40.
77109
+ *
77110
+ * Never applies when the scene has NO comparable reference at all — that is
77111
+ * "not armed yet", it is reported as `no-reference-for-condition`, and silence
77112
+ * there would hide a scene the operator never finished setting up.
77113
+ */
77114
+ var SceneUncoveredPolicySchema = _enum(["skip", "judge-anyway"]);
77115
+ /** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
77116
+ * `unknown` = we cannot judge (no reference for this condition, encoder model
77117
+ * changed, view shifted, no snapshot). `unknown` is a real value, not a null,
77118
+ * and never counts toward hysteresis in either direction. */
77119
+ var SceneVerdictSchema = _enum([
77120
+ "matched",
77121
+ "diverged",
77122
+ "unknown"
77123
+ ]);
77124
+ /** Why a scene cannot judge. Named, because this feature's failure mode is
77125
+ * silence that reads as "nothing has happened". */
77126
+ var SceneUnavailableSchema = _enum([
77127
+ "no-reference-for-condition",
77128
+ "view-shifted",
77129
+ "no-vision-profile",
77130
+ "encoder-model-changed",
77131
+ "no-snapshot"
77132
+ ]);
76314
77133
  /** One captured reference — condition-tagged, model-version-gated. `embedding`
76315
77134
  * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
76316
77135
  var SceneReferenceSchema = object({
@@ -76318,7 +77137,14 @@ var SceneReferenceSchema = object({
76318
77137
  modelId: string(),
76319
77138
  condition: SceneConditionSchema,
76320
77139
  capturedAt: number(),
76321
- thumbnailMediaId: string().optional()
77140
+ thumbnailMediaId: string().optional(),
77141
+ /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
77142
+ * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
77143
+ * normalized rect frame a different piece of world, and the scene would
77144
+ * diverge forever with a perfectly plausible cosine. Checked LAZILY, only
77145
+ * when hysteresis is about to flip — one extra encode per candidate
77146
+ * transition, not per poll. */
77147
+ anchorEmbedding: array(number()).optional()
76322
77148
  });
76323
77149
  var SceneMonitorStateSchema = object({
76324
77150
  id: string(),
@@ -76340,6 +77166,28 @@ var SceneCheckSchema = discriminatedUnion("mode", [object({
76340
77166
  profileId: string().optional(),
76341
77167
  hysteresisCount: number().int().positive()
76342
77168
  })]);
77169
+ var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
77170
+ /** Night is OPTIONAL. A scene with only a daylight reference sits the IR hours
77171
+ * out in silence rather than reporting a fault every night. */
77172
+ var SCENE_DEFAULT_UNCOVERED_POLICY = "skip";
77173
+ /**
77174
+ * Vision-model adjudication of a candidate flip. Field names deliberately
77175
+ * mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
77176
+ *
77177
+ * `onTimeout` defaults to **'hold'**, the OPPOSITE of `NcConfirmGate`'s
77178
+ * fail-open: a notification suppressed is the worse error there, but a vision
77179
+ * model that timed out has not told us the bin is gone, and a latch is a
77180
+ * stateful claim that costs the operator a trip to reset.
77181
+ */
77182
+ var SceneConfirmSchema = object({
77183
+ enabled: boolean().default(false),
77184
+ prompt: string().min(1).max(1e3),
77185
+ profileId: string().optional(),
77186
+ timeoutMs: number().int().min(1e3).max(2e4).default(8e3),
77187
+ maxImagePx: number().int().min(64).max(2048).default(448),
77188
+ /** What a timeout / unavailable model means for the PENDING flip. */
77189
+ onTimeout: _enum(["flip", "hold"]).default("hold")
77190
+ });
76343
77191
  var SceneMonitorSchema = object({
76344
77192
  id: string(),
76345
77193
  label: string(),
@@ -76358,7 +77206,56 @@ var SceneMonitorSchema = object({
76358
77206
  lastConfidence: number().nullable(),
76359
77207
  currentCondition: SceneConditionSchema.nullable(),
76360
77208
  availability: _enum(["ok", "unavailable"]),
76361
- unavailableReason: string().nullable()
77209
+ unavailableReason: string().nullable(),
77210
+ /** Which state is "the initial screen". `null` until the first capture. */
77211
+ baselineStateId: string().nullable(),
77212
+ /** Which boolean drives notification rules and any export. */
77213
+ emit: _enum(["latched", "live"]).default("latched"),
77214
+ /** Live: does the region match the baseline RIGHT NOW. */
77215
+ verdict: SceneVerdictSchema,
77216
+ /** Has it been `diverged` at least once since `armedAt` — the operator's boolean. */
77217
+ latched: boolean(),
77218
+ /** Last reset (or creation). */
77219
+ armedAt: number(),
77220
+ divergedAt: number().nullable(),
77221
+ restoredAt: number().nullable(),
77222
+ /** A check is only COUNTED when the device has been quiet this long. Motion
77223
+ * during the window DISCARDS the observation — a car pulling up in front of
77224
+ * the bin must not be able to spend hysteresis credit. */
77225
+ quietSeconds: number().int().min(0).max(3600).default(60),
77226
+ /** An observation only advances the pending count when it is at least this
77227
+ * far from the previously counted one, so N agreeing checks span real time
77228
+ * rather than N adjacent polls inside one occlusion. */
77229
+ minObservationSpacingSec: number().int().min(0).max(3600).default(120),
77230
+ /** Vision-model adjudication of a candidate flip. Similarity primary only. */
77231
+ confirm: SceneConfirmSchema.optional(),
77232
+ /** Whole-frame anchor cosine below which a flip is REFUSED as `view-shifted`. */
77233
+ anchorThreshold: number().min(0).max(1).default(SCENE_DEFAULT_ANCHOR_THRESHOLD),
77234
+ /** Clear the latch on its own when the scene matches again? Default false —
77235
+ * `restoredAt` and the `scene-restored` edge are recorded regardless, so an
77236
+ * automation can react to the bin coming back without the operator's own
77237
+ * alarm silently clearing itself. */
77238
+ autoRestore: boolean().default(false),
77239
+ /** What to do when the current light has no reference of its own. See
77240
+ * {@link SceneUncoveredPolicySchema} — the default makes night OPTIONAL. */
77241
+ onUncoveredCondition: SceneUncoveredPolicySchema.default(SCENE_DEFAULT_UNCOVERED_POLICY),
77242
+ /**
77243
+ * The light whose checks are currently being SAT OUT under
77244
+ * `onUncoveredCondition: 'skip'` — `null` when the scene is checking normally.
77245
+ *
77246
+ * Engine-reported and advisory only: it moves no verdict, no latch and no
77247
+ * hysteresis. It exists so the card can say *"night (IR) — checks paused,
77248
+ * nothing captured in this light"* in the same calm voice as the coverage
77249
+ * line, because the alternative is a scene that silently stops answering
77250
+ * after sunset with nothing anywhere saying why. A skipped check must never
77251
+ * read as a broken one.
77252
+ */
77253
+ suspendedCondition: SceneConditionSchema.nullable().default(null),
77254
+ /** Named cause when `verdict === 'unknown'`. */
77255
+ unavailable: SceneUnavailableSchema.nullable(),
77256
+ /** Conditions that have at least one comparable reference — the coverage line
77257
+ * ("day ✓ · ir ✓ · dusk ✗") that turns a silent fallback into a visible fact. */
77258
+ coveredConditions: array(SceneConditionSchema)
76362
77259
  });
76363
77260
  var SceneMonitorStatusSchema = object({
76364
77261
  monitors: array(SceneMonitorSchema),
@@ -76371,12 +77268,6 @@ var sceneMonitorCapability = {
76371
77268
  kind: "wrapper",
76372
77269
  defaultActive: true,
76373
77270
  deviceTypes: [DeviceType.Camera],
76374
- deviceConfig: { ui: {
76375
- kind: "widget",
76376
- widgetId: "host/scene-monitor-editor",
76377
- tab: "scenes",
76378
- label: "Scenes"
76379
- } },
76380
77271
  methods: {
76381
77272
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
76382
77273
  createScene: method(object({
@@ -76407,7 +77298,15 @@ var sceneMonitorCapability = {
76407
77298
  "both"
76408
77299
  ]).optional(),
76409
77300
  checkIntervalSec: number().optional(),
76410
- check: SceneCheckSchema.optional()
77301
+ check: SceneCheckSchema.optional(),
77302
+ emit: _enum(["latched", "live"]).optional(),
77303
+ quietSeconds: number().int().min(0).max(3600).optional(),
77304
+ minObservationSpacingSec: number().int().min(0).max(3600).optional(),
77305
+ anchorThreshold: number().min(0).max(1).optional(),
77306
+ autoRestore: boolean().optional(),
77307
+ onUncoveredCondition: SceneUncoveredPolicySchema.optional(),
77308
+ /** `null` clears the vision-model adjudicator. */
77309
+ confirm: SceneConfirmSchema.nullable().optional()
76411
77310
  })
76412
77311
  }), _void(), {
76413
77312
  kind: "mutation",
@@ -76448,6 +77347,26 @@ var sceneMonitorCapability = {
76448
77347
  }), _void(), {
76449
77348
  kind: "mutation",
76450
77349
  auth: "admin"
77350
+ }),
77351
+ /**
77352
+ * Clear the latch, re-arm, and — by default — RE-CAPTURE the baseline for
77353
+ * the CURRENT condition. The bin never goes back in exactly the same spot;
77354
+ * "reset" in the operator's head means *this is the new normal*, and
77355
+ * re-capture is what makes the feature self-healing against slow drift
77356
+ * instead of failing silently weeks later.
77357
+ *
77358
+ * Reachable from three surfaces on this one mutation: the scene card, a
77359
+ * notification button (an `onTrigger` sequence with a `kind:'cap'` step —
77360
+ * no new Notification-Center code at all), and tRPC for scripts.
77361
+ */
77362
+ resetScene: method(object({
77363
+ deviceId: number(),
77364
+ monitorId: string(),
77365
+ /** Defaults to TRUE at the provider seam — see `SCENE_RESET_RECAPTURES`. */
77366
+ recapture: boolean().optional()
77367
+ }), _void(), {
77368
+ kind: "mutation",
77369
+ auth: "admin"
76451
77370
  })
76452
77371
  },
76453
77372
  status: {
@@ -76684,7 +77603,70 @@ var CamStreamDescriptorSchema = object({
76684
77603
  /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
76685
77604
  metadata: record(string(), unknown()).optional()
76686
77605
  });
76687
- DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly());
77606
+ /**
77607
+ * `stream-catalog` — device-scoped, provider-implemented. The pull counterpart
77608
+ * of the removed `publishCameraStream` push: a camera provider returns the full
77609
+ * set of stream descriptors it can offer for the device, synchronously, so the
77610
+ * broker can reconcile its registry against the authoritative provider state.
77611
+ */
77612
+ /**
77613
+ * The catalog as a DURABLE fact rather than a live answer.
77614
+ *
77615
+ * A battery camera's descriptors are profile-stable — they change when the
77616
+ * operator rewrites an encoder profile, not minute to minute — but building
77617
+ * them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
77618
+ * provider is allowed to build them exactly once per profile and must serve
77619
+ * every later pull from a cache.
77620
+ *
77621
+ * Holding that cache only in RAM is what turned a restart into an outage. The
77622
+ * runner comes back with the camera asleep, `buildStreamCatalogUncached`
77623
+ * correctly refuses to wake it, the pull answers `[]`, the broker has no
77624
+ * cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
77625
+ * fails with a flat "No broker for stream" — for as long as the camera sleeps,
77626
+ * which on a battery cam is most of the day. The camera was fine. The stream
77627
+ * was unreachable because the process had forgotten what the camera offers.
77628
+ *
77629
+ * Declaring it here puts it in `device-runtime-state`, the kernel's canonical
77630
+ * declared collection, with the same `restored` durability `battery` uses for
77631
+ * the same reason: the last known value is the only value there is while the
77632
+ * device is asleep. The broker's brokers are therefore always DEFINABLE — it
77633
+ * is the DIAL that wakes a camera, never the catalog (D173).
77634
+ */
77635
+ var StreamCatalogStateSchema = object({
77636
+ /** The descriptors as last built from a real camera response. Never a guess:
77637
+ * a failed or refused build writes NOTHING, so a restored catalog is always
77638
+ * one the camera itself once produced. */
77639
+ descriptors: array(CamStreamDescriptorSchema),
77640
+ /** Ms epoch of the build that produced {@link descriptors}. Lets the wake
77641
+ * path decide whether the camera's own awake window is worth spending on a
77642
+ * re-read. */
77643
+ lastFetchedAt: number()
77644
+ });
77645
+ var streamCatalogCapability = {
77646
+ name: "stream-catalog",
77647
+ scope: "device",
77648
+ deviceNative: true,
77649
+ mode: "singleton",
77650
+ deviceTypes: [DeviceType.Camera],
77651
+ methods: { getCatalog: method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly()) },
77652
+ runtimeState: StreamCatalogStateSchema,
77653
+ /**
77654
+ * Runtime-state durability: **restored** — see the schema doc. A cold
77655
+ * catalog on a sleeping battery camera is not a slow first frame, it is a
77656
+ * camera that cannot be watched at all until it happens to wake.
77657
+ *
77658
+ * Churn is nil by construction: the slice is written only by a SUCCESSFUL
77659
+ * build, and a build only runs when there is no cached copy (or the copy is
77660
+ * a day old and the camera is awake anyway).
77661
+ *
77662
+ * See `RuntimeStateDurability`. Enforced by
77663
+ * `scripts/check-runtime-state-durability.ts`.
77664
+ */
77665
+ durability: "restored",
77666
+ /** Clock field: written, but excluded from the compare that decides whether
77667
+ * persisting is worth a SQLite commit — the descriptors are the value. */
77668
+ volatileStateFields: ["lastFetchedAt"]
77669
+ };
76688
77670
  /** One of the camera's stream profiles. */
76689
77671
  var StreamProfileSchema = _enum([
76690
77672
  "main",
@@ -76938,12 +77920,64 @@ var NetworkAddressSchema = object({
76938
77920
  family: string(),
76939
77921
  internal: boolean()
76940
77922
  });
77923
+ /**
77924
+ * Provenance of the site coordinates, and the whole reason this is not just two
77925
+ * numbers.
77926
+ *
77927
+ * - `operator-set` — a human typed it, or accepted a detection. Authoritative;
77928
+ * nothing overwrites it.
77929
+ * - `derived-from-ip` — the hub geolocated its own public IP once, because a
77930
+ * default that is right to a few kilometres beats the coarse UTC clock split
77931
+ * the sun-times consumers otherwise fall back to.
77932
+ *
77933
+ * The UI shows which one it is. An operator who cannot tell a guess from their
77934
+ * own input will eventually trust the guess.
77935
+ */
77936
+ var SiteLocationSourceSchema = _enum(["operator-set", "derived-from-ip"]);
77937
+ /**
77938
+ * The read shape: the location plus the honest state of the one-shot derivation.
77939
+ *
77940
+ * `derivationAttemptedAt` is what makes the "one call, ever" contract
77941
+ * inspectable. When it is set and `location` is null, the geo-IP lookup ran and
77942
+ * failed; the hub will NOT try again on its own — the fallback is declared
77943
+ * (consumers degrade to their own last resort) and the operator either types the
77944
+ * coordinates or presses detect.
77945
+ */
77946
+ var SiteLocationStatusSchema = object({
77947
+ location: object({
77948
+ /** WGS84 decimal degrees. */
77949
+ latitude: number().min(-90).max(90),
77950
+ longitude: number().min(-180).max(180),
77951
+ source: SiteLocationSourceSchema,
77952
+ /** Epoch ms the value was last written. */
77953
+ updatedAt: number(),
77954
+ /**
77955
+ * Human-readable place the geo-IP service reported ("Napoli, IT"). Display
77956
+ * only — never parsed, never matched on. Absent for an operator-typed value.
77957
+ */
77958
+ label: string().optional()
77959
+ }).nullable(),
77960
+ derivationAttemptedAt: number().nullable(),
77961
+ /** Why the last derivation failed, for the UI to show instead of a shrug. */
77962
+ derivationError: string().nullable()
77963
+ });
77964
+ /** `null` clears the location and re-arms nothing — the derivation stays spent. */
77965
+ var SetSiteLocationInputSchema = object({
77966
+ latitude: number().min(-90).max(90),
77967
+ longitude: number().min(-180).max(180)
77968
+ }).nullable();
76941
77969
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
76942
77970
  kind: "mutation",
76943
77971
  auth: "admin"
76944
77972
  }), method(_void(), _void(), {
76945
77973
  kind: "mutation",
76946
77974
  auth: "admin"
77975
+ }), method(_void(), SiteLocationStatusSchema), method(SetSiteLocationInputSchema, SiteLocationStatusSchema, {
77976
+ kind: "mutation",
77977
+ auth: "admin"
77978
+ }), method(_void(), SiteLocationStatusSchema, {
77979
+ kind: "mutation",
77980
+ auth: "admin"
76947
77981
  });
76948
77982
  /**
76949
77983
  * Tamper / case-open detection sensor. Drives Home Assistant
@@ -78263,6 +79297,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
78263
79297
  humiditySensor: humiditySensorCapability,
78264
79298
  image: imageCapability,
78265
79299
  imageSettings: imageSettingsCapability,
79300
+ intercom: intercomCapability,
78266
79301
  lawnMowerControl: lawnMowerControlCapability,
78267
79302
  lockControl: lockControlCapability,
78268
79303
  mediaPlayer: mediaPlayerCapability,
@@ -78281,6 +79316,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
78281
79316
  sceneMonitor: sceneMonitorCapability,
78282
79317
  scriptRunner: scriptRunnerCapability,
78283
79318
  smoke: smokeCapability,
79319
+ streamCatalog: streamCatalogCapability,
78284
79320
  streamParams: streamParamsCapability,
78285
79321
  switch: switchCapability,
78286
79322
  tamper: tamperCapability,
@@ -78934,6 +79970,15 @@ var BaseDeviceProvider = class extends BaseAddon {
78934
79970
  labels: ["probe not implemented"]
78935
79971
  };
78936
79972
  }
79973
+ /**
79974
+ * Top-level devices restored at once in {@link onRestoreDevices}.
79975
+ *
79976
+ * Four covers the fleets this ships to without turning a boot into a burst a
79977
+ * camera NVR answers with a refusal. A provider whose upstream is a single
79978
+ * session with a serial command channel (a Baichuan hub, an NVR that
79979
+ * serialises ISAPI) should lower it; nothing needs to raise it.
79980
+ */
79981
+ restoreConcurrency = 4;
78937
79982
  async restoreDevices(savedDevices) {
78938
79983
  await this.onRestoreDevices(savedDevices);
78939
79984
  if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
@@ -78965,15 +80010,15 @@ var BaseDeviceProvider = class extends BaseAddon {
78965
80010
  */
78966
80011
  async onRestoreDevices(savedDevices) {
78967
80012
  const restored = /* @__PURE__ */ new Set();
78968
- for (const saved of savedDevices) {
78969
- if (saved.parentDeviceId !== null) continue;
80013
+ const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
80014
+ const restoreOne = async (saved) => {
78970
80015
  const Class = this.deviceClasses[saved.type];
78971
80016
  if (!Class) {
78972
80017
  this.ctx.logger.warn("No device class registered for restored type — skipping", {
78973
80018
  tags: { stableId: saved.stableId },
78974
80019
  meta: { type: saved.type }
78975
80020
  });
78976
- continue;
80021
+ return;
78977
80022
  }
78978
80023
  try {
78979
80024
  await this.ctx.kernel.devices.create(saved.stableId, Class, {});
@@ -78987,7 +80032,15 @@ var BaseDeviceProvider = class extends BaseAddon {
78987
80032
  }
78988
80033
  });
78989
80034
  }
78990
- }
80035
+ };
80036
+ let nextTopLevel = 0;
80037
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
80038
+ for (;;) {
80039
+ const saved = topLevel[nextTopLevel++];
80040
+ if (saved === void 0) return;
80041
+ await restoreOne(saved);
80042
+ }
80043
+ }));
78991
80044
  const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
78992
80045
  for (const saved of childRows) {
78993
80046
  const Class = this.deviceClasses[saved.type];
@@ -81078,6 +82131,12 @@ Object.freeze({
81078
82131
  addonId: null,
81079
82132
  access: "create"
81080
82133
  },
82134
+ "llm.cancel": {
82135
+ capName: "llm",
82136
+ capScope: "system",
82137
+ addonId: null,
82138
+ access: "create"
82139
+ },
81081
82140
  "llm.deleteModel": {
81082
82141
  capName: "llm",
81083
82142
  capScope: "system",
@@ -81162,6 +82221,12 @@ Object.freeze({
81162
82221
  addonId: null,
81163
82222
  access: "view"
81164
82223
  },
82224
+ "llm.resolveModelRef": {
82225
+ capName: "llm",
82226
+ capScope: "system",
82227
+ addonId: null,
82228
+ access: "create"
82229
+ },
81165
82230
  "llm.setDefault": {
81166
82231
  capName: "llm",
81167
82232
  capScope: "system",
@@ -83328,6 +84393,12 @@ Object.freeze({
83328
84393
  addonId: null,
83329
84394
  access: "create"
83330
84395
  },
84396
+ "sceneMonitor.resetScene": {
84397
+ capName: "scene-monitor",
84398
+ capScope: "device",
84399
+ addonId: null,
84400
+ access: "delete"
84401
+ },
83331
84402
  "sceneMonitor.updateScene": {
83332
84403
  capName: "scene-monitor",
83333
84404
  capScope: "device",
@@ -84006,6 +85077,12 @@ Object.freeze({
84006
85077
  addonId: null,
84007
85078
  access: "create"
84008
85079
  },
85080
+ "system.detectSiteLocation": {
85081
+ capName: "system",
85082
+ capScope: "system",
85083
+ addonId: null,
85084
+ access: "create"
85085
+ },
84009
85086
  "system.featureFlags": {
84010
85087
  capName: "system",
84011
85088
  capScope: "system",
@@ -84024,6 +85101,12 @@ Object.freeze({
84024
85101
  addonId: null,
84025
85102
  access: "view"
84026
85103
  },
85104
+ "system.getSiteLocation": {
85105
+ capName: "system",
85106
+ capScope: "system",
85107
+ addonId: null,
85108
+ access: "view"
85109
+ },
84027
85110
  "system.health": {
84028
85111
  capName: "system",
84029
85112
  capScope: "system",
@@ -84048,6 +85131,12 @@ Object.freeze({
84048
85131
  addonId: null,
84049
85132
  access: "create"
84050
85133
  },
85134
+ "system.setSiteLocation": {
85135
+ capName: "system",
85136
+ capScope: "system",
85137
+ addonId: null,
85138
+ access: "create"
85139
+ },
84051
85140
  "terminalSession.adoptLegacyMonitor": {
84052
85141
  capName: "terminal-session",
84053
85142
  capScope: "system",
@@ -84619,6 +85708,1704 @@ Object.freeze({
84619
85708
  access: "create"
84620
85709
  }
84621
85710
  });
85711
+ Object.freeze({
85712
+ "accessories.setChildHidden": [{
85713
+ name: "childDeviceId",
85714
+ form: "single",
85715
+ optional: false
85716
+ }, {
85717
+ name: "deviceId",
85718
+ form: "single",
85719
+ optional: false
85720
+ }],
85721
+ "addonSettings.getDeviceSettings": [{
85722
+ name: "deviceId",
85723
+ form: "single",
85724
+ optional: false
85725
+ }],
85726
+ "addonSettings.updateDeviceSettings": [{
85727
+ name: "deviceId",
85728
+ form: "single",
85729
+ optional: false
85730
+ }],
85731
+ "alarmPanel.arm": [{
85732
+ name: "deviceId",
85733
+ form: "single",
85734
+ optional: false
85735
+ }],
85736
+ "alarmPanel.disarm": [{
85737
+ name: "deviceId",
85738
+ form: "single",
85739
+ optional: false
85740
+ }],
85741
+ "alarmPanel.trigger": [{
85742
+ name: "deviceId",
85743
+ form: "single",
85744
+ optional: false
85745
+ }],
85746
+ "audioAnalysis.resolveDeviceSettings": [{
85747
+ name: "deviceId",
85748
+ form: "single",
85749
+ optional: false
85750
+ }],
85751
+ "audioAnalyzer.classify": [{
85752
+ name: "deviceId",
85753
+ form: "single",
85754
+ optional: true
85755
+ }],
85756
+ "audioMetrics.getCurrentSnapshot": [{
85757
+ name: "deviceId",
85758
+ form: "single",
85759
+ optional: false
85760
+ }],
85761
+ "audioMetrics.getHistory": [{
85762
+ name: "deviceId",
85763
+ form: "single",
85764
+ optional: false
85765
+ }],
85766
+ "automationControl.disable": [{
85767
+ name: "deviceId",
85768
+ form: "single",
85769
+ optional: false
85770
+ }],
85771
+ "automationControl.enable": [{
85772
+ name: "deviceId",
85773
+ form: "single",
85774
+ optional: false
85775
+ }],
85776
+ "automationControl.trigger": [{
85777
+ name: "deviceId",
85778
+ form: "single",
85779
+ optional: false
85780
+ }],
85781
+ "battery.wakeForStream": [{
85782
+ name: "deviceId",
85783
+ form: "single",
85784
+ optional: false
85785
+ }],
85786
+ "brightness.setBrightness": [{
85787
+ name: "deviceId",
85788
+ form: "single",
85789
+ optional: false
85790
+ }],
85791
+ "button.press": [{
85792
+ name: "deviceId",
85793
+ form: "single",
85794
+ optional: false
85795
+ }],
85796
+ "cameraCredentials.getCredentials": [{
85797
+ name: "deviceId",
85798
+ form: "single",
85799
+ optional: false
85800
+ }],
85801
+ "cameraStreams.getBrokerStreams": [{
85802
+ name: "deviceId",
85803
+ form: "single",
85804
+ optional: false
85805
+ }],
85806
+ "cameraStreams.getCameraStreams": [{
85807
+ name: "deviceId",
85808
+ form: "single",
85809
+ optional: false
85810
+ }],
85811
+ "cameraStreams.getProfileRtspEntries": [{
85812
+ name: "deviceId",
85813
+ form: "single",
85814
+ optional: false
85815
+ }],
85816
+ "cameraStreams.getRtspEntries": [{
85817
+ name: "deviceId",
85818
+ form: "single",
85819
+ optional: false
85820
+ }],
85821
+ "cameraStreams.pickStream": [{
85822
+ name: "deviceId",
85823
+ form: "single",
85824
+ optional: false
85825
+ }],
85826
+ "climateControl.setFanMode": [{
85827
+ name: "deviceId",
85828
+ form: "single",
85829
+ optional: false
85830
+ }],
85831
+ "climateControl.setMode": [{
85832
+ name: "deviceId",
85833
+ form: "single",
85834
+ optional: false
85835
+ }],
85836
+ "climateControl.setPreset": [{
85837
+ name: "deviceId",
85838
+ form: "single",
85839
+ optional: false
85840
+ }],
85841
+ "climateControl.setSwingHorizontal": [{
85842
+ name: "deviceId",
85843
+ form: "single",
85844
+ optional: false
85845
+ }],
85846
+ "climateControl.setSwingVertical": [{
85847
+ name: "deviceId",
85848
+ form: "single",
85849
+ optional: false
85850
+ }],
85851
+ "climateControl.setTarget": [{
85852
+ name: "deviceId",
85853
+ form: "single",
85854
+ optional: false
85855
+ }],
85856
+ "climateControl.setTargetHumidity": [{
85857
+ name: "deviceId",
85858
+ form: "single",
85859
+ optional: false
85860
+ }],
85861
+ "climateControl.setTargetRange": [{
85862
+ name: "deviceId",
85863
+ form: "single",
85864
+ optional: false
85865
+ }],
85866
+ "color.setColor": [{
85867
+ name: "deviceId",
85868
+ form: "single",
85869
+ optional: false
85870
+ }],
85871
+ "consumables.reset": [{
85872
+ name: "deviceId",
85873
+ form: "single",
85874
+ optional: false
85875
+ }],
85876
+ "control.setValue": [{
85877
+ name: "deviceId",
85878
+ form: "single",
85879
+ optional: false
85880
+ }],
85881
+ "cover.close": [{
85882
+ name: "deviceId",
85883
+ form: "single",
85884
+ optional: false
85885
+ }],
85886
+ "cover.open": [{
85887
+ name: "deviceId",
85888
+ form: "single",
85889
+ optional: false
85890
+ }],
85891
+ "cover.setPosition": [{
85892
+ name: "deviceId",
85893
+ form: "single",
85894
+ optional: false
85895
+ }],
85896
+ "cover.setTiltPosition": [{
85897
+ name: "deviceId",
85898
+ form: "single",
85899
+ optional: false
85900
+ }],
85901
+ "cover.stop": [{
85902
+ name: "deviceId",
85903
+ form: "single",
85904
+ optional: false
85905
+ }],
85906
+ "dayNight.getOptions": [{
85907
+ name: "deviceId",
85908
+ form: "single",
85909
+ optional: false
85910
+ }],
85911
+ "dayNight.setSettings": [{
85912
+ name: "deviceId",
85913
+ form: "single",
85914
+ optional: false
85915
+ }],
85916
+ "decoder.createSession": [{
85917
+ name: "deviceId",
85918
+ form: "single",
85919
+ optional: true
85920
+ }],
85921
+ "deviceAdoption.release": [{
85922
+ name: "camDeviceId",
85923
+ form: "single",
85924
+ optional: false
85925
+ }],
85926
+ "deviceAdoption.resync": [{
85927
+ name: "camDeviceId",
85928
+ form: "single",
85929
+ optional: false
85930
+ }],
85931
+ "deviceDiscovery.adoptDevice": [{
85932
+ name: "deviceId",
85933
+ form: "single",
85934
+ optional: false
85935
+ }],
85936
+ "deviceDiscovery.listDiscovered": [{
85937
+ name: "deviceId",
85938
+ form: "single",
85939
+ optional: false
85940
+ }],
85941
+ "deviceDiscovery.refreshDiscovery": [{
85942
+ name: "deviceId",
85943
+ form: "single",
85944
+ optional: false
85945
+ }],
85946
+ "deviceDiscovery.releaseDevice": [{
85947
+ name: "childDeviceId",
85948
+ form: "single",
85949
+ optional: false
85950
+ }, {
85951
+ name: "deviceId",
85952
+ form: "single",
85953
+ optional: false
85954
+ }],
85955
+ "deviceManager.adoptionRelease": [{
85956
+ name: "camDeviceId",
85957
+ form: "single",
85958
+ optional: false
85959
+ }],
85960
+ "deviceManager.adoptionResync": [{
85961
+ name: "camDeviceId",
85962
+ form: "single",
85963
+ optional: false
85964
+ }],
85965
+ "deviceManager.applyInitialMeta": [{
85966
+ name: "deviceId",
85967
+ form: "single",
85968
+ optional: false
85969
+ }, {
85970
+ name: "linkDeviceId",
85971
+ form: "single",
85972
+ optional: true
85973
+ }],
85974
+ "deviceManager.disable": [{
85975
+ name: "deviceId",
85976
+ form: "single",
85977
+ optional: false
85978
+ }],
85979
+ "deviceManager.enable": [{
85980
+ name: "deviceId",
85981
+ form: "single",
85982
+ optional: false
85983
+ }],
85984
+ "deviceManager.getBindings": [{
85985
+ name: "deviceId",
85986
+ form: "single",
85987
+ optional: false
85988
+ }],
85989
+ "deviceManager.getChildren": [{
85990
+ name: "parentDeviceId",
85991
+ form: "single",
85992
+ optional: false
85993
+ }],
85994
+ "deviceManager.getConfigSchema": [{
85995
+ name: "deviceId",
85996
+ form: "single",
85997
+ optional: false
85998
+ }],
85999
+ "deviceManager.getDevice": [{
86000
+ name: "deviceId",
86001
+ form: "single",
86002
+ optional: false
86003
+ }],
86004
+ "deviceManager.getDeviceAggregate": [{
86005
+ name: "deviceId",
86006
+ form: "single",
86007
+ optional: false
86008
+ }],
86009
+ "deviceManager.getDeviceLiveInfoAggregate": [{
86010
+ name: "deviceId",
86011
+ form: "single",
86012
+ optional: false
86013
+ }],
86014
+ "deviceManager.getDeviceSettingsAggregate": [{
86015
+ name: "deviceId",
86016
+ form: "single",
86017
+ optional: false
86018
+ }],
86019
+ "deviceManager.getDeviceStatusAggregate": [{
86020
+ name: "deviceId",
86021
+ form: "single",
86022
+ optional: false
86023
+ }],
86024
+ "deviceManager.getDeviceStatusAggregateBatch": [{
86025
+ name: "deviceIds",
86026
+ form: "array",
86027
+ optional: false
86028
+ }],
86029
+ "deviceManager.getLinkedDevices": [{
86030
+ name: "deviceId",
86031
+ form: "single",
86032
+ optional: false
86033
+ }],
86034
+ "deviceManager.getSettingsSchema": [{
86035
+ name: "deviceId",
86036
+ form: "single",
86037
+ optional: false
86038
+ }],
86039
+ "deviceManager.getStreamProfileMap": [{
86040
+ name: "deviceId",
86041
+ form: "single",
86042
+ optional: false
86043
+ }],
86044
+ "deviceManager.getStreamSources": [{
86045
+ name: "deviceId",
86046
+ form: "single",
86047
+ optional: false
86048
+ }],
86049
+ "deviceManager.getWireableFields": [{
86050
+ name: "deviceId",
86051
+ form: "single",
86052
+ optional: false
86053
+ }],
86054
+ "deviceManager.loadConfig": [{
86055
+ name: "deviceId",
86056
+ form: "single",
86057
+ optional: false
86058
+ }],
86059
+ "deviceManager.loadMeta": [{
86060
+ name: "deviceId",
86061
+ form: "single",
86062
+ optional: false
86063
+ }],
86064
+ "deviceManager.loadRuntimeState": [{
86065
+ name: "deviceId",
86066
+ form: "single",
86067
+ optional: false
86068
+ }],
86069
+ "deviceManager.persistConfig": [{
86070
+ name: "deviceId",
86071
+ form: "single",
86072
+ optional: false
86073
+ }],
86074
+ "deviceManager.probeStreams": [{
86075
+ name: "deviceId",
86076
+ form: "single",
86077
+ optional: false
86078
+ }],
86079
+ "deviceManager.registerDevice": [{
86080
+ name: "parentDeviceId",
86081
+ form: "single",
86082
+ optional: true
86083
+ }],
86084
+ "deviceManager.remove": [{
86085
+ name: "deviceId",
86086
+ form: "single",
86087
+ optional: false
86088
+ }],
86089
+ "deviceManager.removeDevice": [{
86090
+ name: "deviceId",
86091
+ form: "single",
86092
+ optional: false
86093
+ }],
86094
+ "deviceManager.runDeviceAction": [{
86095
+ name: "deviceId",
86096
+ form: "single",
86097
+ optional: false
86098
+ }],
86099
+ "deviceManager.setChildLayout": [{
86100
+ name: "deviceId",
86101
+ form: "single",
86102
+ optional: false
86103
+ }],
86104
+ "deviceManager.setDisabled": [{
86105
+ name: "deviceId",
86106
+ form: "single",
86107
+ optional: false
86108
+ }],
86109
+ "deviceManager.setDisplay": [{
86110
+ name: "deviceId",
86111
+ form: "single",
86112
+ optional: false
86113
+ }],
86114
+ "deviceManager.setIntegrationId": [{
86115
+ name: "deviceId",
86116
+ form: "single",
86117
+ optional: false
86118
+ }],
86119
+ "deviceManager.setLinkDeviceId": [{
86120
+ name: "deviceId",
86121
+ form: "single",
86122
+ optional: false
86123
+ }, {
86124
+ name: "linkDeviceId",
86125
+ form: "single",
86126
+ optional: true
86127
+ }],
86128
+ "deviceManager.setLocation": [{
86129
+ name: "deviceId",
86130
+ form: "single",
86131
+ optional: false
86132
+ }],
86133
+ "deviceManager.setMetadata": [{
86134
+ name: "deviceId",
86135
+ form: "single",
86136
+ optional: false
86137
+ }],
86138
+ "deviceManager.setName": [{
86139
+ name: "deviceId",
86140
+ form: "single",
86141
+ optional: false
86142
+ }],
86143
+ "deviceManager.setPrimaryChildEntityId": [{
86144
+ name: "deviceId",
86145
+ form: "single",
86146
+ optional: false
86147
+ }],
86148
+ "deviceManager.setRole": [{
86149
+ name: "deviceId",
86150
+ form: "single",
86151
+ optional: false
86152
+ }],
86153
+ "deviceManager.setStreamProfileMap": [{
86154
+ name: "deviceId",
86155
+ form: "single",
86156
+ optional: false
86157
+ }],
86158
+ "deviceManager.setType": [{
86159
+ name: "deviceId",
86160
+ form: "single",
86161
+ optional: false
86162
+ }],
86163
+ "deviceManager.setWrapperActive": [{
86164
+ name: "deviceId",
86165
+ form: "single",
86166
+ optional: false
86167
+ }],
86168
+ "deviceManager.testField": [{
86169
+ name: "deviceId",
86170
+ form: "single",
86171
+ optional: false
86172
+ }],
86173
+ "deviceManager.updateConfig": [{
86174
+ name: "deviceId",
86175
+ form: "single",
86176
+ optional: false
86177
+ }],
86178
+ "deviceManager.updateDeviceField": [{
86179
+ name: "deviceId",
86180
+ form: "single",
86181
+ optional: false
86182
+ }],
86183
+ "deviceManager.updateDeviceFieldsBatch": [{
86184
+ name: "deviceId",
86185
+ form: "single",
86186
+ optional: false
86187
+ }],
86188
+ "deviceOps.getConfigEntries": [{
86189
+ name: "deviceId",
86190
+ form: "single",
86191
+ optional: false
86192
+ }],
86193
+ "deviceOps.getRawState": [{
86194
+ name: "deviceId",
86195
+ form: "single",
86196
+ optional: false
86197
+ }],
86198
+ "deviceOps.getSettingsSchema": [{
86199
+ name: "deviceId",
86200
+ form: "single",
86201
+ optional: false
86202
+ }],
86203
+ "deviceOps.getStreamSources": [{
86204
+ name: "deviceId",
86205
+ form: "single",
86206
+ optional: false
86207
+ }],
86208
+ "deviceOps.removeDevice": [{
86209
+ name: "deviceId",
86210
+ form: "single",
86211
+ optional: false
86212
+ }],
86213
+ "deviceOps.runAction": [{
86214
+ name: "deviceId",
86215
+ form: "single",
86216
+ optional: false
86217
+ }],
86218
+ "deviceOps.setConfig": [{
86219
+ name: "deviceId",
86220
+ form: "single",
86221
+ optional: false
86222
+ }],
86223
+ "deviceState.getCapSlice": [{
86224
+ name: "deviceId",
86225
+ form: "single",
86226
+ optional: false
86227
+ }],
86228
+ "deviceState.getSnapshot": [{
86229
+ name: "deviceId",
86230
+ form: "single",
86231
+ optional: false
86232
+ }],
86233
+ "deviceState.setCapSlice": [{
86234
+ name: "deviceId",
86235
+ form: "single",
86236
+ optional: false
86237
+ }],
86238
+ "events.getEventClipUrl": [{
86239
+ name: "deviceId",
86240
+ form: "single",
86241
+ optional: false
86242
+ }],
86243
+ "events.getEvents": [{
86244
+ name: "deviceId",
86245
+ form: "single",
86246
+ optional: false
86247
+ }],
86248
+ "events.getEventThumbnail": [{
86249
+ name: "deviceId",
86250
+ form: "single",
86251
+ optional: false
86252
+ }],
86253
+ "faceGallery.getFaceByTrack": [{
86254
+ name: "deviceId",
86255
+ form: "single",
86256
+ optional: false
86257
+ }],
86258
+ "faceGallery.listRecentFaces": [{
86259
+ name: "deviceId",
86260
+ form: "single",
86261
+ optional: true
86262
+ }],
86263
+ "fanControl.setDirection": [{
86264
+ name: "deviceId",
86265
+ form: "single",
86266
+ optional: false
86267
+ }],
86268
+ "fanControl.setOscillating": [{
86269
+ name: "deviceId",
86270
+ form: "single",
86271
+ optional: false
86272
+ }],
86273
+ "fanControl.setPercentage": [{
86274
+ name: "deviceId",
86275
+ form: "single",
86276
+ optional: false
86277
+ }],
86278
+ "fanControl.setPreset": [{
86279
+ name: "deviceId",
86280
+ form: "single",
86281
+ optional: false
86282
+ }],
86283
+ "humidifier.setMode": [{
86284
+ name: "deviceId",
86285
+ form: "single",
86286
+ optional: false
86287
+ }],
86288
+ "humidifier.setOn": [{
86289
+ name: "deviceId",
86290
+ form: "single",
86291
+ optional: false
86292
+ }],
86293
+ "humidifier.setTargetHumidity": [{
86294
+ name: "deviceId",
86295
+ form: "single",
86296
+ optional: false
86297
+ }],
86298
+ "imageSettings.getOptions": [{
86299
+ name: "deviceId",
86300
+ form: "single",
86301
+ optional: false
86302
+ }],
86303
+ "imageSettings.setSettings": [{
86304
+ name: "deviceId",
86305
+ form: "single",
86306
+ optional: false
86307
+ }],
86308
+ "intercom.endTalkSession": [{
86309
+ name: "deviceId",
86310
+ form: "single",
86311
+ optional: false
86312
+ }],
86313
+ "intercom.handleAnswer": [{
86314
+ name: "deviceId",
86315
+ form: "single",
86316
+ optional: false
86317
+ }],
86318
+ "intercom.pushTalkAudio": [{
86319
+ name: "deviceId",
86320
+ form: "single",
86321
+ optional: false
86322
+ }],
86323
+ "intercom.startSession": [{
86324
+ name: "deviceId",
86325
+ form: "single",
86326
+ optional: false
86327
+ }],
86328
+ "intercom.startTalkSession": [{
86329
+ name: "deviceId",
86330
+ form: "single",
86331
+ optional: false
86332
+ }],
86333
+ "intercom.stopSession": [{
86334
+ name: "deviceId",
86335
+ form: "single",
86336
+ optional: false
86337
+ }],
86338
+ "lawnMowerControl.dock": [{
86339
+ name: "deviceId",
86340
+ form: "single",
86341
+ optional: false
86342
+ }],
86343
+ "lawnMowerControl.pause": [{
86344
+ name: "deviceId",
86345
+ form: "single",
86346
+ optional: false
86347
+ }],
86348
+ "lawnMowerControl.startMowing": [{
86349
+ name: "deviceId",
86350
+ form: "single",
86351
+ optional: false
86352
+ }],
86353
+ "lockControl.lock": [{
86354
+ name: "deviceId",
86355
+ form: "single",
86356
+ optional: false
86357
+ }],
86358
+ "lockControl.open": [{
86359
+ name: "deviceId",
86360
+ form: "single",
86361
+ optional: false
86362
+ }],
86363
+ "lockControl.unlock": [{
86364
+ name: "deviceId",
86365
+ form: "single",
86366
+ optional: false
86367
+ }],
86368
+ "mediaPlayer.next": [{
86369
+ name: "deviceId",
86370
+ form: "single",
86371
+ optional: false
86372
+ }],
86373
+ "mediaPlayer.pause": [{
86374
+ name: "deviceId",
86375
+ form: "single",
86376
+ optional: false
86377
+ }],
86378
+ "mediaPlayer.play": [{
86379
+ name: "deviceId",
86380
+ form: "single",
86381
+ optional: false
86382
+ }],
86383
+ "mediaPlayer.playMedia": [{
86384
+ name: "deviceId",
86385
+ form: "single",
86386
+ optional: false
86387
+ }],
86388
+ "mediaPlayer.previous": [{
86389
+ name: "deviceId",
86390
+ form: "single",
86391
+ optional: false
86392
+ }],
86393
+ "mediaPlayer.seek": [{
86394
+ name: "deviceId",
86395
+ form: "single",
86396
+ optional: false
86397
+ }],
86398
+ "mediaPlayer.selectSource": [{
86399
+ name: "deviceId",
86400
+ form: "single",
86401
+ optional: false
86402
+ }],
86403
+ "mediaPlayer.setMute": [{
86404
+ name: "deviceId",
86405
+ form: "single",
86406
+ optional: false
86407
+ }],
86408
+ "mediaPlayer.setRepeat": [{
86409
+ name: "deviceId",
86410
+ form: "single",
86411
+ optional: false
86412
+ }],
86413
+ "mediaPlayer.setShuffle": [{
86414
+ name: "deviceId",
86415
+ form: "single",
86416
+ optional: false
86417
+ }],
86418
+ "mediaPlayer.setVolume": [{
86419
+ name: "deviceId",
86420
+ form: "single",
86421
+ optional: false
86422
+ }],
86423
+ "mediaPlayer.stop": [{
86424
+ name: "deviceId",
86425
+ form: "single",
86426
+ optional: false
86427
+ }],
86428
+ "motion.isDetected": [{
86429
+ name: "deviceId",
86430
+ form: "single",
86431
+ optional: false
86432
+ }],
86433
+ "motionDetection.analyze": [{
86434
+ name: "deviceId",
86435
+ form: "single",
86436
+ optional: false
86437
+ }],
86438
+ "motionDetection.removeCamera": [{
86439
+ name: "deviceId",
86440
+ form: "single",
86441
+ optional: false
86442
+ }],
86443
+ "motionTrigger.setMotionTrigger": [{
86444
+ name: "deviceId",
86445
+ form: "single",
86446
+ optional: false
86447
+ }],
86448
+ "motionZones.getOptions": [{
86449
+ name: "deviceId",
86450
+ form: "single",
86451
+ optional: false
86452
+ }],
86453
+ "motionZones.setZone": [{
86454
+ name: "deviceId",
86455
+ form: "single",
86456
+ optional: false
86457
+ }],
86458
+ "nativeObjectDetection.setEnabled": [{
86459
+ name: "deviceId",
86460
+ form: "single",
86461
+ optional: false
86462
+ }],
86463
+ "networkQuality.getDeviceStats": [{
86464
+ name: "deviceId",
86465
+ form: "single",
86466
+ optional: false
86467
+ }],
86468
+ "networkQuality.reportClientStats": [{
86469
+ name: "deviceId",
86470
+ form: "single",
86471
+ optional: false
86472
+ }],
86473
+ "notificationRules.setDeviceMuted": [{
86474
+ name: "deviceId",
86475
+ form: "single",
86476
+ optional: false
86477
+ }],
86478
+ "notifier.cancel": [{
86479
+ name: "deviceId",
86480
+ form: "single",
86481
+ optional: false
86482
+ }],
86483
+ "notifier.send": [{
86484
+ name: "deviceId",
86485
+ form: "single",
86486
+ optional: false
86487
+ }],
86488
+ "osd.setOverlay": [{
86489
+ name: "deviceId",
86490
+ form: "single",
86491
+ optional: false
86492
+ }],
86493
+ "osdManager.clearSlotBinding": [{
86494
+ name: "deviceId",
86495
+ form: "single",
86496
+ optional: false
86497
+ }],
86498
+ "osdManager.copyDeviceConfiguration": [{
86499
+ name: "sourceDeviceId",
86500
+ form: "single",
86501
+ optional: false
86502
+ }, {
86503
+ name: "targetDeviceId",
86504
+ form: "single",
86505
+ optional: false
86506
+ }],
86507
+ "osdManager.getDeviceOsd": [{
86508
+ name: "deviceId",
86509
+ form: "single",
86510
+ optional: false
86511
+ }],
86512
+ "osdManager.getSourceCatalog": [{
86513
+ name: "deviceId",
86514
+ form: "single",
86515
+ optional: false
86516
+ }],
86517
+ "osdManager.previewSlot": [{
86518
+ name: "deviceId",
86519
+ form: "single",
86520
+ optional: false
86521
+ }],
86522
+ "osdManager.renderDevice": [{
86523
+ name: "deviceId",
86524
+ form: "single",
86525
+ optional: false
86526
+ }],
86527
+ "osdManager.setSlotBinding": [{
86528
+ name: "deviceId",
86529
+ form: "single",
86530
+ optional: false
86531
+ }],
86532
+ "petFeeder.callPet": [{
86533
+ name: "deviceId",
86534
+ form: "single",
86535
+ optional: false
86536
+ }],
86537
+ "petFeeder.cancelFeed": [{
86538
+ name: "deviceId",
86539
+ form: "single",
86540
+ optional: false
86541
+ }],
86542
+ "petFeeder.feed": [{
86543
+ name: "deviceId",
86544
+ form: "single",
86545
+ optional: false
86546
+ }],
86547
+ "petFeeder.markFoodReplenished": [{
86548
+ name: "deviceId",
86549
+ form: "single",
86550
+ optional: false
86551
+ }],
86552
+ "petFeeder.playSound": [{
86553
+ name: "deviceId",
86554
+ form: "single",
86555
+ optional: false
86556
+ }],
86557
+ "petFeeder.resetDesiccant": [{
86558
+ name: "deviceId",
86559
+ form: "single",
86560
+ optional: false
86561
+ }],
86562
+ "petFeeder.setChildLock": [{
86563
+ name: "deviceId",
86564
+ form: "single",
86565
+ optional: false
86566
+ }],
86567
+ "petFeeder.setFeedSound": [{
86568
+ name: "deviceId",
86569
+ form: "single",
86570
+ optional: false
86571
+ }],
86572
+ "petFeeder.setIndicatorLight": [{
86573
+ name: "deviceId",
86574
+ form: "single",
86575
+ optional: false
86576
+ }],
86577
+ "petFeeder.setVolume": [{
86578
+ name: "deviceId",
86579
+ form: "single",
86580
+ optional: false
86581
+ }],
86582
+ "pipelineAnalytics.clearTracks": [{
86583
+ name: "deviceId",
86584
+ form: "single",
86585
+ optional: false
86586
+ }],
86587
+ "pipelineAnalytics.completeRetrainTrack": [{
86588
+ name: "deviceId",
86589
+ form: "single",
86590
+ optional: false
86591
+ }],
86592
+ "pipelineAnalytics.deleteDeviceEvents": [{
86593
+ name: "deviceId",
86594
+ form: "single",
86595
+ optional: false
86596
+ }],
86597
+ "pipelineAnalytics.deleteTracks": [{
86598
+ name: "deviceId",
86599
+ form: "single",
86600
+ optional: false
86601
+ }],
86602
+ "pipelineAnalytics.deselectRetrainFrame": [{
86603
+ name: "deviceId",
86604
+ form: "single",
86605
+ optional: false
86606
+ }],
86607
+ "pipelineAnalytics.getActiveTracks": [{
86608
+ name: "deviceId",
86609
+ form: "single",
86610
+ optional: false
86611
+ }],
86612
+ "pipelineAnalytics.getAudioEvents": [{
86613
+ name: "deviceId",
86614
+ form: "single",
86615
+ optional: false
86616
+ }],
86617
+ "pipelineAnalytics.getEventDensity": [{
86618
+ name: "deviceId",
86619
+ form: "single",
86620
+ optional: false
86621
+ }],
86622
+ "pipelineAnalytics.getEventMedia": [{
86623
+ name: "deviceId",
86624
+ form: "single",
86625
+ optional: false
86626
+ }],
86627
+ "pipelineAnalytics.getKeyEvents": [{
86628
+ name: "deviceId",
86629
+ form: "single",
86630
+ optional: false
86631
+ }],
86632
+ "pipelineAnalytics.getMotionEvents": [{
86633
+ name: "deviceId",
86634
+ form: "single",
86635
+ optional: false
86636
+ }],
86637
+ "pipelineAnalytics.getObjectEvents": [{
86638
+ name: "deviceId",
86639
+ form: "single",
86640
+ optional: false
86641
+ }],
86642
+ "pipelineAnalytics.getRetrainExportUrl": [{
86643
+ name: "deviceIds",
86644
+ form: "array",
86645
+ optional: true
86646
+ }],
86647
+ "pipelineAnalytics.getSensorEvents": [{
86648
+ name: "deviceId",
86649
+ form: "single",
86650
+ optional: false
86651
+ }],
86652
+ "pipelineAnalytics.getTrack": [{
86653
+ name: "deviceId",
86654
+ form: "single",
86655
+ optional: false
86656
+ }],
86657
+ "pipelineAnalytics.getTrackMedia": [{
86658
+ name: "deviceId",
86659
+ form: "single",
86660
+ optional: false
86661
+ }],
86662
+ "pipelineAnalytics.getTrainingExportSummary": [{
86663
+ name: "deviceIds",
86664
+ form: "array",
86665
+ optional: true
86666
+ }],
86667
+ "pipelineAnalytics.getTrainingExportUrl": [{
86668
+ name: "deviceIds",
86669
+ form: "array",
86670
+ optional: true
86671
+ }],
86672
+ "pipelineAnalytics.listEventKinds": [{
86673
+ name: "deviceId",
86674
+ form: "single",
86675
+ optional: false
86676
+ }],
86677
+ "pipelineAnalytics.listEventKindsBatch": [{
86678
+ name: "deviceIds",
86679
+ form: "array",
86680
+ optional: false
86681
+ }],
86682
+ "pipelineAnalytics.listOpsLog": [{
86683
+ name: "deviceId",
86684
+ form: "single",
86685
+ optional: true
86686
+ }],
86687
+ "pipelineAnalytics.listRecentTracks": [{
86688
+ name: "deviceIds",
86689
+ form: "array",
86690
+ optional: false
86691
+ }],
86692
+ "pipelineAnalytics.listRetrainStaging": [{
86693
+ name: "deviceIds",
86694
+ form: "array",
86695
+ optional: true
86696
+ }],
86697
+ "pipelineAnalytics.listTrackMedia": [{
86698
+ name: "deviceId",
86699
+ form: "single",
86700
+ optional: false
86701
+ }],
86702
+ "pipelineAnalytics.listTracks": [{
86703
+ name: "deviceId",
86704
+ form: "single",
86705
+ optional: false
86706
+ }],
86707
+ "pipelineAnalytics.proposeRetrainAnnotations": [{
86708
+ name: "deviceId",
86709
+ form: "single",
86710
+ optional: false
86711
+ }],
86712
+ "pipelineAnalytics.pruneEventsBefore": [{
86713
+ name: "deviceId",
86714
+ form: "single",
86715
+ optional: false
86716
+ }],
86717
+ "pipelineAnalytics.pruneTracksBefore": [{
86718
+ name: "deviceId",
86719
+ form: "single",
86720
+ optional: false
86721
+ }],
86722
+ "pipelineAnalytics.rebuildObjectEmbeddings": [{
86723
+ name: "deviceId",
86724
+ form: "single",
86725
+ optional: true
86726
+ }],
86727
+ "pipelineAnalytics.restageRetrainTrack": [{
86728
+ name: "deviceId",
86729
+ form: "single",
86730
+ optional: false
86731
+ }],
86732
+ "pipelineAnalytics.saveRetrainAnnotations": [{
86733
+ name: "deviceId",
86734
+ form: "single",
86735
+ optional: false
86736
+ }],
86737
+ "pipelineAnalytics.searchObjectEvents": [{
86738
+ name: "deviceId",
86739
+ form: "single",
86740
+ optional: true
86741
+ }],
86742
+ "pipelineAnalytics.selectRetrainFrames": [{
86743
+ name: "deviceId",
86744
+ form: "single",
86745
+ optional: false
86746
+ }],
86747
+ "pipelineAnalytics.setTrackFlags": [{
86748
+ name: "deviceId",
86749
+ form: "single",
86750
+ optional: false
86751
+ }],
86752
+ "pipelineAnalytics.wipeAllAnalytics": [{
86753
+ name: "deviceId",
86754
+ form: "single",
86755
+ optional: false
86756
+ }],
86757
+ "pipelineExecutor.runPipeline": [{
86758
+ name: "deviceId",
86759
+ form: "single",
86760
+ optional: true
86761
+ }],
86762
+ "pipelineExecutor.runPipelineBatch": [{
86763
+ name: "deviceId",
86764
+ form: "single",
86765
+ optional: true
86766
+ }],
86767
+ "pipelineOrchestrator.assignAudio": [{
86768
+ name: "deviceId",
86769
+ form: "single",
86770
+ optional: false
86771
+ }],
86772
+ "pipelineOrchestrator.assignPipeline": [{
86773
+ name: "deviceId",
86774
+ form: "single",
86775
+ optional: false
86776
+ }],
86777
+ "pipelineOrchestrator.getAudioAssignment": [{
86778
+ name: "deviceId",
86779
+ form: "single",
86780
+ optional: false
86781
+ }],
86782
+ "pipelineOrchestrator.getCameraMetrics": [{
86783
+ name: "deviceId",
86784
+ form: "single",
86785
+ optional: false
86786
+ }],
86787
+ "pipelineOrchestrator.getCameraSettings": [{
86788
+ name: "deviceId",
86789
+ form: "single",
86790
+ optional: false
86791
+ }],
86792
+ "pipelineOrchestrator.getCameraStatus": [{
86793
+ name: "deviceId",
86794
+ form: "single",
86795
+ optional: false
86796
+ }],
86797
+ "pipelineOrchestrator.getCameraStatuses": [{
86798
+ name: "deviceIds",
86799
+ form: "array",
86800
+ optional: true
86801
+ }],
86802
+ "pipelineOrchestrator.getCameraStepOverrides": [{
86803
+ name: "deviceId",
86804
+ form: "single",
86805
+ optional: false
86806
+ }],
86807
+ "pipelineOrchestrator.getCameraSwitches": [{
86808
+ name: "deviceId",
86809
+ form: "single",
86810
+ optional: false
86811
+ }],
86812
+ "pipelineOrchestrator.getPipelineAssignment": [{
86813
+ name: "deviceId",
86814
+ form: "single",
86815
+ optional: false
86816
+ }],
86817
+ "pipelineOrchestrator.getPipelineDevicePin": [{
86818
+ name: "deviceId",
86819
+ form: "single",
86820
+ optional: false
86821
+ }],
86822
+ "pipelineOrchestrator.resolvePipeline": [{
86823
+ name: "deviceId",
86824
+ form: "single",
86825
+ optional: false
86826
+ }],
86827
+ "pipelineOrchestrator.setCameraPipelineForAgent": [{
86828
+ name: "deviceId",
86829
+ form: "single",
86830
+ optional: false
86831
+ }],
86832
+ "pipelineOrchestrator.setCameraStepOverride": [{
86833
+ name: "deviceId",
86834
+ form: "single",
86835
+ optional: false
86836
+ }],
86837
+ "pipelineOrchestrator.setCameraStepToggle": [{
86838
+ name: "deviceId",
86839
+ form: "single",
86840
+ optional: false
86841
+ }],
86842
+ "pipelineOrchestrator.setCameraSwitch": [{
86843
+ name: "deviceId",
86844
+ form: "single",
86845
+ optional: false
86846
+ }],
86847
+ "pipelineOrchestrator.setPipelineDevicePin": [{
86848
+ name: "deviceId",
86849
+ form: "single",
86850
+ optional: false
86851
+ }],
86852
+ "pipelineOrchestrator.unassignAudio": [{
86853
+ name: "deviceId",
86854
+ form: "single",
86855
+ optional: false
86856
+ }],
86857
+ "pipelineOrchestrator.unassignPipeline": [{
86858
+ name: "deviceId",
86859
+ form: "single",
86860
+ optional: false
86861
+ }],
86862
+ "pipelineRunner.attachCamera": [{
86863
+ name: "deviceId",
86864
+ form: "single",
86865
+ optional: false
86866
+ }],
86867
+ "pipelineRunner.detachCamera": [{
86868
+ name: "deviceId",
86869
+ form: "single",
86870
+ optional: false
86871
+ }],
86872
+ "pipelineRunner.getCameraMetrics": [{
86873
+ name: "deviceId",
86874
+ form: "single",
86875
+ optional: false
86876
+ }],
86877
+ "pipelineRunner.reportMotion": [{
86878
+ name: "deviceId",
86879
+ form: "single",
86880
+ optional: false
86881
+ }],
86882
+ "pipelineRunner.runDetailSubtree": [{
86883
+ name: "deviceId",
86884
+ form: "single",
86885
+ optional: false
86886
+ }],
86887
+ "pipelineRunner.runStatelessStep": [{
86888
+ name: "sourceDeviceId",
86889
+ form: "single",
86890
+ optional: false
86891
+ }],
86892
+ "plateGallery.getPlateByTrack": [{
86893
+ name: "deviceId",
86894
+ form: "single",
86895
+ optional: false
86896
+ }],
86897
+ "plateGallery.listPlates": [{
86898
+ name: "deviceId",
86899
+ form: "single",
86900
+ optional: true
86901
+ }],
86902
+ "privacyMask.getOptions": [{
86903
+ name: "deviceId",
86904
+ form: "single",
86905
+ optional: false
86906
+ }],
86907
+ "privacyMask.setAudioEnabled": [{
86908
+ name: "deviceId",
86909
+ form: "single",
86910
+ optional: false
86911
+ }],
86912
+ "privacyMask.setMask": [{
86913
+ name: "deviceId",
86914
+ form: "single",
86915
+ optional: false
86916
+ }],
86917
+ "ptz.continuousMove": [{
86918
+ name: "deviceId",
86919
+ form: "single",
86920
+ optional: false
86921
+ }],
86922
+ "ptz.deletePreset": [{
86923
+ name: "deviceId",
86924
+ form: "single",
86925
+ optional: false
86926
+ }],
86927
+ "ptz.getOptions": [{
86928
+ name: "deviceId",
86929
+ form: "single",
86930
+ optional: false
86931
+ }],
86932
+ "ptz.getPosition": [{
86933
+ name: "deviceId",
86934
+ form: "single",
86935
+ optional: false
86936
+ }],
86937
+ "ptz.getPresets": [{
86938
+ name: "deviceId",
86939
+ form: "single",
86940
+ optional: false
86941
+ }],
86942
+ "ptz.goHome": [{
86943
+ name: "deviceId",
86944
+ form: "single",
86945
+ optional: false
86946
+ }],
86947
+ "ptz.goToPreset": [{
86948
+ name: "deviceId",
86949
+ form: "single",
86950
+ optional: false
86951
+ }],
86952
+ "ptz.move": [{
86953
+ name: "deviceId",
86954
+ form: "single",
86955
+ optional: false
86956
+ }],
86957
+ "ptz.savePreset": [{
86958
+ name: "deviceId",
86959
+ form: "single",
86960
+ optional: false
86961
+ }],
86962
+ "ptz.setAutofocus": [{
86963
+ name: "deviceId",
86964
+ form: "single",
86965
+ optional: false
86966
+ }],
86967
+ "ptz.stop": [{
86968
+ name: "deviceId",
86969
+ form: "single",
86970
+ optional: false
86971
+ }],
86972
+ "ptzAutotrack.getSettings": [{
86973
+ name: "deviceId",
86974
+ form: "single",
86975
+ optional: false
86976
+ }],
86977
+ "ptzAutotrack.getStatus": [{
86978
+ name: "deviceId",
86979
+ form: "single",
86980
+ optional: false
86981
+ }],
86982
+ "ptzAutotrack.setEnabled": [{
86983
+ name: "deviceId",
86984
+ form: "single",
86985
+ optional: false
86986
+ }],
86987
+ "ptzAutotrack.setSettings": [{
86988
+ name: "deviceId",
86989
+ form: "single",
86990
+ optional: false
86991
+ }],
86992
+ "reboot.reboot": [{
86993
+ name: "deviceId",
86994
+ form: "single",
86995
+ optional: false
86996
+ }],
86997
+ "recording.deleteFootprint": [{
86998
+ name: "deviceId",
86999
+ form: "single",
87000
+ optional: false
87001
+ }],
87002
+ "recording.getAvailability": [{
87003
+ name: "deviceId",
87004
+ form: "single",
87005
+ optional: false
87006
+ }],
87007
+ "recording.getDaysWithRecordings": [{
87008
+ name: "deviceId",
87009
+ form: "single",
87010
+ optional: false
87011
+ }],
87012
+ "recording.getDeviceConfig": [{
87013
+ name: "deviceId",
87014
+ form: "single",
87015
+ optional: false
87016
+ }],
87017
+ "recording.getPlaybackManifest": [{
87018
+ name: "deviceId",
87019
+ form: "single",
87020
+ optional: false
87021
+ }],
87022
+ "recording.listOpsLog": [{
87023
+ name: "deviceId",
87024
+ form: "single",
87025
+ optional: true
87026
+ }],
87027
+ "recording.locateSegment": [{
87028
+ name: "deviceId",
87029
+ form: "single",
87030
+ optional: false
87031
+ }],
87032
+ "recording.pruneFootage": [{
87033
+ name: "deviceId",
87034
+ form: "single",
87035
+ optional: false
87036
+ }],
87037
+ "recording.readGopBytes": [{
87038
+ name: "deviceId",
87039
+ form: "single",
87040
+ optional: false
87041
+ }],
87042
+ "recording.readSegmentBytes": [{
87043
+ name: "deviceId",
87044
+ form: "single",
87045
+ optional: false
87046
+ }],
87047
+ "recording.relocateFootage": [{
87048
+ name: "deviceId",
87049
+ form: "single",
87050
+ optional: true
87051
+ }],
87052
+ "recording.renderClip": [{
87053
+ name: "deviceId",
87054
+ form: "single",
87055
+ optional: false
87056
+ }],
87057
+ "recording.renderGif": [{
87058
+ name: "deviceId",
87059
+ form: "single",
87060
+ optional: false
87061
+ }],
87062
+ "recording.rescanStorage": [{
87063
+ name: "deviceId",
87064
+ form: "single",
87065
+ optional: false
87066
+ }],
87067
+ "recording.setDeviceConfig": [{
87068
+ name: "deviceId",
87069
+ form: "single",
87070
+ optional: false
87071
+ }],
87072
+ "recording.startStorageMigrationMove": [{
87073
+ name: "deviceId",
87074
+ form: "single",
87075
+ optional: true
87076
+ }],
87077
+ "recordingExport.createExport": [{
87078
+ name: "deviceId",
87079
+ form: "single",
87080
+ optional: false
87081
+ }],
87082
+ "recordingExport.listExports": [{
87083
+ name: "deviceId",
87084
+ form: "single",
87085
+ optional: true
87086
+ }],
87087
+ "sceneMonitor.captureReference": [{
87088
+ name: "deviceId",
87089
+ form: "single",
87090
+ optional: false
87091
+ }],
87092
+ "sceneMonitor.createScene": [{
87093
+ name: "deviceId",
87094
+ form: "single",
87095
+ optional: false
87096
+ }],
87097
+ "sceneMonitor.deleteReference": [{
87098
+ name: "deviceId",
87099
+ form: "single",
87100
+ optional: false
87101
+ }],
87102
+ "sceneMonitor.deleteScene": [{
87103
+ name: "deviceId",
87104
+ form: "single",
87105
+ optional: false
87106
+ }],
87107
+ "sceneMonitor.listScenes": [{
87108
+ name: "deviceId",
87109
+ form: "single",
87110
+ optional: false
87111
+ }],
87112
+ "sceneMonitor.recheckNow": [{
87113
+ name: "deviceId",
87114
+ form: "single",
87115
+ optional: false
87116
+ }],
87117
+ "sceneMonitor.resetScene": [{
87118
+ name: "deviceId",
87119
+ form: "single",
87120
+ optional: false
87121
+ }],
87122
+ "sceneMonitor.updateScene": [{
87123
+ name: "deviceId",
87124
+ form: "single",
87125
+ optional: false
87126
+ }],
87127
+ "scriptRunner.run": [{
87128
+ name: "deviceId",
87129
+ form: "single",
87130
+ optional: false
87131
+ }],
87132
+ "scriptRunner.stop": [{
87133
+ name: "deviceId",
87134
+ form: "single",
87135
+ optional: false
87136
+ }],
87137
+ "snapshot.getSnapshot": [{
87138
+ name: "deviceId",
87139
+ form: "single",
87140
+ optional: false
87141
+ }],
87142
+ "snapshot.getSnapshotLinks": [{
87143
+ name: "targets",
87144
+ form: "object-array",
87145
+ optional: false,
87146
+ itemField: "deviceId"
87147
+ }],
87148
+ "snapshot.getSnapshotOverview": [{
87149
+ name: "deviceIds",
87150
+ form: "array",
87151
+ optional: false
87152
+ }],
87153
+ "snapshot.invalidateCache": [{
87154
+ name: "deviceId",
87155
+ form: "single",
87156
+ optional: false
87157
+ }],
87158
+ "streamBroker.acquireEgressTranscode": [{
87159
+ name: "deviceId",
87160
+ form: "single",
87161
+ optional: false
87162
+ }],
87163
+ "streamBroker.assignProfile": [{
87164
+ name: "deviceId",
87165
+ form: "single",
87166
+ optional: false
87167
+ }],
87168
+ "streamBroker.getDeviceAudioMute": [{
87169
+ name: "deviceId",
87170
+ form: "single",
87171
+ optional: false
87172
+ }],
87173
+ "streamBroker.getStreamWithCodec": [{
87174
+ name: "deviceId",
87175
+ form: "single",
87176
+ optional: false
87177
+ }],
87178
+ "streamBroker.produceEventMedia": [{
87179
+ name: "deviceId",
87180
+ form: "single",
87181
+ optional: false
87182
+ }],
87183
+ "streamBroker.publishCameraStream": [{
87184
+ name: "deviceId",
87185
+ form: "single",
87186
+ optional: false
87187
+ }],
87188
+ "streamBroker.renderPreBufferClip": [{
87189
+ name: "deviceId",
87190
+ form: "single",
87191
+ optional: false
87192
+ }],
87193
+ "streamBroker.restartProfile": [{
87194
+ name: "deviceId",
87195
+ form: "single",
87196
+ optional: false
87197
+ }],
87198
+ "streamBroker.retractCameraStream": [{
87199
+ name: "deviceId",
87200
+ form: "single",
87201
+ optional: false
87202
+ }],
87203
+ "streamBroker.setDeviceAudioMute": [{
87204
+ name: "deviceId",
87205
+ form: "single",
87206
+ optional: false
87207
+ }],
87208
+ "streamBroker.unassignProfile": [{
87209
+ name: "deviceId",
87210
+ form: "single",
87211
+ optional: false
87212
+ }],
87213
+ "streamCatalog.getCatalog": [{
87214
+ name: "deviceId",
87215
+ form: "single",
87216
+ optional: false
87217
+ }],
87218
+ "streamParams.getConfigSchema": [{
87219
+ name: "deviceId",
87220
+ form: "single",
87221
+ optional: false
87222
+ }],
87223
+ "streamParams.getOptions": [{
87224
+ name: "deviceId",
87225
+ form: "single",
87226
+ optional: false
87227
+ }],
87228
+ "streamParams.setProfile": [{
87229
+ name: "deviceId",
87230
+ form: "single",
87231
+ optional: false
87232
+ }],
87233
+ "switch.setState": [{
87234
+ name: "deviceId",
87235
+ form: "single",
87236
+ optional: false
87237
+ }],
87238
+ "vacuumControl.locate": [{
87239
+ name: "deviceId",
87240
+ form: "single",
87241
+ optional: false
87242
+ }],
87243
+ "vacuumControl.pause": [{
87244
+ name: "deviceId",
87245
+ form: "single",
87246
+ optional: false
87247
+ }],
87248
+ "vacuumControl.returnToBase": [{
87249
+ name: "deviceId",
87250
+ form: "single",
87251
+ optional: false
87252
+ }],
87253
+ "vacuumControl.setFanSpeed": [{
87254
+ name: "deviceId",
87255
+ form: "single",
87256
+ optional: false
87257
+ }],
87258
+ "vacuumControl.start": [{
87259
+ name: "deviceId",
87260
+ form: "single",
87261
+ optional: false
87262
+ }],
87263
+ "vacuumControl.stop": [{
87264
+ name: "deviceId",
87265
+ form: "single",
87266
+ optional: false
87267
+ }],
87268
+ "valve.close": [{
87269
+ name: "deviceId",
87270
+ form: "single",
87271
+ optional: false
87272
+ }],
87273
+ "valve.open": [{
87274
+ name: "deviceId",
87275
+ form: "single",
87276
+ optional: false
87277
+ }],
87278
+ "valve.setPosition": [{
87279
+ name: "deviceId",
87280
+ form: "single",
87281
+ optional: false
87282
+ }],
87283
+ "valve.stop": [{
87284
+ name: "deviceId",
87285
+ form: "single",
87286
+ optional: false
87287
+ }],
87288
+ "videoclips.getClipPlayback": [{
87289
+ name: "deviceId",
87290
+ form: "single",
87291
+ optional: false
87292
+ }],
87293
+ "videoclips.listClips": [{
87294
+ name: "deviceId",
87295
+ form: "single",
87296
+ optional: false
87297
+ }],
87298
+ "waterHeater.setAway": [{
87299
+ name: "deviceId",
87300
+ form: "single",
87301
+ optional: false
87302
+ }],
87303
+ "waterHeater.setOperationMode": [{
87304
+ name: "deviceId",
87305
+ form: "single",
87306
+ optional: false
87307
+ }],
87308
+ "waterHeater.setTargetTemp": [{
87309
+ name: "deviceId",
87310
+ form: "single",
87311
+ optional: false
87312
+ }],
87313
+ "webrtcSession.addIceCandidate": [{
87314
+ name: "deviceId",
87315
+ form: "single",
87316
+ optional: false
87317
+ }],
87318
+ "webrtcSession.closeSession": [{
87319
+ name: "deviceId",
87320
+ form: "single",
87321
+ optional: false
87322
+ }],
87323
+ "webrtcSession.createSession": [{
87324
+ name: "deviceId",
87325
+ form: "single",
87326
+ optional: false
87327
+ }],
87328
+ "webrtcSession.getIceCandidates": [{
87329
+ name: "deviceId",
87330
+ form: "single",
87331
+ optional: false
87332
+ }],
87333
+ "webrtcSession.getSessionState": [{
87334
+ name: "deviceId",
87335
+ form: "single",
87336
+ optional: false
87337
+ }],
87338
+ "webrtcSession.handleAnswer": [{
87339
+ name: "deviceId",
87340
+ form: "single",
87341
+ optional: false
87342
+ }],
87343
+ "webrtcSession.handleOffer": [{
87344
+ name: "deviceId",
87345
+ form: "single",
87346
+ optional: false
87347
+ }],
87348
+ "webrtcSession.hasAdaptiveBitrate": [{
87349
+ name: "deviceId",
87350
+ form: "single",
87351
+ optional: false
87352
+ }],
87353
+ "webrtcSession.listStreams": [{
87354
+ name: "deviceId",
87355
+ form: "single",
87356
+ optional: false
87357
+ }],
87358
+ "zoneAnalytics.getCameraHistory": [{
87359
+ name: "deviceId",
87360
+ form: "single",
87361
+ optional: false
87362
+ }],
87363
+ "zoneAnalytics.getCurrentSnapshot": [{
87364
+ name: "deviceId",
87365
+ form: "single",
87366
+ optional: false
87367
+ }],
87368
+ "zoneAnalytics.getUnzonedHistory": [{
87369
+ name: "deviceId",
87370
+ form: "single",
87371
+ optional: false
87372
+ }],
87373
+ "zoneAnalytics.getZoneHistory": [{
87374
+ name: "deviceId",
87375
+ form: "single",
87376
+ optional: false
87377
+ }],
87378
+ "zoneRules.listRules": [{
87379
+ name: "deviceId",
87380
+ form: "single",
87381
+ optional: false
87382
+ }],
87383
+ "zoneRules.setRules": [{
87384
+ name: "deviceId",
87385
+ form: "single",
87386
+ optional: false
87387
+ }],
87388
+ "zones.addZone": [{
87389
+ name: "deviceId",
87390
+ form: "single",
87391
+ optional: false
87392
+ }],
87393
+ "zones.listZones": [{
87394
+ name: "deviceId",
87395
+ form: "single",
87396
+ optional: false
87397
+ }],
87398
+ "zones.removeZone": [{
87399
+ name: "deviceId",
87400
+ form: "single",
87401
+ optional: false
87402
+ }],
87403
+ "zones.updateZone": [{
87404
+ name: "deviceId",
87405
+ form: "single",
87406
+ optional: false
87407
+ }]
87408
+ });
84622
87409
  Object.freeze({
84623
87410
  "broker": "broker",
84624
87411
  "device-export": "device-export",