@camstack/types 1.2.16 → 1.2.17

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 (59) hide show
  1. package/dist/addon.d.ts +1 -0
  2. package/dist/addon.js +265 -1
  3. package/dist/addon.mjs +264 -2
  4. package/dist/capabilities/addons.cap.d.ts +7 -7
  5. package/dist/capabilities/broker.cap.d.ts +8 -8
  6. package/dist/capabilities/camera-streams.cap.d.ts +5 -5
  7. package/dist/capabilities/climate-control.cap.d.ts +8 -8
  8. package/dist/capabilities/day-night.cap.d.ts +8 -8
  9. package/dist/capabilities/decoder.cap.d.ts +2 -2
  10. package/dist/capabilities/device-manager.cap.d.ts +2 -2
  11. package/dist/capabilities/face-gallery.cap.d.ts +1 -1
  12. package/dist/capabilities/image-settings.cap.d.ts +16 -16
  13. package/dist/capabilities/index.d.ts +1 -1
  14. package/dist/capabilities/intercom.cap.d.ts +6 -6
  15. package/dist/capabilities/lawn-mower-control.cap.d.ts +4 -4
  16. package/dist/capabilities/llm-runtime.cap.d.ts +3 -3
  17. package/dist/capabilities/llm.cap.d.ts +4 -4
  18. package/dist/capabilities/local-network.cap.d.ts +38 -0
  19. package/dist/capabilities/media-player.cap.d.ts +9 -9
  20. package/dist/capabilities/metrics-provider.cap.d.ts +2 -2
  21. package/dist/capabilities/mqtt-broker.cap.d.ts +3 -3
  22. package/dist/capabilities/notification-output.cap.d.ts +4 -0
  23. package/dist/capabilities/notification-rules.cap.d.ts +236 -18
  24. package/dist/capabilities/pipeline-analytics.cap.d.ts +64 -27
  25. package/dist/capabilities/pipeline-executor.cap.d.ts +2 -2
  26. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +15 -15
  27. package/dist/capabilities/pipeline-runner.cap.d.ts +2 -2
  28. package/dist/capabilities/recording-export.cap.d.ts +7 -7
  29. package/dist/capabilities/recording.cap.d.ts +102 -129
  30. package/dist/capabilities/schemas/orchestrator-metrics.d.ts +2 -2
  31. package/dist/capabilities/schemas/streaming-shared.d.ts +4 -4
  32. package/dist/capabilities/storage-provider.cap.d.ts +44 -0
  33. package/dist/capabilities/storage.cap.d.ts +17 -1
  34. package/dist/capabilities/stream-broker.cap.d.ts +58 -12
  35. package/dist/capabilities/webrtc-session.cap.d.ts +6 -6
  36. package/dist/generated/addon-api.d.ts +5351 -30965
  37. package/dist/generated/cap-input-defaults.d.ts +29 -0
  38. package/dist/generated/device-proxy.d.ts +2 -2
  39. package/dist/generated/method-access-map.d.ts +1 -1
  40. package/dist/generated/system-proxy.d.ts +2 -2
  41. package/dist/index.d.ts +2 -1
  42. package/dist/index.js +589 -134
  43. package/dist/index.mjs +580 -131
  44. package/dist/interfaces/addon.d.ts +9 -0
  45. package/dist/interfaces/ops-log.d.ts +2 -0
  46. package/dist/interfaces/recording-config.d.ts +33 -91
  47. package/dist/interfaces/relocate.d.ts +55 -0
  48. package/dist/interfaces/storage-location-declaration.d.ts +1 -1
  49. package/dist/interfaces/storage-location.d.ts +4 -0
  50. package/dist/interfaces/stream-broker.d.ts +1 -1
  51. package/dist/lifecycle/job.d.ts +6 -6
  52. package/dist/node.js +1 -0
  53. package/dist/node.mjs +1 -0
  54. package/dist/notification/condition-taxonomy.d.ts +61 -0
  55. package/dist/notification/format-transcode.d.ts +12 -1
  56. package/dist/{sleep-C3ceNSsf.mjs → sleep-Ct4Is70g.mjs} +6 -0
  57. package/dist/{sleep-DRg9F6JJ.js → sleep-yfPaamGa.js} +6 -0
  58. package/package.json +1 -1
  59. package/dist/interfaces/recording-config-migrate.d.ts +0 -12
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-DRg9F6JJ.js");
2
+ const require_sleep = require("./sleep-yfPaamGa.js");
3
3
  const require_event_category = require("./event-category-Cdyife4p.js");
4
4
  const require_enums = require("./enums.js");
5
5
  const require_err_msg = require("./err-msg-COpsHMw2.js");
