@camstack/addon-provider-dreo 0.2.6 → 0.2.7

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 +451 -76
  2. package/dist/addon.mjs +451 -76
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
37
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
38
38
  //#endregion
39
- //#region ../types/dist/event-category-BLcNejAE.mjs
39
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
40
40
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
41
41
  EventCategory["SystemBoot"] = "system.boot";
42
42
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -307,6 +307,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
307
307
  */
308
308
  EventCategory["DeviceStateChanged"] = "device.state-changed";
309
309
  /**
310
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
311
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
312
+ *
313
+ * Emitted only on a change, so a steady scene is silent. It exists so a
314
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
315
+ * one live badge with no push signal at all, and it cost a request every
316
+ * four seconds per visible camera.
317
+ *
318
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
319
+ * keeps a slow reconcile rather than trusting it alone.
320
+ */
321
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
322
+ /**
310
323
  * Cap event fired by every device that registers the `battery`
311
324
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
312
325
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -7236,35 +7249,22 @@ var ConvertResultSchema = object({
7236
7249
  */
7237
7250
  var RecordingWeekdaySchema = number().int().min(0).max(6);
7238
7251
  var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
7239
- var RecordingScheduleSchema = discriminatedUnion("kind", [object({ kind: literal("always") }), object({
7240
- kind: literal("timeOfDay"),
7241
- start: string().regex(HHMM),
7242
- end: string().regex(HHMM),
7243
- /** Restrict to these weekdays; omit = every day. */
7244
- days: array(RecordingWeekdaySchema).optional()
7245
- })]);
7246
- var RecordingModeSchema = _enum([
7247
- "continuous",
7248
- "onMotion",
7249
- "onAudioThreshold"
7250
- ]);
7251
7252
  /**
7252
- * First-class, authoritative per-camera storage mode — the explicit choice the
7253
- * UI reads directly (never inferred from `rules`):
7254
- * - `off` — not recording.
7255
- * - `events` — record only around triggers (motion / audio threshold),
7256
- * with pre/post-buffer.
7257
- * - `continuous` — record 24/7 within the schedule.
7253
+ * DERIVED per-camera storage summary — the single field cheap consumers read
7254
+ * (the viewer's status dot, the camera list) instead of walking `bands`:
7255
+ * - `off` — no band covers the camera (or it is disabled).
7256
+ * - `events` — every band records around triggers only.
7257
+ * - `continuous` — at least one band records continuously.
7258
7258
  *
7259
- * `mode` compiles one-way to the internal `rules[]` consumed by the policy
7260
- * engine (see `compileRules`); `rules[]` is never authored directly anymore.
7259
+ * NEVER authored: the recorder stamps it from the authoritative `bands` on
7260
+ * every save (`activeModeForConfig`). Writing it has no effect.
7261
7261
  */
7262
7262
  var RecordingStorageModeSchema = _enum([
7263
7263
  "off",
7264
7264
  "events",
7265
7265
  "continuous"
7266
7266
  ]);