@@ -792,35 +792,22 @@ var EVENT_PAD_MS = {
792
792
  */
793
793
  var RecordingWeekdaySchema = zod.z.number().int().min(0).max(6);
794
794
  var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
795
- var RecordingScheduleSchema = zod.z.discriminatedUnion("kind", [zod.z.object({ kind: zod.z.literal("always") }), zod.z.object({
796
- kind: zod.z.literal("timeOfDay"),
797
- start: zod.z.string().regex(HHMM),
798
- end: zod.z.string().regex(HHMM),
799
- /** Restrict to these weekdays; omit = every day. */
800
- days: zod.z.array(RecordingWeekdaySchema).optional()
801
- })]);
802
- var RecordingModeSchema = zod.z.enum([
803
- "continuous",
804
- "onMotion",
805
- "onAudioThreshold"
806
- ]);
807
795
  /**
808
- * First-class, authoritative per-camera storage mode — the explicit choice the
809
- * UI reads directly (never inferred from `rules`):
810
- * - `off` — not recording.
811
- * - `events` — record only around triggers (motion / audio threshold),
812
- * with pre/post-buffer.
813
- * - `continuous` — record 24/7 within the schedule.
796
+ * DERIVED per-camera storage summary — the single field cheap consumers read
797
+ * (the viewer's status dot, the camera list) instead of walking `bands`:
798
+ * - `off` — no band covers the camera (or it is disabled).
799
+ * - `events` — every band records around triggers only.
800
+ * - `continuous` — at least one band records continuously.
814
801
  *
815
- * `mode` compiles one-way to the internal `rules[]` consumed by the policy
816
- * engine (see `compileRules`); `rules[]` is never authored directly anymore.
802
+ * NEVER authored: the recorder stamps it from the authoritative `bands` on
803
+ * every save (`activeModeForConfig`). Writing it has no effect.
817
804
  */
818
805
  var RecordingStorageModeSchema = zod.z.enum([
819
806
  "off",
820
807
  "events",
821
808
  "continuous"
822
809
  ]);
823
- /** Which detectors trigger an `events`-mode recording. */
810
+ /** Which detectors trigger an `events`-mode band. */
824
811
  var RecordingTriggersSchema = zod.z.object({
825
812
  motion: zod.z.boolean().optional(),
826
813
  audioThresholdDbfs: zod.z.number().optional()
@@ -856,18 +843,6 @@ var RecordingBandSchema = zod.z.object({
856
843
  preBufferSec: zod.z.number().min(0).optional(),
857
844
  postBufferSec: zod.z.number().min(0).optional()
858
845
  });
859
- var RecordingRuleSchema = zod.z.object({
860
- schedule: RecordingScheduleSchema,
861
- mode: RecordingModeSchema,
862
- /** Seconds of footage to retain BEFORE a trigger (applied at keep/discard). */
863
- preBufferSec: zod.z.number().min(0).default(0),
864
- /** Keep recording until this many seconds after the last trigger. */
865
- postBufferSec: zod.z.number().min(0).default(0),
866
- /** Each new trigger restarts the post-buffer window. */
867
- resetTimeoutOnNewEvent: zod.z.boolean().default(true),
868
- /** onAudioThreshold only — dBFS level that counts as a trigger. */
869
- thresholdDbfs: zod.z.number().optional()
870
- });
871
846
  /**
872
847
  * Per-device retention overrides. Every field is optional; an unset or `0`
873
848
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -960,40 +935,28 @@ function resolveScrubThumbnailGeometry(preset) {
960
935
  /**
961
936
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
962
937
  *
963
- * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
964
- * are its mode-specific parameters. `rules` is a DEPRECATED authoring input kept
965
- * only for transition + migration (`migrateRulesToMode`); the policy engine
966
- * consumes the compiled output of `compileRules(config)`, never `rules` directly.
938
+ * `bands` is the ONLY authored recording intent: what to record, when, and on
939
+ * which trigger. `mode` is a derived summary the recorder stamps on save; every
940
+ * other field is a storage knob (profiles, segment length, retention, scrub).
941
+ *
942
+ * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
943
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
944
+ * A stale caller must fail loudly — silently stripping its legacy intent would
945
+ * persist a band-less config, i.e. silently stop recording the camera.
967
946
  */
968
947
  var RecordingConfigSchema = zod.z.object({
969
948
  enabled: zod.z.boolean(),
970
- /** Authoritative storage mode. Absent on legacy targets derived once via
971
- * `migrateRulesToMode`, then persisted. */
949
+ /** DERIVED summary of `bands`, stamped by the recorder on every save.
950
+ * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
972
951
  mode: RecordingStorageModeSchema.optional(),
973
952
  profiles: zod.z.array(require_sleep.CamProfileSchema).optional(),
974
953
  segmentSeconds: zod.z.number().int().positive().optional(),
975
- /** Shared recording time-bands for `events` & `continuous` — record only when
976
- * the wall-clock falls inside one of these windows. Omit or empty = always.
977
- * `continuous` compiles to one rule per band; `events` to band × trigger. */
978
- schedules: zod.z.array(RecordingScheduleSchema).optional(),
979
- /** Legacy single-band predecessor of `schedules`. Read-compat only — it is
980
- * normalized into `schedules` on read and never written going forward. (Not
981
- * tagged `@deprecated`: the normalization paths must read it cast-free.) */
982
- schedule: RecordingScheduleSchema.optional(),
983
- /** `events`-mode only — which detectors trigger a recording. */
984
- triggers: RecordingTriggersSchema.optional(),
985
- /** `events`-mode only — seconds retained before / after a trigger. */
986
- preBufferSec: zod.z.number().min(0).optional(),
987
- postBufferSec: zod.z.number().min(0).optional(),
988
- /** DEPRECATED authoring input; retained for migration/transition. */
989
- rules: zod.z.array(RecordingRuleSchema).optional(),
990
954
  /**
991
- * AUTHORITATIVE mode-per-band recording model (recorder). When present it
992
- * is the single source of truth; the legacy `mode`/`schedules`/`schedule`/
993
- * `triggers`/`rules` fields above are kept for READ-COMPAT only and are
994
- * derived into bands once via `migrateConfigToBands`.
955
+ * AUTHORITATIVE mode-per-band recording model the single source of truth
956
+ * the recorder's band engine consumes. An empty array = record nothing;
957
+ * "off" is the absence of a covering band, never a band value.
995
958
  */
996
- bands: zod.z.array(RecordingBandSchema).optional(),
959
+ bands: zod.z.array(RecordingBandSchema).default([]),
997
960
  retention: RecordingRetentionSchema.optional(),
998
961
  /**
999
962
  * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
@@ -1001,62 +964,30 @@ var RecordingConfigSchema = zod.z.object({
1001
964
  * windows only — existing sheets are immutable, and each window's index
1002
965
  * carries its own tile dims so mixed-preset history renders correctly.
1003
966
  */
1004
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
1005
- });
1006
- //#endregion
1007
- //#region src/interfaces/recording-config-migrate.ts
1008
- /** All seven weekdays (0 = Sunday 6 = Saturday), used when a schedule omits a
1009
- * weekday filter. */
1010
- var ALL_DAYS = [
1011
- 0,
1012
- 1,
1013
- 2,
1014
- 3,
1015
- 4,
1016
- 5,
1017
- 6
1018
- ];
1019
- /** Full-day span used to represent an `always` schedule as a band. The `HHMM`
1020
- * regex maxes out at 23:59, so a 00:00–23:59 same-day span all-day (the band
1021
- * engine treats `end > start` as a normal, non-wrapping span). */
1022
- var ALL_DAY_START = "00:00";
1023
- var ALL_DAY_END = "23:59";
1024
- function bandFromSchedule(schedule, mode, config) {
1025
- const days = schedule.kind === "timeOfDay" && schedule.days ? schedule.days : [...ALL_DAYS];
1026
- const start = schedule.kind === "timeOfDay" ? schedule.start : ALL_DAY_START;
1027
- const end = schedule.kind === "timeOfDay" ? schedule.end : ALL_DAY_END;
1028
- if (mode === "continuous") return {
1029
- days,
1030
- start,
1031
- end,
1032
- mode
1033
- };
1034
- return {
1035
- days,
1036
- start,
1037
- end,
1038
- mode,
1039
- ...config.triggers ? { triggers: config.triggers } : {},
1040
- ...config.preBufferSec !== void 0 ? { preBufferSec: config.preBufferSec } : {},
1041
- ...config.postBufferSec !== void 0 ? { postBufferSec: config.postBufferSec } : {}
1042
- };
1043
- }
1044
- /**
1045
- * One-way migration from the legacy recording-config shape to the authoritative
1046
- * mode-per-band model. Pure (no clock access).
1047
- *
1048
- * - If `config.bands` is present, it is authoritative and returned as-is.
1049
- * - Otherwise the result is derived from the legacy fields: a disabled config or
1050
- * `mode: 'off'` yields no bands; every legacy schedule maps to one band whose
1051
- * mode is `continuous` when `config.mode === 'continuous'`, else `events`
1052
- * (carrying `triggers`/`preBufferSec`/`postBufferSec`).
1053
- */
1054
- function migrateConfigToBands(config) {
1055
- if (config.bands) return config.bands;
1056
- if (!config.enabled || config.mode === "off") return [];
1057
- const schedules = config.schedules ?? (config.schedule ? [config.schedule] : [{ kind: "always" }]);
1058
- const bandMode = config.mode === "continuous" ? "continuous" : "events";
1059
- return schedules.map((schedule) => bandFromSchedule(schedule, bandMode, config));
967
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional(),
968
+ /**
969
+ * OPT-IN thumbnail-strip generation for this camera: every keyframe of the
970
+ * low recording saved as a JPEG (the fast-drag scrub depth), a derived
971
+ * cache that eviction reclaims with the footage. Absent/false = no strips
972
+ * are written and scrub reads exact keyframes at every velocity.
973
+ */
974
+ stripsEnabled: zod.z.boolean().optional()
975
+ }).strict();
976
+ /**
977
+ * Derive the {@link RecordingStorageModeSchema} summary from the authoritative
978
+ * bands: `continuous` when any band records continuously, `events` when a band
979
+ * exists at all, else `off`. Pure.
980
+ *
981
+ * THE one definition: the recorder stamps `config.mode` with it on save and
982
+ * reports it as the cap's `activeMode`, and the settings UI reads it back to
983
+ * decide which authoring tab a config opens in. Two copies would drift, and the
984
+ * symptom (a status dot disagreeing with what is being recorded) is exactly the
985
+ * bug this field was added to fix.
986
+ */
987
+ function deriveRecordingMode(config) {
988
+ if (!config.enabled || config.bands.length === 0) return "off";
989
+ if (config.bands.some((band) => band.mode === "continuous")) return "continuous";
990
+ return "events";
1060
991
  }
1061
992
  //#endregion
1062
993
  //#region src/interfaces/ops-log.ts
@@ -1078,7 +1009,8 @@ var OpsLogOpSchema = zod.z.enum([
1078
1009
  "prune",
1079
1010
  "manual-delete",
1080
1011
  "rescan",
1081
- "retention-run"
1012
+ "retention-run",
1013
+ "relocate"
1082
1014
  ]);
1083
1015
  /** Why the operation ran. */
1084
1016
  var OpsLogReasonSchema = zod.z.enum([
@@ -1124,6 +1056,57 @@ var OPS_LOG_RING_DEFAULT_MAX = 500;
1124
1056
  /** Default page size for a `listOpsLog` query when the caller omits `limit`. */
1125
1057
  var OPS_LOG_DEFAULT_LIMIT = 200;
1126
1058
  //#endregion
1059
+ //#region src/interfaces/relocate.ts
1060
+ /**
1061
+ * Entity-relocation job state (storage entity-routing spec, Phase 4).
1062
+ *
1063
+ * One shape shared by the recorder's `relocateFootage` (segments + strips) and
1064
+ * pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
1065
+ * page renders both movers with one component. Jobs are in-RAM (a restart
1066
+ * forgets them — re-running is safe by construction: copy-if-absent, delete
1067
+ * after verify) and each completed/failed run also lands one durable ops-log
1068
+ * row on the owning addon's surface.
1069
+ */
1070
+ var RelocateJobStateSchema = zod.z.enum([
1071
+ "running",
1072
+ "done",
1073
+ "failed",
1074
+ "cancelled"
1075
+ ]);
1076
+ var RelocateJobSchema = zod.z.object({
1077
+ jobId: zod.z.string(),
1078
+ state: RelocateJobStateSchema,
1079
+ /** Source location — for media relocation this is informational ('*': rows
1080
+ * move from wherever they are to the target). */
1081
+ fromLocationId: zod.z.string(),
1082
+ toLocationId: zod.z.string(),
1083
+ /** Scoped device, or null = every device. */
1084
+ deviceId: zod.z.number().nullable(),
1085
+ /** What the job moves (owner-addon specific: segments/strips or media). */
1086
+ entities: zod.z.array(zod.z.string()),
1087
+ filesMoved: zod.z.number().int(),
1088
+ bytesMoved: zod.z.number().int(),
1089
+ /** Total files discovered up front; null while (or when) unknown. */
1090
+ filesTotal: zod.z.number().int().nullable(),
1091
+ startedAt: zod.z.number(),
1092
+ finishedAt: zod.z.number().nullable(),
1093
+ error: zod.z.string().nullable()
1094
+ });
1095
+ var RelocateFootageInputSchema = zod.z.object({
1096
+ deviceId: zod.z.number().optional(),
1097
+ fromLocationId: zod.z.string(),
1098
+ toLocationId: zod.z.string(),
1099
+ entities: zod.z.array(zod.z.enum(["segments", "strips"])).optional(),
1100
+ /** Copy throttle in MB/s (default 40) — the drain is a background chore,
1101
+ * never allowed to starve live writers. */
1102
+ throttleMbps: zod.z.number().min(1).max(1e3).optional()
1103
+ });
1104
+ var RelocateMediaInputSchema = zod.z.object({
1105
+ deviceId: zod.z.number().optional(),
1106
+ toLocationId: zod.z.string(),
1107
+ throttleMbps: zod.z.number().min(1).max(1e3).optional()
1108
+ });
1109
+ //#endregion
1127
1110
  //#region src/interfaces/logging.ts
1128
1111
  /**
1129
1112
  * Severity ordering. Declared once here because it had already been re-declared
@@ -1197,6 +1180,13 @@ var StorageLocationSchema = zod.z.object({
1197
1180
  nodeId: zod.z.string().optional(),
1198
1181
  isDefault: zod.z.boolean().default(false),
1199
1182
  isSystem: zod.z.boolean().default(false),
1183
+ /** COMPUTED at read time by the orchestrator (statfs of the backing volume
1184
+ * for node-local locations it can reach) — never persisted, absent when the
1185
+ * volume is remote/unreachable. The single capacity truth every UI reads. */
1186
+ capacity: zod.z.object({
1187
+ totalBytes: zod.z.number(),
1188
+ availableBytes: zod.z.number()
1189
+ }).nullable().optional(),
1200
1190
  createdAt: zod.z.number(),
1201
1191
  updatedAt: zod.z.number()
1202
1192
  });
@@ -4344,9 +4334,20 @@ var DEGRADE_ORDER = {
4344
4334
  "markdown"
4345
4335
  ]
4346
4336
  };
4347
- /** Pick the best target format the kind supports for a given source format. */
4337
+ /**
4338
+ * Pick the best target format the kind supports for a given source format.
4339
+ *
4340
+ * TOLERANT of a source outside the union. `NotificationSchema.format` carries
4341
+ * `.default('text')`, but that default only runs where the schema is PARSED —
4342
+ * the tRPC router. A cap call arriving from another addon (`ctx.api`) skips
4343
+ * that parse, so `format` reaches here `undefined`, and the bare
4344
+ * `DEGRADE_ORDER[source]` threw `is not iterable`. That broke the one promise
4345
+ * the degrade path makes ("never throws — degradation is reported, never
4346
+ * surfaced as an error") and dead-lettered every Notification-Center delivery
4347
+ * for a day (2026-07-30). An unknown source degrades like `text`.
4348
+ */
4348
4349
  function resolveFormat(source, supported) {
4349
- for (const candidate of DEGRADE_ORDER[source]) if (supported.includes(candidate)) return candidate;
4350
+ for (const candidate of DEGRADE_ORDER[source] ?? DEGRADE_ORDER.text) if (supported.includes(candidate)) return candidate;
4350
4351
  return supported[0] ?? "text";
4351
4352
  }
4352
4353
  /** Transcode a body between formats. */
@@ -4393,7 +4394,7 @@ function resolveLevel(levels, n) {
4393
4394
  })[0]);
4394
4395
  }
4395
4396
  function splitBody(body, maxLen) {
4396
- if (maxLen <= 0 || body.length <= maxLen) return [body];
4397
+ if (!Number.isFinite(maxLen) || maxLen <= 0 || body.length <= maxLen) return [body];
4397
4398
  const parts = [];
4398
4399
  for (let i = 0; i < body.length; i += maxLen) parts.push(body.slice(i, i + maxLen));
4399
4400
  return parts;
@@ -4477,6 +4478,114 @@ function prepareNotification(caps, n) {
4477
4478
  };
4478
4479
  }
4479
4480
  //#endregion
4481
+ //#region src/notification/condition-taxonomy.ts
4482
+ /** Condition ids that render the class picker (video macros/subs + audio). */
4483
+ var CLASS_CONDITION_IDS = new Set(["classes", "classesExclude"]);
4484
+ /** Condition ids that render the sensor/control label picker. */
4485
+ var LABEL_CONDITION_IDS = new Set(["sensorKinds"]);
4486
+ function toOption(entry) {
4487
+ return {
4488
+ value: entry.kind,
4489
+ label: entry.label
4490
+ };
4491
+ }
4492
+ /**
4493
+ * Group detection entries by their macro. A macro entry (`parentKind === null`)
4494
+ * opens its own group and is itself the first option in it; subs fall under
4495
+ * their `parentKind`. First-appearance order is preserved, which is why
4496
+ * `buildNcTaxonomy` emits macros before their subs.
4497
+ */
4498
+ function groupVideoClasses(videoClasses) {
4499
+ const order = [];
4500
+ const byKey = /* @__PURE__ */ new Map();
4501
+ const ensure = (key, fallbackLabel) => {
4502
+ const existing = byKey.get(key);
4503
+ if (existing !== void 0) return existing;
4504
+ const created = {
4505
+ fallbackLabel,
4506
+ options: []
4507
+ };
4508
+ byKey.set(key, created);
4509
+ order.push(key);
4510
+ return created;
4511
+ };
4512
+ for (const entry of videoClasses) {
4513
+ const key = entry.parentKind ?? entry.kind;
4514
+ const group = ensure(key, entry.parentKind === null ? entry.label : key);
4515
+ group.options.push(toOption(entry));
4516
+ if (entry.parentKind === null) group.fallbackLabel = entry.label;
4517
+ }
4518
+ return order.flatMap((key) => {
4519
+ const group = byKey.get(key);
4520
+ return group === void 0 ? [] : [{
4521
+ key,
4522
+ fallbackLabel: group.fallbackLabel,
4523
+ options: group.options
4524
+ }];
4525
+ });
4526
+ }
4527
+ /** Group sensor/control labels by their category parent (`sensor` | `control`). */
4528
+ function groupLabels(labels) {
4529
+ const order = [];
4530
+ const byKey = /* @__PURE__ */ new Map();
4531
+ for (const entry of labels) {
4532
+ const key = entry.parentKind ?? "other";
4533
+ const existing = byKey.get(key);
4534
+ if (existing === void 0) {
4535
+ byKey.set(key, [toOption(entry)]);
4536
+ order.push(key);
4537
+ } else existing.push(toOption(entry));
4538
+ }
4539
+ return order.flatMap((key) => {
4540
+ const options = byKey.get(key);
4541
+ return options === void 0 ? [] : [{
4542
+ key,
4543
+ fallbackLabel: key,
4544
+ options
4545
+ }];
4546
+ });
4547
+ }
4548
+ /**
4549
+ * Build the grouped picker model for a condition id, or `null` when the
4550
+ * condition has no fixed vocabulary (→ free-text tag input).
4551
+ */
4552
+ function pickerForCondition(conditionId, taxonomy) {
4553
+ if (!taxonomy) return null;
4554
+ if (CLASS_CONDITION_IDS.has(conditionId)) {
4555
+ const groups = groupVideoClasses(taxonomy.videoClasses);
4556
+ if (taxonomy.audioKinds.length > 0) groups.push({
4557
+ key: "audio",
4558
+ fallbackLabel: "Audio",
4559
+ options: taxonomy.audioKinds.map(toOption)
4560
+ });
4561
+ return groups.length > 0 ? { groups } : null;
4562
+ }
4563
+ if (LABEL_CONDITION_IDS.has(conditionId)) {
4564
+ const groups = groupLabels(taxonomy.labels);
4565
+ return groups.length > 0 ? { groups } : null;
4566
+ }
4567
+ return null;
4568
+ }
4569
+ /** All option values across a picker's groups (for custom-value detection). */
4570
+ function knownValues(picker) {
4571
+ const set = /* @__PURE__ */ new Set();
4572
+ for (const group of picker.groups) for (const option of group.options) set.add(option.value);
4573
+ return set;
4574
+ }
4575
+ /**
4576
+ * The condition ids shown in the ALWAYS-VISIBLE area of a rule editor. Every
4577
+ * other catalog descriptor is advanced and lives behind an expander.
4578
+ *
4579
+ * Shared for the same reason as the picker model: two surfaces disagreeing
4580
+ * about what counts as "basic" is a worse outcome than either choice.
4581
+ */
4582
+ var NC_BASE_CONDITION_KEYS = ["devices", "classes"];
4583
+ var BASE_CONDITION_SET = new Set(NC_BASE_CONDITION_KEYS);
4584
+ /** True when a condition key belongs to the BASE surface. */
4585
+ function isBaseConditionKey(key) {
4586
+ return BASE_CONDITION_SET.has(key);
4587
+ }
4588
+ //#endregion
4480
4589
  //#region src/capabilities/mask-shape.ts