7267
- /** Which detectors trigger an `events`-mode recording. */
7267
+ /** Which detectors trigger an `events`-mode band. */
7268
7268
  var RecordingTriggersSchema = object({
7269
7269
  motion: boolean().optional(),
7270
7270
  audioThresholdDbfs: number().optional()
@@ -7300,18 +7300,6 @@ var RecordingBandSchema = object({
7300
7300
  preBufferSec: number().min(0).optional(),
7301
7301
  postBufferSec: number().min(0).optional()
7302
7302
  });
7303
- var RecordingRuleSchema = object({
7304
- schedule: RecordingScheduleSchema,
7305
- mode: RecordingModeSchema,
7306
- /** Seconds of footage to retain BEFORE a trigger (applied at keep/discard). */
7307
- preBufferSec: number().min(0).default(0),
7308
- /** Keep recording until this many seconds after the last trigger. */
7309
- postBufferSec: number().min(0).default(0),
7310
- /** Each new trigger restarts the post-buffer window. */
7311
- resetTimeoutOnNewEvent: boolean().default(true),
7312
- /** onAudioThreshold only — dBFS level that counts as a trigger. */
7313
- thresholdDbfs: number().optional()
7314
- });
7315
7303
  /**
7316
7304
  * Per-device retention overrides. Every field is optional; an unset or `0`
7317
7305
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7345,40 +7333,28 @@ var ScrubThumbnailPresetSchema = _enum([
7345
7333
  /**
7346
7334
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7347
7335
  *
7348
- * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
7349
- * are its mode-specific parameters. `rules` is a DEPRECATED authoring input kept
7350
- * only for transition + migration (`migrateRulesToMode`); the policy engine
7351
- * consumes the compiled output of `compileRules(config)`, never `rules` directly.
7336
+ * `bands` is the ONLY authored recording intent: what to record, when, and on
7337
+ * which trigger. `mode` is a derived summary the recorder stamps on save; every
7338
+ * other field is a storage knob (profiles, segment length, retention, scrub).
7339
+ *
7340
+ * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
7341
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
7342
+ * A stale caller must fail loudly — silently stripping its legacy intent would
7343
+ * persist a band-less config, i.e. silently stop recording the camera.
7352
7344
  */
7353
7345
  var RecordingConfigSchema = object({
7354
7346
  enabled: boolean(),
7355
- /** Authoritative storage mode. Absent on legacy targets derived once via
7356
- * `migrateRulesToMode`, then persisted. */
7347
+ /** DERIVED summary of `bands`, stamped by the recorder on every save.
7348
+ * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7357
7349
  mode: RecordingStorageModeSchema.optional(),
7358
7350
  profiles: array(CamProfileSchema).optional(),
7359
7351
  segmentSeconds: number().int().positive().optional(),
7360
- /** Shared recording time-bands for `events` & `continuous` — record only when
7361
- * the wall-clock falls inside one of these windows. Omit or empty = always.
7362
- * `continuous` compiles to one rule per band; `events` to band × trigger. */
7363
- schedules: array(RecordingScheduleSchema).optional(),
7364
- /** Legacy single-band predecessor of `schedules`. Read-compat only — it is
7365
- * normalized into `schedules` on read and never written going forward. (Not
7366
- * tagged `@deprecated`: the normalization paths must read it cast-free.) */
7367
- schedule: RecordingScheduleSchema.optional(),
7368
- /** `events`-mode only — which detectors trigger a recording. */
7369
- triggers: RecordingTriggersSchema.optional(),
7370
- /** `events`-mode only — seconds retained before / after a trigger. */
7371
- preBufferSec: number().min(0).optional(),
7372
- postBufferSec: number().min(0).optional(),
7373
- /** DEPRECATED authoring input; retained for migration/transition. */
7374
- rules: array(RecordingRuleSchema).optional(),
7375
7352
  /**
7376
- * AUTHORITATIVE mode-per-band recording model (recorder). When present it
7377
- * is the single source of truth; the legacy `mode`/`schedules`/`schedule`/
7378
- * `triggers`/`rules` fields above are kept for READ-COMPAT only and are
7379
- * derived into bands once via `migrateConfigToBands`.
7353
+ * AUTHORITATIVE mode-per-band recording model the single source of truth
7354
+ * the recorder's band engine consumes. An empty array = record nothing;
7355
+ * "off" is the absence of a covering band, never a band value.
7380
7356
  */
7381
- bands: array(RecordingBandSchema).optional(),
7357
+ bands: array(RecordingBandSchema).default([]),
7382
7358
  retention: RecordingRetentionSchema.optional(),
7383
7359
  /**
7384
7360
  * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
@@ -7386,8 +7362,15 @@ var RecordingConfigSchema = object({
7386
7362
  * windows only — existing sheets are immutable, and each window's index
7387
7363
  * carries its own tile dims so mixed-preset history renders correctly.
7388
7364
  */
7389
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7390
- });
7365
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional(),
7366
+ /**
7367
+ * OPT-IN thumbnail-strip generation for this camera: every keyframe of the
7368
+ * low recording saved as a JPEG (the fast-drag scrub depth), a derived
7369
+ * cache that eviction reclaims with the footage. Absent/false = no strips
7370
+ * are written and scrub reads exact keyframes at every velocity.
7371
+ */
7372
+ stripsEnabled: boolean().optional()
7373
+ }).strict();
7391
7374
  /**
7392
7375
  * Ops-log — the durable, append-only operations audit shared by the
7393
7376
  * recordings and events management surfaces.
@@ -7406,7 +7389,8 @@ var OpsLogOpSchema = _enum([
7406
7389
  "prune",
7407
7390
  "manual-delete",
7408
7391
  "rescan",
7409
- "retention-run"
7392
+ "retention-run",
7393
+ "relocate"
7410
7394
  ]);
7411
7395
  /** Why the operation ran. */
7412
7396
  var OpsLogReasonSchema = _enum([
@@ -7445,6 +7429,55 @@ var OpsLogQueryInputSchema = object({
7445
7429
  limit: number().int().min(1).max(1e3).optional()
7446
7430
  });
7447
7431
  /**
7432
+ * Entity-relocation job state (storage entity-routing spec, Phase 4).
7433
+ *
7434
+ * One shape shared by the recorder's `relocateFootage` (segments + strips) and
7435
+ * pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
7436
+ * page renders both movers with one component. Jobs are in-RAM (a restart
7437
+ * forgets them — re-running is safe by construction: copy-if-absent, delete
7438
+ * after verify) and each completed/failed run also lands one durable ops-log
7439
+ * row on the owning addon's surface.
7440
+ */
7441
+ var RelocateJobStateSchema = _enum([
7442
+ "running",
7443
+ "done",
7444
+ "failed",
7445
+ "cancelled"
7446
+ ]);
7447
+ var RelocateJobSchema = object({
7448
+ jobId: string(),
7449
+ state: RelocateJobStateSchema,
7450
+ /** Source location — for media relocation this is informational ('*': rows
7451
+ * move from wherever they are to the target). */
7452
+ fromLocationId: string(),
7453
+ toLocationId: string(),
7454
+ /** Scoped device, or null = every device. */
7455
+ deviceId: number().nullable(),
7456
+ /** What the job moves (owner-addon specific: segments/strips or media). */
7457
+ entities: array(string()),
7458
+ filesMoved: number().int(),
7459
+ bytesMoved: number().int(),
7460
+ /** Total files discovered up front; null while (or when) unknown. */
7461
+ filesTotal: number().int().nullable(),
7462
+ startedAt: number(),
7463
+ finishedAt: number().nullable(),
7464
+ error: string().nullable()
7465
+ });
7466
+ var RelocateFootageInputSchema = object({
7467
+ deviceId: number().optional(),
7468
+ fromLocationId: string(),
7469
+ toLocationId: string(),
7470
+ entities: array(_enum(["segments", "strips"])).optional(),
7471
+ /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7472
+ * never allowed to starve live writers. */
7473
+ throttleMbps: number().min(1).max(1e3).optional()
7474
+ });
7475
+ var RelocateMediaInputSchema = object({
7476
+ deviceId: number().optional(),
7477
+ toLocationId: string(),
7478
+ throttleMbps: number().min(1).max(1e3).optional()
7479
+ });
7480
+ /**
7448
7481
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7449
7482
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7450
7483
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7494,6 +7527,13 @@ var StorageLocationSchema = object({
7494
7527
  nodeId: string().optional(),
7495
7528
  isDefault: boolean().default(false),
7496
7529
  isSystem: boolean().default(false),
7530
+ /** COMPUTED at read time by the orchestrator (statfs of the backing volume
7531
+ * for node-local locations it can reach) — never persisted, absent when the
7532
+ * volume is remote/unreachable. The single capacity truth every UI reads. */
7533
+ capacity: object({
7534
+ totalBytes: number(),
7535
+ availableBytes: number()
7536
+ }).nullable().optional(),
7497
7537
  createdAt: number(),
7498
7538
  updatedAt: number()
7499
7539
  });
@@ -8261,7 +8301,8 @@ var NcTaxonomyEntrySchema = object({
8261
8301
  /** Macro/category parent for grouping ('car' → 'vehicle'); null for a top. */
8262
8302
  parentKind: string().nullable()
8263
8303
  });