4481
4590
  /**
4482
4591
  * Shared geometry vocabulary for on-frame shape caps — privacy-mask,
@@ -4807,12 +4916,60 @@ var NcRuleTargetSchema = zod.z.object({
4807
4916
  * - `keyFrame` — the clean scene frame (no subject box).
4808
4917
  * - `none` — no attachment.
4809
4918
  */
4810
- var NcMediaPolicySchema = zod.z.object({ attach: zod.z.enum([
4811
- "best",
4812
- "best-matching",
4813
- "keyFrame",
4814
- "none"
4815
- ]).default("best") });
4919
+ /**
4920
+ * WHAT THE PICTURE SHOWS — chosen explicitly, because the implicit ladders
4921
+ * conflate it with the selection strategy and betray the request: asking for
4922
+ * the clean scene frame on an object-event owner used to start at
4923
+ * `fullFrameBoxed`, i.e. the annotated frame (2026-07-30).
4924
+ * - `cropped` — the subject, tight. What "who is at the door" wants.
4925
+ * - `full` — the clean scene, no annotation. What "what is going on" wants.
4926
+ * - `boxed` — the scene WITH the detection boxes drawn, for verifying what
4927
+ * the pipeline actually saw.
4928
+ */
4929
+ var NcMediaFrameSchema = zod.z.enum([
4930
+ "cropped",
4931
+ "full",
4932
+ "boxed"
4933
+ ]);
4934
+ var NcMediaPolicySchema = zod.z.object({
4935
+ attach: zod.z.enum([
4936
+ "best",
4937
+ "best-matching",
4938
+ "keyFrame",
4939
+ "none"
4940
+ ]).default("best"),
4941
+ /** What the still shows. Absent = `cropped` (the historical `best` shape). */
4942
+ frame: NcMediaFrameSchema.optional(),
4943
+ /** Crop the attached still to the rule's condition-zone bbox (padded) —
4944
+ * "show me the ZONE", not the whole scene or the subject crop. */
4945
+ zoneCrop: zod.z.boolean().optional(),
4946
+ /**
4947
+ * Also attach a short GIF cut from the stream broker's clip ring around the
4948
+ * event — NOT from the recording, so the camera does not have to be
4949
+ * recording, and the window sits AROUND the moment instead of a segment
4950
+ * behind it. Fail-closed: a window the ring does not cover contributes no
4951
+ * gif, never a failed notification.
4952
+ */
4953
+ gif: zod.z.boolean().optional(),
4954
+ /**
4955
+ * Also attach a short MP4 CLIP of the same cut. Same source and the same
4956
+ * fail-closed rule as `gif`. Prefer it where the backend takes video
4957
+ * (telegram, discord, zentik, webhook); backends that do not are handled by
4958
+ * the degrade engine, which drops the video and keeps the still.
4959
+ */
4960
+ clip: zod.z.boolean().optional(),
4961
+ /** Seconds of footage BEFORE / AFTER the event instant. Defaults 3 / 7. */
4962
+ clipPreRollSec: zod.z.number().int().min(0).max(30).optional(),
4963
+ clipPostRollSec: zod.z.number().int().min(0).max(30).optional(),
4964
+ /**
4965
+ * Which stream profile the footage is cut from. Absent = the CHEAPEST
4966
+ * assigned profile: a notification is watched on a phone, so the 4K
4967
+ * rendition would burn CPU to produce a file the client downscales anyway.
4968
+ * A profile that is not assigned falls back to the cheapest, and the render
4969
+ * reports which one actually ran.
4970
+ */
4971
+ profile: require_sleep.CamProfileSchema.optional()
4972
+ });
4816
4973
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
4817
4974
  var NcThrottleSchema = zod.z.object({
4818
4975
  cooldownSec: zod.z.number().int().min(0).max(86400).default(60),
@@ -4826,7 +4983,19 @@ var NcRuleInputSchema = zod.z.object({
4826
4983
  delivery: NcDeliverySchema,
4827
4984
  conditions: NcConditionsSchema.default({}),
4828
4985
  schedule: NcScheduleSchema.optional(),
4829
- targets: zod.z.array(NcRuleTargetSchema).min(1),
4986
+ /** May be empty when `targetUsers` addresses at least one user — the
4987
+ * "at least one addressee" invariant is enforced by the provider, because
4988
+ * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
4989
+ targets: zod.z.array(NcRuleTargetSchema),
4990
+ /**
4991
+ * USERS this rule addresses in addition to `targets` (Phase 4). At fire
4992
+ * time each user fans out to the personal targets they own
4993
+ * (`config.ownerUserId`), filtered by that user's `allowedDevices` for the
4994
+ * firing camera — a user is never notified about a device they cannot open.
4995
+ * Per-user opt-outs (`disabledTargetIds`) still apply to the fanned-out
4996
+ * targets.
4997
+ */
4998
+ targetUsers: zod.z.array(zod.z.string()).optional(),
4830
4999
  media: NcMediaPolicySchema.default({ attach: "best" }),
4831
5000
  throttle: NcThrottleSchema.default({
4832
5001
  cooldownSec: 60,
@@ -5301,7 +5470,20 @@ var notificationRulesCapability = {
5301
5470
  kind: "mutation",
5302
5471
  auth: "admin"
5303
5472
  }),
5304
- getConditionCatalog: require_sleep.method(zod.z.object({}), zod.z.object({ catalog: zod.z.array(NcConditionDescriptorSchema) })),
5473
+ /**
5474
+ * The machine-readable condition surface a rule editor renders from, plus
5475
+ * the picker VOCABULARY those conditions draw on.
5476
+ *
5477
+ * `taxonomy` is optional so an older provider that only returns `catalog`
5478
+ * still validates; a client that receives none keeps free-text inputs.
5479
+ * It was previously served ONLY on the `nc.getConditionCatalog` bridge
5480
+ * action, which is why the viewer had grouped pickers and the admin UI —
5481
+ * which reaches this cap — was stuck typing `person` from memory.
5482
+ */
5483
+ getConditionCatalog: require_sleep.method(zod.z.object({}), zod.z.object({
5484
+ catalog: zod.z.array(NcConditionDescriptorSchema),
5485
+ taxonomy: NcTaxonomySchema.optional()
5486
+ })),
5305
5487
  /**
5306
5488
  * Queryable delivery history — a read-only view over the durable outbox
5307
5489
  * (fired rule, subject summary, target, status, timestamps, error on a
@@ -6390,6 +6572,48 @@ var streamBrokerCapability = {
6390
6572
  kind: "mutation",
6391
6573
  auth: "admin"
6392
6574
  }),
6575
+ /**
6576
+ * Render a short GIF or MP4 from the broker's PRE-BUFFER around an instant.
6577
+ *
6578
+ * The pre-buffer is the only source that already holds the seconds BEFORE
6579
+ * an event (it is what it exists for — "when an addon needs the last N
6580
+ * seconds of video for clip creation"). Cutting from recorded segments
6581
+ * cannot: only finalized segments are addressable and the one covering the
6582
+ * moment is still being written, so a segment-sourced clip is either
6583
+ * delayed or shows the approach instead of the event (operator,
6584
+ * 2026-07-30). It also means a camera does not have to be RECORDING to
6585
+ * produce a notification clip.
6586
+ *
6587
+ * Returns muxed BYTES, never packets: the frame plane never crosses a
6588
+ * process boundary at frame rate (D9/D18) — this is one compressed,
6589
+ * on-demand payload per notification.
6590
+ *
6591
+ * Waits up to `postRollSec` for the tail to accumulate, so the window is
6592
+ * CENTRED on `aroundMs`.
6593
+ */
6594
+ renderPreBufferClip: require_sleep.method(zod.z.object({
6595
+ deviceId: zod.z.number(),
6596
+ /** Absent = the LOWEST assigned profile — a notification attachment is
6597
+ * watched on a phone, and the cheap rendition is the right default. */
6598
+ profile: require_sleep.CamProfileSchema.optional(),
6599
+ aroundMs: zod.z.number(),
6600
+ preRollSec: zod.z.number().min(0).max(20).default(3),
6601
+ postRollSec: zod.z.number().min(0).max(20).default(5),
6602
+ format: zod.z.enum(["gif", "mp4"]).default("gif"),
6603
+ maxWidth: zod.z.number().int().min(120).max(1920).default(480),
6604
+ /** GIF only — MP4 keeps the source cadence. */
6605
+ fps: zod.z.number().int().min(1).max(15).default(5)
6606
+ }), zod.z.object({
6607
+ base64: zod.z.string(),
6608
+ mime: zod.z.string(),
6609
+ bytes: zod.z.number().int(),
6610
+ /** The profile actually rendered (what the default resolved to). */
6611
+ profile: require_sleep.CamProfileSchema,
6612
+ durationMs: zod.z.number()
6613
+ }), {
6614
+ kind: "mutation",
6615
+ auth: "admin"
6616
+ }),
6393
6617
  listAllCameraStreams: require_sleep.method(zod.z.void(), zod.z.array(require_sleep.CameraStreamSchema).readonly()),
6394
6618
  listAllProfileSlots: require_sleep.method(zod.z.void(), zod.z.array(require_sleep.ProfileSlotSchema).readonly()),
6395
6619
  getBrokerStats: require_sleep.method(zod.z.object({ brokerId: zod.z.string() }), require_sleep.BrokerStatsSchema),
@@ -14433,6 +14657,8 @@ function createSystemProxy(api) {
14433
14657
  list: (input) => dispatch("localNetwork", "list", "query", input),
14434
14658
  getPreferred: (input) => dispatch("localNetwork", "getPreferred", "query", input),
14435
14659
  getConnectionEndpoints: (input) => dispatch("localNetwork", "getConnectionEndpoints", "query", input),
14660
+ getNotificationEndpoint: (input) => dispatch("localNetwork", "getNotificationEndpoint", "query", input),
14661
+ setNotificationEndpoint: (input) => dispatch("localNetwork", "setNotificationEndpoint", "mutation", input),
14436
14662
  getAllowedAddresses: (input) => dispatch("localNetwork", "getAllowedAddresses", "query", input),
14437
14663
  setAllowedAddresses: (input) => dispatch("localNetwork", "setAllowedAddresses", "mutation", input),
14438
14664
  resetAllowlistToBestMatch: (input) => dispatch("localNetwork", "resetAllowlistToBestMatch", "mutation", input)
@@ -14603,7 +14829,10 @@ function createSystemProxy(api) {
14603
14829
  },
14604
14830
  recording: {
14605
14831
  getStorageUsage: (input) => dispatch("recording", "getStorageUsage", "query", input),
14606
- listOpsLog: (input) => dispatch("recording", "listOpsLog", "query", input)
14832
+ listOpsLog: (input) => dispatch("recording", "listOpsLog", "query", input),
14833
+ relocateFootage: (input) => dispatch("recording", "relocateFootage", "mutation", input),
14834
+ getRelocateStatus: (input) => dispatch("recording", "getRelocateStatus", "query", input),
14835
+ cancelRelocate: (input) => dispatch("recording", "cancelRelocate", "mutation", input)
14607
14836
  },
14608
14837
  recordingExport: {
14609
14838
  getExport: (input) => dispatch("recordingExport", "getExport", "query", input),
@@ -19645,6 +19874,36 @@ var TargetKindSchema = zod.z.object({
19645
19874
  icon: zod.z.string(),
19646
19875
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
19647
19876
  addonId: zod.z.string(),
19877
+ /**
19878
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
19879
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
19880
+ * when the addon bundles no icon for that kind — the client then falls back
19881
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
19882
+ *
19883
+ * Root-relative on purpose: it resolves against whatever origin serves a web
19884
+ * client, and a native client joins it onto its own hub base.
19885
+ *
19886
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
19887
+ * field that survived only because the runtime cap-router forwards provider
19888
+ * output verbatim — so every consumer had to re-declare it by hand to stop
19889
+ * its own Zod parse from stripping it, and the whole arrangement would have
19890
+ * broken silently the moment output validation was tightened anywhere.
19891
+ */
19892
+ iconUrl: zod.z.string().optional(),
19893
+ /**
19894
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
19895
+ *
19896
+ * The server knows this and therefore says it, because the client cannot
19897
+ * safely guess: a React-Native client renders SVG and raster through two
19898
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
19899
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
19900
+ * placeholder glyph for every vector icon while the web build looked fine.
19901
+ *
19902
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
19903
+ * not been updated — a client that cannot determine the type should prefer
19904
+ * its raster path, which is the safe default for an unknown image.
19905
+ */
19906
+ iconMediaType: zod.z.string().optional(),
19648
19907
  configSchema: ConfigSchemaPassthrough,
19649
19908
  supportsDiscovery: zod.z.boolean(),
19650
19909
  caps: TargetKindCapsSchema
@@ -20523,6 +20782,23 @@ var pipelineAnalyticsCapability = {
20523
20782
  }),
20524
20783
  /** The events ops-log rows (newest-first), optionally scoped to one camera.
20525
20784
  * Backed by a declared pipeline-analytics SQLite collection. */
20785
+ /**
20786
+ * Move event-media blobs onto `toLocationId` (entity-routing spec Phase
20787
+ * 4): per-row copy → stamp row.locationId → delete old blob. Rows already
20788
+ * at the target are skipped, so a re-run resumes. Single-flight.
20789
+ */
20790
+ relocateMedia: require_sleep.method(RelocateMediaInputSchema, zod.z.object({ jobId: zod.z.string() }), {
20791
+ kind: "mutation",
20792
+ auth: "admin"
20793
+ }),
20794
+ getMediaRelocateStatus: require_sleep.method(zod.z.object({}), zod.z.array(RelocateJobSchema).readonly(), {
20795
+ kind: "query",
20796
+ auth: "admin"
20797
+ }),
20798
+ cancelMediaRelocate: require_sleep.method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
20799
+ kind: "mutation",
20800
+ auth: "admin"
20801
+ }),
20526
20802
  listOpsLog: require_sleep.method(OpsLogQueryInputSchema, zod.z.array(OpsLogEntrySchema).readonly(), {
20527
20803
  kind: "query",
20528
20804
  auth: "admin"
@@ -24160,6 +24436,17 @@ var ConnectionEndpointSchema = zod.z.object({
24160
24436
  priority: zod.z.number()
24161
24437
  });
24162
24438
  var GetConnectionEndpointsResultSchema = zod.z.object({ endpoints: zod.z.array(ConnectionEndpointSchema).readonly() });
24439
+ /**
24440
+ * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
24441
+ * AUTO (resolved from the candidate ranking at send time); `resolved` reports
24442
+ * what AUTO currently picks, so the UI can show the effective value either way.
24443
+ */
24444
+ var NotificationEndpointSchema = zod.z.object({
24445
+ /** The operator's explicit choice, or null for AUTO. */
24446
+ baseUrl: zod.z.string().nullable(),
24447
+ /** What the ranking currently resolves to (null when nothing is reachable). */
24448
+ resolved: zod.z.string().nullable()
24449
+ });
24163
24450
  var AllowedAddressesSchema = zod.z.object({
24164
24451
  /**
24165
24452
  * Allowlist of interface addresses operators have explicitly opted
@@ -24211,6 +24498,26 @@ var localNetworkCapability = {
24211
24498
  scheme: zod.z.enum(["http", "https"]).optional()
24212
24499
  }), GetConnectionEndpointsResultSchema),
24213
24500
  /**
24501
+ * The endpoint OUTBOUND artifacts must be addressed by — notification
24502
+ * attachment links and deep links. `baseUrl: null` means AUTO: the first
24503
+ * public candidate (a configured origin, then a connected tunnel, then the
24504
+ * LAN), never loopback.
24505
+ *
24506
+ * Why an explicit marker: the automatic ranking cannot know which of
24507
+ * several reachable addresses the RECIPIENT can actually open. A link is
24508
+ * consumed on someone's phone, off the LAN, possibly hours later — and a
24509
+ * link nobody can open is worse than no link, because the notification
24510
+ * arrives advertising media that 404s.
24511
+ */
24512
+ getNotificationEndpoint: require_sleep.method(zod.z.void(), NotificationEndpointSchema),
24513
+ /**
24514
+ * Mark which endpoint notification artifacts use. `baseUrl: null` restores
24515
+ * AUTO. The value is stored verbatim (not an index into the candidate
24516
+ * list): interfaces come and go, and an index would silently re-point at a
24517
+ * different address after a reboot.
24518
+ */
24519
+ setNotificationEndpoint: require_sleep.method(zod.z.object({ baseUrl: zod.z.string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }),
24520
+ /**
24214
24521
  * Read the operator's allowlist. Empty = "auto" (no filter). Used
24215
24522
  * by the admin UI's address selector to seed its checkbox state.
24216
24523
  */
@@ -25322,7 +25629,12 @@ var RecordingDeviceUsageSchema = zod.z.object({
25322
25629
  var RecordingLocationUsageSchema = zod.z.object({
25323
25630
  /** StorageLocation id; null for the legacy/degraded single-root fallback. */
25324
25631
  locationId: zod.z.string().nullable(),
25325
- /** Bytes of recordings stored on this location. */
25632
+ /** Every location id sharing this row's PHYSICAL volume (aliases). One row
25633
+ * is emitted per physical disk (2026-07-29): two locations on one root
25634
+ * previously rendered as two identical "disks" with a nonsensical used
25635
+ * split — hydrate attribution across aliases is arbitrary by nature. */
25636
+ locationIds: zod.z.array(zod.z.string()).optional(),
25637
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
25326
25638
  usedBytes: zod.z.number(),
25327
25639
  /** Free bytes on the location's volume; null when capacity is unknown (remote). */
25328
25640
  availableBytes: zod.z.number().nullable(),
@@ -25478,6 +25790,77 @@ var recordingCapability = {
25478
25790
  listOpsLog: require_sleep.method(OpsLogQueryInputSchema, zod.z.array(OpsLogEntrySchema).readonly(), {
25479
25791
  kind: "query",
25480
25792
  auth: "admin"
25793
+ }),
25794
+ /**
25795
+ * Move footage (segments and/or strips) from one recordings location to
25796
+ * another — the drain workflow's mover (entity-routing spec Phase 4).
25797
+ * Throttled copy → size-verify → delete source → index refresh; resumable
25798
+ * by construction (copy-if-absent). Single-flight: one job at a time.
25799
+ */
25800
+ /**
25801
+ * Render a short GIF from recorded footage around `aroundMs` (low profile,
25802
+ * palette-free ffmpeg gif). Synchronous — the window is a few seconds of
25803
+ * `low`, rendering in ~1-2s. Built for notification attachments: the
25804
+ * caller gets bytes, nothing persists. Fails when no footage covers the
25805
+ * window (camera not recording → caller skips the attachment).
25806
+ */
25807
+ renderGif: require_sleep.method(zod.z.object({
25808
+ deviceId: zod.z.number(),
25809
+ aroundMs: zod.z.number(),
25810
+ preRollSec: zod.z.number().min(0).max(30).default(2),
25811
+ postRollSec: zod.z.number().min(0).max(30).default(5),
25812
+ maxWidth: zod.z.number().int().min(120).max(1280).default(480),
25813
+ fps: zod.z.number().int().min(1).max(15).default(5)
25814
+ }), zod.z.object({
25815
+ gifBase64: zod.z.string(),
25816
+ fromMs: zod.z.number(),
25817
+ toMs: zod.z.number()
25818
+ }), {
25819
+ kind: "mutation",
25820
+ auth: "admin"
25821
+ }),
25822
+ /**
25823
+ * Render a short MP4 clip from recorded footage around `aroundMs` — the
25824
+ * "send a clip, not a gif" notification attachment. Same window, source
25825
+ * and fail-closed contract as {@link renderGif}: no footage covering the
25826
+ * window ⇒ it throws and the caller ships no attachment. Re-encoded (never
25827
+ * stream-copied) so a concat across segments with different encoder
25828
+ * parameters still plays. Silent by contract.
25829
+ *
25830
+ * The returned window is SEGMENT-GRANULAR: the source playlist is built
25831
+ * from whole segments, so the clip starts at or before `fromMs` and ends
25832
+ * at or after `toMs` — overshoot bounded by one segment per side (an 8 s
25833
+ * ask over 4 s segments measured 12 s). `fromMs`/`toMs` report the window
25834
+ * ASKED for, not the encoded duration.
25835
+ */
25836
+ renderClip: require_sleep.method(zod.z.object({
25837
+ deviceId: zod.z.number(),
25838
+ aroundMs: zod.z.number(),
25839
+ preRollSec: zod.z.number().min(0).max(30).default(3),
25840
+ postRollSec: zod.z.number().min(0).max(30).default(7),
25841
+ maxWidth: zod.z.number().int().min(160).max(1920).default(640)
25842
+ }), zod.z.object({
25843
+ clipBase64: zod.z.string(),
25844
+ mime: zod.z.string(),
25845
+ fromMs: zod.z.number(),
25846
+ toMs: zod.z.number(),
25847
+ bytes: zod.z.number().int()
25848
+ }), {
25849
+ kind: "mutation",
25850
+ auth: "admin"
25851
+ }),
25852
+ relocateFootage: require_sleep.method(RelocateFootageInputSchema, zod.z.object({ jobId: zod.z.string() }), {
25853
+ kind: "mutation",
25854
+ auth: "admin"
25855
+ }),
25856
+ /** Every known relocate job (in-RAM, newest-first). */
25857
+ getRelocateStatus: require_sleep.method(zod.z.object({}), zod.z.array(RelocateJobSchema).readonly(), {
25858
+ kind: "query",
25859
+ auth: "admin"
25860
+ }),
25861
+ cancelRelocate: require_sleep.method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
25862
+ kind: "mutation",
25863
+ auth: "admin"
25481
25864
  })
25482
25865
  }
25483
25866
  };
@@ -29310,6 +29693,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
29310
29693
  addonId: null,
29311
29694
  access: "view"
29312
29695
  },
29696
+ "localNetwork.getNotificationEndpoint": {
29697
+ capName: "local-network",
29698
+ capScope: "system",
29699
+ addonId: null,
29700
+ access: "view"
29701
+ },
29313
29702
  "localNetwork.getPreferred": {
29314
29703
  capName: "local-network",
29315
29704
  capScope: "system",
@@ -29334,6 +29723,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
29334
29723
  addonId: null,
29335
29724
  access: "create"
29336
29725
  },
29726
+ "localNetwork.setNotificationEndpoint": {
29727
+ capName: "local-network",
29728
+ capScope: "system",
29729
+ addonId: null,
29730
+ access: "create"
29731
+ },
29337
29732
  "lockControl.lock": {
29338
29733
  capName: "lock-control",
29339
29734
  capScope: "device",
@@ -29976,6 +30371,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
29976
30371
  addonId: null,
29977
30372
  access: "create"
29978
30373
  },
30374
+ "pipelineAnalytics.cancelMediaRelocate": {
30375
+ capName: "pipeline-analytics",
30376
+ capScope: "device",
30377
+ addonId: null,
30378
+ access: "create"
30379
+ },
29979
30380
  "pipelineAnalytics.clearTracks": {
29980
30381
  capName: "pipeline-analytics",
29981
30382
  capScope: "device",
@@ -30030,6 +30431,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
30030
30431
  addonId: null,
30031
30432
  access: "view"
30032
30433
  },
30434
+ "pipelineAnalytics.getMediaRelocateStatus": {
30435
+ capName: "pipeline-analytics",
30436
+ capScope: "device",
30437
+ addonId: null,
30438
+ access: "view"
30439
+ },
30033
30440
  "pipelineAnalytics.getMotionEvents": {
30034
30441
  capName: "pipeline-analytics",
30035
30442
  capScope: "device",
@@ -30102,6 +30509,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
30102
30509
  addonId: null,
30103
30510
  access: "create"
30104
30511
  },
30512
+ "pipelineAnalytics.relocateMedia": {
30513
+ capName: "pipeline-analytics",
30514
+ capScope: "device",
30515
+ addonId: null,
30516
+ access: "create"
30517
+ },
30105
30518
  "pipelineAnalytics.searchObjectEvents": {
30106
30519
  capName: "pipeline-analytics",
30107
30520
  capScope: "device",
@@ -30864,6 +31277,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
30864
31277
  addonId: null,
30865
31278
  access: "create"
30866
31279
  },
31280
+ "recording.cancelRelocate": {
31281
+ capName: "recording",
31282
+ capScope: "system",
31283
+ addonId: null,
31284
+ access: "create"
31285
+ },
30867
31286
  "recording.deleteFootprint": {
30868
31287
  capName: "recording",
30869
31288
  capScope: "system",
@@ -30894,6 +31313,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
30894
31313
  addonId: null,
30895
31314
  access: "view"
30896
31315
  },
31316
+ "recording.getRelocateStatus": {
31317
+ capName: "recording",
31318
+ capScope: "system",
31319
+ addonId: null,
31320
+ access: "view"
31321
+ },
30897
31322
  "recording.getStorageUsage": {
30898
31323
  capName: "recording",
30899
31324
  capScope: "system",
@@ -30924,6 +31349,24 @@ var METHOD_ACCESS_MAP = Object.freeze({
30924
31349
  addonId: null,
30925
31350
  access: "view"
30926
31351
  },
31352
+ "recording.relocateFootage": {
31353
+ capName: "recording",
31354
+ capScope: "system",
31355
+ addonId: null,
31356
+ access: "create"
31357
+ },
31358
+ "recording.renderClip": {
31359
+ capName: "recording",
31360
+ capScope: "system",
31361
+ addonId: null,
31362
+ access: "create"
31363
+ },
31364
+ "recording.renderGif": {
31365
+ capName: "recording",
31366
+ capScope: "system",
31367
+ addonId: null,
31368
+ access: "create"
31369
+ },
30927
31370
  "recording.rescanStorage": {
30928
31371
  capName: "recording",
30929
31372
  capScope: "system",
@@ -31518,6 +31961,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
31518
31961
  addonId: null,
31519
31962
  access: "create"
31520
31963
  },
31964
+ "streamBroker.renderPreBufferClip": {
31965
+ capName: "stream-broker",
31966
+ capScope: "system",
31967
+ addonId: null,
31968
+ access: "create"
31969
+ },
31521
31970
  "streamBroker.restartProfile": {
31522
31971
  capName: "stream-broker",
31523
31972
  capScope: "system",
@@ -33325,6 +33774,7 @@ exports.MotionZonePatchSchema = MotionZonePatchSchema;
33325
33774
  exports.MotionZoneRegionSchema = MotionZoneRegionSchema;
33326
33775
  exports.MotionZoneStatusSchema = MotionZoneStatusSchema;
33327
33776
  exports.MqttBrokerStatusSchema = StatusSchema;
33777
+ exports.NC_BASE_CONDITION_KEYS = NC_BASE_CONDITION_KEYS;
33328
33778
  exports.NC_CONDITION_CATALOG = NC_CONDITION_CATALOG;
33329
33779
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
33330
33780
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
@@ -33345,6 +33795,7 @@ exports.NcHistoryFilterSchema = NcHistoryFilterSchema;
33345
33795
  exports.NcHistoryRecordKindSchema = NcHistoryRecordKindSchema;
33346
33796
  exports.NcHistoryStatusSchema = NcHistoryStatusSchema;
33347
33797
  exports.NcHistorySubjectSchema = NcHistorySubjectSchema;
33798
+ exports.NcMediaFrameSchema = NcMediaFrameSchema;
33348
33799
  exports.NcMediaPolicySchema = NcMediaPolicySchema;
33349
33800
  exports.NcOccupancyConditionSchema = NcOccupancyConditionSchema;
33350
33801
  exports.NcPlateMatcherSchema = NcPlateMatcherSchema;
@@ -33451,17 +33902,18 @@ exports.RecordingDaysSchema = RecordingDaysSchema;
33451
33902
  exports.RecordingDeviceUsageSchema = RecordingDeviceUsageSchema;
33452
33903
  exports.RecordingLocationUsageSchema = RecordingLocationUsageSchema;
33453
33904
  exports.RecordingManifestSchema = RecordingManifestSchema;
33454
- exports.RecordingModeSchema = RecordingModeSchema;
33455
33905
  exports.RecordingRangeSchema = RecordingRangeSchema;
33456
33906
  exports.RecordingRetentionSchema = RecordingRetentionSchema;
33457
- exports.RecordingRuleSchema = RecordingRuleSchema;
33458
- exports.RecordingScheduleSchema = RecordingScheduleSchema;
33459
33907
  exports.RecordingStatusSchema = RecordingStatusSchema;
33460
33908
  exports.RecordingStorageModeSchema = RecordingStorageModeSchema;
33461
33909
  exports.RecordingStorageUsageSchema = RecordingStorageUsageSchema;
33462
33910
  exports.RecordingTriggersSchema = RecordingTriggersSchema;
33463
33911
  exports.RecordingWeekdaySchema = RecordingWeekdaySchema;
33464
33912
  exports.RedirectLoginMethodSchema = RedirectLoginMethodSchema;
33913
+ exports.RelocateFootageInputSchema = RelocateFootageInputSchema;
33914
+ exports.RelocateJobSchema = RelocateJobSchema;
33915
+ exports.RelocateJobStateSchema = RelocateJobStateSchema;
33916
+ exports.RelocateMediaInputSchema = RelocateMediaInputSchema;
33465
33917
  exports.RenderedAsSchema = RenderedAsSchema;
33466
33918
  exports.ReportMotionInputSchema = ReportMotionInputSchema;
33467
33919
  exports.RingBuffer = RingBuffer;
@@ -33693,6 +34145,7 @@ exports.dayNightCapability = dayNightCapability;
33693
34145
  exports.decoderCapability = decoderCapability;
33694
34146
  exports.defaultDeviceFor = defaultDeviceFor;
33695
34147
  exports.defineCustomActions = defineCustomActions;
34148
+ exports.deriveRecordingMode = deriveRecordingMode;
33696
34149
  exports.describeModelVariant = describeModelVariant;
33697
34150
  exports.detectionPipelineCapability = detectionPipelineCapability;
33698
34151
  exports.deviceAdoptionCapability = deviceAdoptionCapability;
@@ -33750,6 +34203,7 @@ exports.integrationsCapability = integrationsCapability;
33750
34203
  exports.intercomCapability = intercomCapability;
33751
34204
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
33752
34205
  exports.isArrayOutputSchema = isArrayOutputSchema;
34206
+ exports.isBaseConditionKey = isBaseConditionKey;
33753
34207
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
33754
34208
  exports.isDeployableToAgent = isDeployableToAgent;
33755
34209
  exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
@@ -33759,6 +34213,7 @@ exports.isObjectInput = isObjectInput;
33759
34213
  exports.isVoidInput = isVoidInput;
33760
34214
  exports.jobKindSchema = jobKindSchema;
33761
34215
  exports.kebabToCamel = kebabToCamel;
34216
+ exports.knownValues = knownValues;
33762
34217
  exports.lawnMowerControlCapability = lawnMowerControlCapability;
33763
34218
  exports.lifecycleJobSchema = lifecycleJobSchema;
33764
34219
  exports.lifecycleJobScopeSchema = lifecycleJobScopeSchema;
@@ -33784,7 +34239,6 @@ exports.mergeSourceInfo = mergeSourceInfo;
33784
34239
  exports.meshNetworkCapability = meshNetworkCapability;
33785
34240
  exports.method = require_sleep.method;
33786
34241
  exports.metricsProviderCapability = metricsProviderCapability;
33787
- exports.migrateConfigToBands = migrateConfigToBands;
33788
34242
  exports.modelConvertCapability = modelConvertCapability;
33789
34243
  exports.modelDistributorCapability = modelDistributorCapability;
33790
34244
  exports.modelFormatForRuntime = modelFormatForRuntime;
@@ -33817,6 +34271,7 @@ exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
33817
34271
  exports.petFeederCapability = petFeederCapability;
33818
34272
  exports.pickAccessoryControl = pickAccessoryControl;
33819
34273
  exports.pickPreferredRtspEntry = pickPreferredRtspEntry;
34274
+ exports.pickerForCondition = pickerForCondition;
33820
34275
  exports.pipelineAnalyticsCapability = pipelineAnalyticsCapability;
33821
34276
  exports.pipelineExecutorCapability = pipelineExecutorCapability;
33822
34277
  exports.pipelineOrchestratorCapability = pipelineOrchestratorCapability;