8264
- object({
8304
+ /** The complete NC picker taxonomy — three grouped buckets. */
8305
+ var NcTaxonomySchema = object({
8265
8306
  videoClasses: array(NcTaxonomyEntrySchema),
8266
8307
  audioKinds: array(NcTaxonomyEntrySchema),
8267
8308
  labels: array(NcTaxonomyEntrySchema)
@@ -9164,6 +9205,7 @@ function shallowEqual(a, b) {
9164
9205
  for (const k of ak) if (a[k] !== b[k]) return false;
9165
9206
  return true;
9166
9207
  }
9208
+ new Set(["devices", "classes"]);
9167
9209
  /**
9168
9210
  * Shared geometry vocabulary for on-frame shape caps — privacy-mask,
9169
9211
  * motion-zones, and the detection zones/lines editor all speak this one
@@ -9322,6 +9364,29 @@ var NcOccupancyConditionSchema = object({
9322
9364
  count: number().int().min(0).default(1),
9323
9365
  sustainSeconds: number().int().min(0).max(3600).default(15)
9324
9366
  });
9367
+ /**
9368
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9369
+ *
9370
+ * The values are not symmetric, and deliberately so — the absent value has to
9371
+ * mean exactly what every rule authored before this condition existed already
9372
+ * does:
9373
+ * - `enter` — entries and every NON-crossing record (movement state,
9374
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9375
+ * an operator who never asked for exits must not start receiving them.
9376
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9377
+ * fails closed, because "the car left the drive" is a question about a
9378
+ * boundary, not about a detection.
9379
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9380
+ *
9381
+ * A rule asking for a direction should normally also scope `zones`, which the
9382
+ * engine evaluates against the crossed zone as well as the current membership
9383
+ * (an exit's membership no longer contains the zone it just left).
9384
+ */
9385
+ var NcCrossingSchema = _enum([
9386
+ "enter",
9387
+ "exit",
9388
+ "any"
9389
+ ]);
9325
9390
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9326
9391
  var NcZoneConditionSchema = object({
9327
9392
  ids: array(string().min(1)).min(1),
@@ -9346,6 +9411,13 @@ var NcConditionsSchema = object({
9346
9411
  /** Veto zones — any hit fails the rule. */
9347
9412
  zonesExclude: array(string().min(1)).optional(),
9348
9413
  /**
9414
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9415
+ * and a closed track carries none, so a `track-end` rule asking for one
9416
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9417
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9418
+ */
9419
+ crossing: NcCrossingSchema.optional(),
9420
+ /**
9349
9421
  * Exact (case-insensitive) match on the record's collapsed `label`
9350
9422
  * (identity name / plate text / subclass).
9351
9423
  */
@@ -9480,17 +9552,85 @@ var NcRuleTargetSchema = object({
9480
9552
  * - `keyFrame` — the clean scene frame (no subject box).
9481
9553
  * - `none` — no attachment.
9482
9554
  */
9483
- var NcMediaPolicySchema = object({ attach: _enum([
9484
- "best",
9485
- "best-matching",
9486
- "keyFrame",
9487
- "none"
9488
- ]).default("best") });
9555
+ /**
9556
+ * WHAT THE PICTURE SHOWS — chosen explicitly, because the implicit ladders
9557
+ * conflate it with the selection strategy and betray the request: asking for
9558
+ * the clean scene frame on an object-event owner used to start at
9559
+ * `fullFrameBoxed`, i.e. the annotated frame (2026-07-30).
9560
+ * - `cropped` — the subject, tight. What "who is at the door" wants.
9561
+ * - `full` — the clean scene, no annotation. What "what is going on" wants.
9562
+ * - `boxed` — the scene WITH the detection boxes drawn, for verifying what
9563
+ * the pipeline actually saw.
9564
+ */
9565
+ var NcMediaFrameSchema = _enum([
9566
+ "cropped",
9567
+ "full",
9568
+ "boxed"
9569
+ ]);
9570
+ var NcMediaPolicySchema = object({
9571
+ attach: _enum([
9572
+ "best",
9573
+ "best-matching",
9574
+ "keyFrame",
9575
+ "none"
9576
+ ]).default("best"),
9577
+ /** What the still shows. Absent = `cropped` (the historical `best` shape). */
9578
+ frame: NcMediaFrameSchema.optional(),
9579
+ /** Crop the attached still to the rule's condition-zone bbox (padded) —
9580
+ * "show me the ZONE", not the whole scene or the subject crop. */
9581
+ zoneCrop: boolean().optional(),
9582
+ /**
9583
+ * Also attach a short GIF cut from the stream broker's clip ring around the
9584
+ * event — NOT from the recording, so the camera does not have to be
9585
+ * recording, and the window sits AROUND the moment instead of a segment
9586
+ * behind it. Fail-closed: a window the ring does not cover contributes no
9587
+ * gif, never a failed notification.
9588
+ */
9589
+ gif: boolean().optional(),
9590
+ /**
9591
+ * Also attach a short MP4 CLIP of the same cut. Same source and the same
9592
+ * fail-closed rule as `gif`. Prefer it where the backend takes video
9593
+ * (telegram, discord, zentik, webhook); backends that do not are handled by
9594
+ * the degrade engine, which drops the video and keeps the still.
9595
+ */
9596
+ clip: boolean().optional(),
9597
+ /** Seconds of footage BEFORE / AFTER the event instant. Defaults 3 / 7. */
9598
+ clipPreRollSec: number().int().min(0).max(30).optional(),
9599
+ clipPostRollSec: number().int().min(0).max(30).optional(),
9600
+ /**
9601
+ * Which stream profile the footage is cut from. Absent = the CHEAPEST
9602
+ * assigned profile: a notification is watched on a phone, so the 4K
9603
+ * rendition would burn CPU to produce a file the client downscales anyway.
9604
+ * A profile that is not assigned falls back to the cheapest, and the render
9605
+ * reports which one actually ran.
9606
+ */
9607
+ profile: CamProfileSchema.optional()
9608
+ });
9609
+ /**
9610
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9611
+ * notification suppresses.
9612
+ * - `shared` (default, and the absent value) — one window for the whole
9613
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9614
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9615
+ * at once and cat→cat still waits.
9616
+ *
9617
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9618
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9619
+ * see `cooldownKey` in the rule engine).
9620
+ */
9621
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9489
9622
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9490
9623
  var NcThrottleSchema = object({
9491
9624
  cooldownSec: number().int().min(0).max(86400).default(60),
9492
9625
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9493
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9626
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9627
+ /**
9628
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9629
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9630
+ * rule authored before this field simply carries none — and the engine
9631
+ * reads absent as `shared`, the pre-existing behaviour.
9632
+ */
9633
+ granularity: NcThrottleGranularitySchema.optional()
9494
9634
  });
9495
9635
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9496
9636
  var NcRuleInputSchema = object({
@@ -9499,7 +9639,19 @@ var NcRuleInputSchema = object({
9499
9639
  delivery: NcDeliverySchema,
9500
9640
  conditions: NcConditionsSchema.default({}),
9501
9641
  schedule: NcScheduleSchema.optional(),
9502
- targets: array(NcRuleTargetSchema).min(1),
9642
+ /** May be empty when `targetUsers` addresses at least one user — the
9643
+ * "at least one addressee" invariant is enforced by the provider, because
9644
+ * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
9645
+ targets: array(NcRuleTargetSchema),
9646
+ /**
9647
+ * USERS this rule addresses in addition to `targets` (Phase 4). At fire
9648
+ * time each user fans out to the personal targets they own
9649
+ * (`config.ownerUserId`), filtered by that user's `allowedDevices` for the
9650
+ * firing camera — a user is never notified about a device they cannot open.
9651
+ * Per-user opt-outs (`disabledTargetIds`) still apply to the fanned-out
9652
+ * targets.
9653
+ */
9654
+ targetUsers: array(string()).optional(),
9503
9655
  media: NcMediaPolicySchema.default({ attach: "best" }),
9504
9656
  throttle: NcThrottleSchema.default({
9505
9657
  cooldownSec: 60,
@@ -9586,6 +9738,7 @@ var NcConditionDescriptorSchema = object({
9586
9738
  "schedule",
9587
9739
  "plateMatcher",
9588
9740
  "packagePhase",
9741
+ "crossingSelect",
9589
9742
  "polygonDraw",
9590
9743
  "occupancy"
9591
9744
  ]),
@@ -9712,7 +9865,10 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
9712
9865
  }), object({ results: array(NcTestResultSchema) }), {
9713
9866
  kind: "mutation",
9714
9867
  auth: "admin"
9715
- }), method(object({}), object({ catalog: array(NcConditionDescriptorSchema) })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
9868
+ }), method(object({}), object({
9869
+ catalog: array(NcConditionDescriptorSchema),
9870
+ taxonomy: NcTaxonomySchema.optional()
9871
+ })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
9716
9872
  /**
9717
9873
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
9718
9874
  *
@@ -10721,6 +10877,28 @@ method(object({
10721
10877
  }), object({ success: literal(true) }), {
10722
10878
  kind: "mutation",
10723
10879
  auth: "admin"
10880
+ }), method(object({
10881
+ deviceId: number(),
10882
+ /** Absent = the LOWEST assigned profile — a notification attachment is
10883
+ * watched on a phone, and the cheap rendition is the right default. */
10884
+ profile: CamProfileSchema.optional(),
10885
+ aroundMs: number(),
10886
+ preRollSec: number().min(0).max(20).default(3),
10887
+ postRollSec: number().min(0).max(20).default(5),
10888
+ format: _enum(["gif", "mp4"]).default("gif"),
10889
+ maxWidth: number().int().min(120).max(1920).default(480),
10890
+ /** GIF only — MP4 keeps the source cadence. */
10891
+ fps: number().int().min(1).max(15).default(5)
10892
+ }), object({
10893
+ base64: string(),
10894
+ mime: string(),
10895
+ bytes: number().int(),
10896
+ /** The profile actually rendered (what the default resolved to). */
10897
+ profile: CamProfileSchema,
10898
+ durationMs: number()
10899
+ }), {
10900
+ kind: "mutation",
10901
+ auth: "admin"
10724
10902
  }), method(_void(), array(CameraStreamSchema).readonly()), method(_void(), array(ProfileSlotSchema).readonly()), method(object({ brokerId: string() }), BrokerStatsSchema), method(object({ brokerId: string() }), object({
10725
10903
  probed: boolean(),
10726
10904
  summary: string()
@@ -18800,7 +18978,10 @@ method(object({
18800
18978
  }), method(object({
18801
18979
  deviceId: number(),
18802
18980
  caps: array(string()).readonly().optional()
18803
- }), record(string(), unknown().nullable()));
18981
+ }), record(string(), unknown().nullable())), method(object({
18982
+ deviceIds: array(number()).readonly(),
18983
+ caps: array(string()).readonly().optional()
18984
+ }), record(string(), record(string(), unknown().nullable())));
18804
18985
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
18805
18986
  deviceId: number(),
18806
18987
  capName: string()
@@ -19731,6 +19912,36 @@ var TargetKindSchema = object({
19731
19912
  icon: string(),
19732
19913
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
19733
19914
  addonId: string(),
19915
+ /**
19916
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
19917
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
19918
+ * when the addon bundles no icon for that kind — the client then falls back
19919
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
19920
+ *
19921
+ * Root-relative on purpose: it resolves against whatever origin serves a web
19922
+ * client, and a native client joins it onto its own hub base.
19923
+ *
19924
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
19925
+ * field that survived only because the runtime cap-router forwards provider
19926
+ * output verbatim — so every consumer had to re-declare it by hand to stop
19927
+ * its own Zod parse from stripping it, and the whole arrangement would have
19928
+ * broken silently the moment output validation was tightened anywhere.
19929
+ */
19930
+ iconUrl: string().optional(),
19931
+ /**
19932
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
19933
+ *
19934
+ * The server knows this and therefore says it, because the client cannot
19935
+ * safely guess: a React-Native client renders SVG and raster through two
19936
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
19937
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
19938
+ * placeholder glyph for every vector icon while the web build looked fine.
19939
+ *
19940
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
19941
+ * not been updated — a client that cannot determine the type should prefer
19942
+ * its raster path, which is the safe default for an unknown image.
19943
+ */
19944
+ iconMediaType: string().optional(),
19734
19945
  configSchema: ConfigSchemaPassthrough,
19735
19946
  supportsDiscovery: boolean(),
19736
19947
  caps: TargetKindCapsSchema
@@ -20178,6 +20389,29 @@ var MotionEventSchema = object({
20178
20389
  * Absent on legacy rows ⇒ treat as `pipeline`.
20179
20390
  */
20180
20391
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
20392
+ /**
20393
+ * The confirmed zone crossing that produced an object event. Present ONLY on
20394
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
20395
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
20396
+ * appearance event carry none, so a rule asking for a direction fails closed
20397
+ * on them.
20398
+ *
20399
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
20400
+ * into its own event, so a frame in which a track enters A while leaving B
20401
+ * produces two events with two directions — never one ambiguous row.
20402
+ *
20403
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
20404
+ * membership the box has NOW, and by definition it no longer contains the zone
20405
+ * that was just left. Without the id here, a zone-scoped rule could never match
20406
+ * the exit it asked for.
20407
+ */
20408
+ var ZoneCrossingSchema = object({
20409
+ direction: _enum(["enter", "exit"]),
20410
+ /** Admin zone id crossed. */
20411
+ zoneId: string(),
20412
+ /** Zone display name at crossing time (falls back to the id). */
20413
+ zoneName: string().optional()
20414
+ });
20181
20415
  var ObjectEventSchema = object({
20182
20416
  ...BaseEventFields,
20183
20417
  kind: literal("object"),
@@ -20204,6 +20438,12 @@ var ObjectEventSchema = object({
20204
20438
  zones: array(string()).readonly().optional(),
20205
20439
  /** Omitted in slim projection. */
20206
20440
  state: TrackStateSchema.optional(),
20441
+ /**
20442
+ * The zone crossing this event IS, when it is one. Absent on every other
20443
+ * event kind (movement state, appearance, package) — see
20444
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
20445
+ */
20446
+ zoneCrossing: ZoneCrossingSchema.optional(),
20207
20447
  /** Detection-frame dimensions in pixels — let consumers normalize the
20208
20448
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20209
20449
  frameWidth: number().optional(),
@@ -20462,6 +20702,15 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20462
20702
  }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
20463
20703
  kind: "mutation",
20464
20704
  auth: "admin"
20705
+ }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
20706
+ kind: "mutation",
20707
+ auth: "admin"
20708
+ }), method(object({}), array(RelocateJobSchema).readonly(), {
20709
+ kind: "query",
20710
+ auth: "admin"
20711
+ }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
20712
+ kind: "mutation",
20713
+ auth: "admin"
20465
20714
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20466
20715
  kind: "query",
20467
20716
  auth: "admin"
@@ -22941,6 +23190,17 @@ var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
22941
23190
  */
22942
23191
  priority: number()
22943
23192
  })).readonly() });
23193
+ /**
23194
+ * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
23195
+ * AUTO (resolved from the candidate ranking at send time); `resolved` reports
23196
+ * what AUTO currently picks, so the UI can show the effective value either way.
23197
+ */
23198
+ var NotificationEndpointSchema = object({
23199
+ /** The operator's explicit choice, or null for AUTO. */
23200
+ baseUrl: string().nullable(),
23201
+ /** What the ranking currently resolves to (null when nothing is reachable). */
23202
+ resolved: string().nullable()
23203
+ });
22944
23204
  var AllowedAddressesSchema = object({
22945
23205
  /**
22946
23206
  * Allowlist of interface addresses operators have explicitly opted
@@ -22963,7 +23223,7 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22963
23223
  * to avoid mixed-content blocks in the browser. The public
22964
23224
  * tunnel always emits `https://` regardless. */
22965
23225
  scheme: _enum(["http", "https"]).optional()
22966
- }), GetConnectionEndpointsResultSchema), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
23226
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
22967
23227
  /**
22968
23228
  * mesh-network — collection cap for mesh-VPN providers.
22969
23229
  *
@@ -23762,7 +24022,12 @@ var RecordingDeviceUsageSchema = object({
23762
24022
  var RecordingLocationUsageSchema = object({
23763
24023
  /** StorageLocation id; null for the legacy/degraded single-root fallback. */
23764
24024
  locationId: string().nullable(),
23765
- /** Bytes of recordings stored on this location. */
24025
+ /** Every location id sharing this row's PHYSICAL volume (aliases). One row
24026
+ * is emitted per physical disk (2026-07-29): two locations on one root
24027
+ * previously rendered as two identical "disks" with a nonsensical used
24028
+ * split — hydrate attribution across aliases is arbitrary by nature. */
24029
+ locationIds: array(string()).optional(),
24030
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
23766
24031
  usedBytes: number(),
23767
24032
  /** Free bytes on the location's volume; null when capacity is unknown (remote). */
23768
24033
  availableBytes: number().nullable(),
@@ -23874,6 +24139,44 @@ method(object({
23874
24139
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23875
24140
  kind: "query",
23876
24141
  auth: "admin"
24142
+ }), method(object({
24143
+ deviceId: number(),
24144
+ aroundMs: number(),
24145
+ preRollSec: number().min(0).max(30).default(2),
24146
+ postRollSec: number().min(0).max(30).default(5),
24147
+ maxWidth: number().int().min(120).max(1280).default(480),
24148
+ fps: number().int().min(1).max(15).default(5)
24149
+ }), object({
24150
+ gifBase64: string(),
24151
+ fromMs: number(),
24152
+ toMs: number()
24153
+ }), {
24154
+ kind: "mutation",
24155
+ auth: "admin"
24156
+ }), method(object({
24157
+ deviceId: number(),
24158
+ aroundMs: number(),
24159
+ preRollSec: number().min(0).max(30).default(3),
24160
+ postRollSec: number().min(0).max(30).default(7),
24161
+ maxWidth: number().int().min(160).max(1920).default(640)
24162
+ }), object({
24163
+ clipBase64: string(),
24164
+ mime: string(),
24165
+ fromMs: number(),
24166
+ toMs: number(),
24167
+ bytes: number().int()
24168
+ }), {
24169
+ kind: "mutation",
24170
+ auth: "admin"
24171
+ }), method(RelocateFootageInputSchema, object({ jobId: string() }), {
24172
+ kind: "mutation",
24173
+ auth: "admin"
24174
+ }), method(object({}), array(RelocateJobSchema).readonly(), {
24175
+ kind: "query",
24176
+ auth: "admin"
24177
+ }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
24178
+ kind: "mutation",
24179
+ auth: "admin"
23877
24180
  });
23878
24181
  /**
23879
24182
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25465,6 +25768,12 @@ Object.freeze({
25465
25768
  addonId: null,
25466
25769
  access: "view"
25467
25770
  },
25771
+ "deviceManager.getDeviceStatusAggregateBatch": {
25772
+ capName: "device-manager",
25773
+ capScope: "system",
25774
+ addonId: null,
25775
+ access: "view"
25776
+ },
25468
25777
  "deviceManager.getLinkedDevices": {
25469
25778
  capName: "device-manager",
25470
25779
  capScope: "system",
@@ -26359,6 +26668,12 @@ Object.freeze({
26359
26668
  addonId: null,
26360
26669
  access: "view"
26361
26670
  },
26671
+ "localNetwork.getNotificationEndpoint": {
26672
+ capName: "local-network",
26673
+ capScope: "system",
26674
+ addonId: null,
26675
+ access: "view"
26676
+ },
26362
26677
  "localNetwork.getPreferred": {
26363
26678
  capName: "local-network",
26364
26679
  capScope: "system",
@@ -26383,6 +26698,12 @@ Object.freeze({
26383
26698
  addonId: null,
26384
26699
  access: "create"
26385
26700
  },
26701
+ "localNetwork.setNotificationEndpoint": {
26702
+ capName: "local-network",
26703
+ capScope: "system",
26704
+ addonId: null,
26705
+ access: "create"
26706
+ },
26386
26707
  "lockControl.lock": {
26387
26708
  capName: "lock-control",
26388
26709
  capScope: "device",
@@ -27025,6 +27346,12 @@ Object.freeze({
27025
27346
  addonId: null,
27026
27347
  access: "create"
27027
27348
  },
27349
+ "pipelineAnalytics.cancelMediaRelocate": {
27350
+ capName: "pipeline-analytics",
27351
+ capScope: "device",
27352
+ addonId: null,
27353
+ access: "create"
27354
+ },
27028
27355
  "pipelineAnalytics.clearTracks": {
27029
27356
  capName: "pipeline-analytics",
27030
27357
  capScope: "device",
@@ -27079,6 +27406,12 @@ Object.freeze({
27079
27406
  addonId: null,
27080
27407
  access: "view"
27081
27408
  },
27409
+ "pipelineAnalytics.getMediaRelocateStatus": {
27410
+ capName: "pipeline-analytics",
27411
+ capScope: "device",
27412
+ addonId: null,
27413
+ access: "view"
27414
+ },
27082
27415
  "pipelineAnalytics.getMotionEvents": {
27083
27416
  capName: "pipeline-analytics",
27084
27417
  capScope: "device",
@@ -27151,6 +27484,12 @@ Object.freeze({
27151
27484
  addonId: null,
27152
27485
  access: "create"
27153
27486
  },
27487
+ "pipelineAnalytics.relocateMedia": {
27488
+ capName: "pipeline-analytics",
27489
+ capScope: "device",
27490
+ addonId: null,
27491
+ access: "create"
27492
+ },
27154
27493
  "pipelineAnalytics.searchObjectEvents": {
27155
27494
  capName: "pipeline-analytics",
27156
27495
  capScope: "device",
@@ -27913,6 +28252,12 @@ Object.freeze({
27913
28252
  addonId: null,
27914
28253
  access: "create"
27915
28254
  },
28255
+ "recording.cancelRelocate": {
28256
+ capName: "recording",
28257
+ capScope: "system",
28258
+ addonId: null,
28259
+ access: "create"
28260
+ },
27916
28261
  "recording.deleteFootprint": {
27917
28262
  capName: "recording",
27918
28263
  capScope: "system",
@@ -27943,6 +28288,12 @@ Object.freeze({
27943
28288
  addonId: null,
27944
28289
  access: "view"
27945
28290
  },
28291
+ "recording.getRelocateStatus": {
28292
+ capName: "recording",
28293
+ capScope: "system",
28294
+ addonId: null,
28295
+ access: "view"
28296
+ },
27946
28297
  "recording.getStorageUsage": {
27947
28298
  capName: "recording",
27948
28299
  capScope: "system",
@@ -27973,6 +28324,24 @@ Object.freeze({
27973
28324
  addonId: null,
27974
28325
  access: "view"
27975
28326
  },
28327
+ "recording.relocateFootage": {
28328
+ capName: "recording",
28329
+ capScope: "system",
28330
+ addonId: null,
28331
+ access: "create"
28332
+ },
28333
+ "recording.renderClip": {
28334
+ capName: "recording",
28335
+ capScope: "system",
28336
+ addonId: null,
28337
+ access: "create"
28338
+ },
28339
+ "recording.renderGif": {
28340
+ capName: "recording",
28341
+ capScope: "system",
28342
+ addonId: null,
28343
+ access: "create"
28344
+ },
27976
28345
  "recording.rescanStorage": {
27977
28346
  capName: "recording",
27978
28347
  capScope: "system",
@@ -28567,6 +28936,12 @@ Object.freeze({
28567
28936
  addonId: null,
28568
28937
  access: "create"
28569
28938
  },
28939
+ "streamBroker.renderPreBufferClip": {
28940
+ capName: "stream-broker",
28941
+ capScope: "system",
28942
+ addonId: null,
28943
+ access: "create"
28944
+ },
28570
28945
  "streamBroker.restartProfile": {
28571
28946
  capName: "stream-broker",
28572
28947
  capScope: "system",