@camstack/addon-export-alexa 1.2.84 → 1.2.86

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.
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  //#endregion
27
27
  let node_crypto = require("node:crypto");
28
28
  node_crypto = __toESM(node_crypto);
29
- //#region ../types/dist/event-category-zAv7pMUz.mjs
29
+ //#region ../types/dist/event-category-CnLqLOKs.mjs
30
30
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
31
31
  EventCategory["SystemBoot"] = "system.boot";
32
32
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -221,6 +221,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
221
221
  EventCategory["ProcessCrashed"] = "process.crashed";
222
222
  EventCategory["ProcessRestartScheduled"] = "process.restart_scheduled";
223
223
  EventCategory["ProcessRestarted"] = "process.restarted";
224
+ /**
225
+ * The SET of storage locations changed — one was created, edited, enabled,
226
+ * disabled or deleted through `storage.upsertLocation` / `deleteLocation`.
227
+ *
228
+ * Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
229
+ * it must also converge on its own periodic path, because a dropped event
230
+ * must not leave a node writing to yesterday's disk set forever. It exists
231
+ * because there was NO signal at all — an operator who added a second
232
+ * recordings disk in the admin UI got nothing, and the recorder kept its
233
+ * resolved locations until something else happened to re-resolve them
234
+ * (D387). Payload `StorageLocationsChangedPayload`.
235
+ */
236
+ EventCategory["StorageLocationsChanged"] = "storage.locations-changed";
224
237
  EventCategory["RecordingStarted"] = "recording.started";
225
238
  EventCategory["RecordingStopped"] = "recording.stopped";
226
239
  EventCategory["RecordingError"] = "recording.error";
@@ -7783,111 +7796,6 @@ var CameraSwitchGroupSchema = object({
7783
7796
  fetchedAt: number()
7784
7797
  });
7785
7798
  /**
7786
- * Per-component log CHANNELS — the gate a hot path consults, and the registry
7787
- * an addon declares its channels in.
7788
- *
7789
- * ## Two axes, deliberately separated
7790
- *
7791
- * - **DECLARATION** — which channels exist. Only the addon knows:
7792
- * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
7793
- * baichuan/handshake. A hand-wired central list rots at the first addition,
7794
- * and rots silently. So a channel is declared where it is consulted, and the
7795
- * `log-channels` capability enumerates the declarations.
7796
- * - **VALUE** — at which level, for which scope, until when. That stays ONE
7797
- * thing: the logging settings document on the `system` cap. Two authorities
7798
- * over the values is the exact defect
7799
- * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
7800
- * remove; re-introducing it from the cure side would be grotesque.
7801
- *
7802
- * Nothing in this file reads a clock, an env var or a store. The registry is
7803
- * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
7804
- * the hot path with a value somebody actually read, and by
7805
- * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
7806
- * never reaches here, so it can neither disarm an armed channel nor arm a
7807
- * disarmed one (D49).
7808
- *
7809
- * ## The canonical call shape
7810
- *
7811
- * ```ts
7812
- * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
7813
- * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
7814
- * }
7815
- * ```
7816
- *
7817
- * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
7818
- * read. Disarmed, a call site costs one load and one branch, and the `extras`
7819
- * object literal is never constructed because it lives inside the branch. It
7820
- * is the same shape already proven in production at `stream-broker.ts:1650`,
7821
- * and the same discipline `LoggingGate.allowsDestination` uses for the
7822
- * destination floor (measured at 1.93 ns/call when off).
7823
- *
7824
- * ## Why a channel emits at `info`
7825
- *
7826
- * `loki-logging.addon.ts` pins the destination default at `info` and
7827
- * `loki-destination.ts` drops everything below it, so a line emitted at
7828
- * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
7829
- * minutes. A diagnostic that cannot be read an hour later is worse than no
7830
- * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
7831
- * emits at the channel's declared level, whose schema floor is `info`.
7832
- */
7833
- /**
7834
- * The level a channel writes at once armed.
7835
- *
7836
- * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
7837
- * not leave the process for Loki, and the whole point of arming a channel is
7838
- * to read it later.
7839
- */
7840
- var LogChannelLevelSchema = _enum([
7841
- "info",
7842
- "warn",
7843
- "error"
7844
- ]);
7845
- /**
7846
- * What an addon declares about one channel. No value, no state — a
7847
- * declaration is inert.
7848
- */
7849
- var LogChannelDescriptorSchema = object({
7850
- /**
7851
- * Dotted `area.thing`, unique across the workspace. `area` is conventionally
7852
- * the addon's short name so an operator reading a channel list can tell who
7853
- * owns it without a second lookup.
7854
- */
7855
- name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
7856
- /** One sentence: what the operator will SEE after arming it. */
7857
- description: string().min(1),
7858
- /** The level its lines are emitted at. Never below `info`. */
7859
- defaultLevel: LogChannelLevelSchema,
7860
- /**
7861
- * Whether this channel can be narrowed to a camera.
7862
- *
7863
- * `true` is a PROMISE with two halves, and both must hold: the gate is
7864
- * consulted with the numeric device id, AND every line the channel admits
7865
- * carries `tags: { deviceId }` with that same numeric id. The second half is
7866
- * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
7867
- * keeps `deviceId` out of the stream labels for cardinality, so the tag in
7868
- * the body is the only way to filter.
7869
- *
7870
- * A channel whose lines carry the device only in `meta` (or not at all) is
7871
- * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
7872
- * the operator narrows to one camera, sees nothing, and concludes the code
7873
- * path was never taken.
7874
- */
7875
- perDevice: boolean()
7876
- });
7877
- /**
7878
- * An armed window over one channel, as the document hands it to a mirror.
7879
- *
7880
- * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
7881
- * expires by itself, which is the one failure a boolean cannot avoid.
7882
- */
7883
- var LogChannelWindowSchema = object({
7884
- channel: string().min(1),
7885
- /** Epoch ms the window closes at. */
7886
- armedUntilMs: number(),
7887
- /** `null` = every camera. A non-empty list narrows to those numeric ids. */
7888
- deviceIds: array(number().int()).readonly().nullable()
7889
- });
7890
- /**
7891
7799
  * Ops-log — the durable, append-only operations audit shared by the
7892
7800
  * recordings and events management surfaces.
7893
7801
  *
@@ -8809,6 +8717,21 @@ var StorageCleanupJobSchema = object({
8809
8717
  });
8810
8718
  var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
8811
8719
  /**
8720
+ * The one typed state of a storage location. Authoritative Zod schema — the TS
8721
+ * alias below is `z.infer<>` of it, never a second spelling.
8722
+ */
8723
+ var StorageLocationModeSchema = _enum([
8724
+ "active",
8725
+ "readonly",
8726
+ "drain",
8727
+ "disabled"
8728
+ ]);
8729
+ _enum([
8730
+ "normal",
8731
+ "never",
8732
+ "drain"
8733
+ ]);
8734
+ /**
8812
8735
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8813
8736
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8814
8737
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8833,8 +8756,11 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8833
8756
  * `STORAGE_LOCATION_CARDINALITY` map has been removed.
8834
8757
  *
8835
8758
  * `id` is a stable namespaced string of the form `<type>:<slug>`.
8836
- * The default location for a type uses `id === <type>:default` by
8837
- * convention (the bare type ref like `'backups'` resolves to it).
8759
+ * The seed names its first instance `<type>:default` — a NAME, not a flag.
8760
+ * There is no default location any more (D383): `enabled` is the whole write
8761
+ * model, and a bare type ref resolves to the sole location of the type, or —
8762
+ * transitionally, only while legacy NULL-stamped rows exist — to the row whose
8763
+ * slug is `default`.
8838
8764
  *
8839
8765
  * `isSystem` is a legacy persisted flag. Seed still creates the initial
8840
8766
  * `<type>:default` locations; the flag is no longer a lock, a badge, or a
@@ -8855,23 +8781,37 @@ var StorageLocationSchema = object({
8855
8781
  * flag at upsert time, not here (the schema is provider-agnostic).
8856
8782
  */
8857
8783
  nodeId: string().optional(),
8858
- isDefault: boolean().default(false),
8859
8784
  isSystem: boolean().default(false),
8860
8785
  /**
8861
- * Operator opt-in: whether consumers that BALANCE across several locations
8862
- * of a type may write here. Recordings reads it today; event media and
8863
- * backups are the next consumers, which is why the flag lives on the
8864
- * location rather than in any one addon's store — nothing has to be
8865
- * extended to add the next consumer.
8786
+ * THE write switch, and the only one (D383). `enabled: true` means every
8787
+ * consumer that chooses a write target for this type may write here, and all
8788
+ * enabled locations of a type are used TOGETHER; `false` means read-only —
8789
+ * still read, still played back, still age-swept, still drained, never
8790
+ * written.
8866
8791
  *
8867
- * OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
8868
- * flag existed reads back with no flag and keeps working exactly as before;
8869
- * that is the whole compat story, and it is why no migration ships with it.
8870
- * A newly CREATED sibling is stamped `false` by the orchestrator (creating a
8871
- * disk must not silently start writing to it); the default of a type is
8872
- * always stamped `true`.
8792
+ * OPTIONAL only for the wire: an upsert that omits it means "leave what is
8793
+ * stored" on an update and "born inert unless it is the first location of its
8794
+ * type" on a create. On a PERSISTED row absence is legacy and it means
8795
+ * enabled — {@link isLocationEnabled} is the one place that says so, and the
8796
+ * orchestrator stamps every flagless row `true` once at hydrate so absence
8797
+ * stops existing rather than being re-derived on every read.
8873
8798
  */
8874
8799
  enabled: boolean().optional(),
8800
+ /**
8801
+ * THE state of this location (D385), and the only authority on what may be
8802
+ * written, read or evicted here. Interpreted in exactly one place —
8803
+ * `storage-location-mode.ts` — which also folds the legacy
8804
+ * `enabled` / `config.readOnly` pair into a mode so an old row is never
8805
+ * ambiguous.
8806
+ *
8807
+ * OPTIONAL only for the wire and for rows written before D385: absence is
8808
+ * resolved by `resolveLocationMode`, and the orchestrator stamps every
8809
+ * unstamped row ONCE at hydrate so absence stops existing rather than being
8810
+ * re-derived on every read. `enabled` survives one release as a DERIVED
8811
+ * mirror (`mode === 'active'`); `withLocationMode` is the only writer of
8812
+ * either, so the two cannot disagree.
8813
+ */
8814
+ mode: StorageLocationModeSchema.optional(),
8875
8815
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
8876
8816
  * for node-local locations it can reach) — never persisted, absent when the
8877
8817
  * volume is remote/unreachable. The single capacity truth every UI reads. */
@@ -8879,13 +8819,50 @@ var StorageLocationSchema = object({
8879
8819
  totalBytes: number(),
8880
8820
  availableBytes: number()
8881
8821
  }).nullable().optional(),
8822
+ /**
8823
+ * How much of that volume CamStack ITSELF holds on this location (D388) —
8824
+ * COMPUTED at read time from the `storage-occupancy` providers' own figures,
8825
+ * never persisted, never a filesystem walk.
8826
+ *
8827
+ * **ABSENT MEANS UNKNOWN, never zero.** No provider has reported for this
8828
+ * location yet — nobody stores here, the owning addon is down, or the first
8829
+ * refresh has not completed. A UI must omit the segment rather than draw it
8830
+ * at zero, which would claim we occupy nothing (D315). It is an OBJECT and
8831
+ * not a bare number precisely so that a `?? 0` on the consuming side has to
8832
+ * be spelled out loud instead of appearing by accident.
8833
+ *
8834
+ * `measuredAtMs` is the OLDEST contributing measurement, so it is honest
8835
+ * about the whole figure rather than about its freshest part.
8836
+ */
8837
+ owned: object({
8838
+ bytes: number().int().nonnegative(),
8839
+ measuredAtMs: number().int().nonnegative()
8840
+ }).optional(),
8882
8841
  createdAt: number(),
8883
8842
  updatedAt: number()
8884
8843
  });
8844
+ object({ isDefault: boolean().optional() });
8845
+ /**
8846
+ * How far a `drain` has got (D386) — the read a UI renders, and nothing more.
8847
+ *
8848
+ * `estimatedEmptyAtMs` is derived from the growth the ratchet has actually
8849
+ * OBSERVED and is `null` when it has observed none. Never a fabricated date: a
8850
+ * drain with no observed growth has no honest ETA, and inventing one is how an
8851
+ * operator learns not to believe the screen.
8852
+ */
8853
+ var StorageDrainProgressSchema = object({
8854
+ locationId: string(),
8855
+ startedAtMs: number(),
8856
+ startBytes: number(),
8857
+ bytesRemaining: number(),
8858
+ drained: boolean(),
8859
+ estimatedEmptyAtMs: number().nullable()
8860
+ });
8885
8861
  /**
8886
8862
  * Reference accepted by consumer-facing `api.storage.*` calls.
8887
8863
  * Either:
8888
- * - a `StorageLocationType` (e.g. `'backups'`) → orchestrator resolves to the default of that type
8864
+ * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
8865
+ * (transitionally, the `<type>:default`-slugged row when several exist)
8889
8866
  * - a fully-qualified id (e.g. `'backups:nas-01'`) → addresses a specific instance
8890
8867
  *
8891
8868
  * The orchestrator's `resolveRef(ref)` handles both cases.
@@ -9061,6 +9038,111 @@ var DecoderSessionConfigSchema = object({
9061
9038
  */
9062
9039
  debug: boolean().optional()
9063
9040
  });
9041
+ /**
9042
+ * Per-component log CHANNELS — the gate a hot path consults, and the registry
9043
+ * an addon declares its channels in.
9044
+ *
9045
+ * ## Two axes, deliberately separated
9046
+ *
9047
+ * - **DECLARATION** — which channels exist. Only the addon knows:
9048
+ * `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
9049
+ * baichuan/handshake. A hand-wired central list rots at the first addition,
9050
+ * and rots silently. So a channel is declared where it is consulted, and the
9051
+ * `log-channels` capability enumerates the declarations.
9052
+ * - **VALUE** — at which level, for which scope, until when. That stays ONE
9053
+ * thing: the logging settings document on the `system` cap. Two authorities
9054
+ * over the values is the exact defect
9055
+ * `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
9056
+ * remove; re-introducing it from the cure side would be grotesque.
9057
+ *
9058
+ * Nothing in this file reads a clock, an env var or a store. The registry is
9059
+ * a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
9060
+ * the hot path with a value somebody actually read, and by
9061
+ * {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
9062
+ * never reaches here, so it can neither disarm an armed channel nor arm a
9063
+ * disarmed one (D49).
9064
+ *
9065
+ * ## The canonical call shape
9066
+ *
9067
+ * ```ts
9068
+ * if (CH_RTP.on && CH_RTP.wants(deviceId)) {
9069
+ * CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
9070
+ * }
9071
+ * ```
9072
+ *
9073
+ * `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
9074
+ * read. Disarmed, a call site costs one load and one branch, and the `extras`
9075
+ * object literal is never constructed because it lives inside the branch. It
9076
+ * is the same shape already proven in production at `stream-broker.ts:1650`,
9077
+ * and the same discipline `LoggingGate.allowsDestination` uses for the
9078
+ * destination floor (measured at 1.93 ns/call when off).
9079
+ *
9080
+ * ## Why a channel emits at `info`
9081
+ *
9082
+ * `loki-logging.addon.ts` pins the destination default at `info` and
9083
+ * `loki-destination.ts` drops everything below it, so a line emitted at
9084
+ * `debug` never reaches Loki and the hub's in-memory ring only holds ~35
9085
+ * minutes. A diagnostic that cannot be read an hour later is worse than no
9086
+ * diagnostic, because it looks done. {@link LogChannelGate.log} therefore
9087
+ * emits at the channel's declared level, whose schema floor is `info`.
9088
+ */
9089
+ /**
9090
+ * The level a channel writes at once armed.
9091
+ *
9092
+ * `debug` is absent ON PURPOSE and not by omission: below `info` the line does
9093
+ * not leave the process for Loki, and the whole point of arming a channel is
9094
+ * to read it later.
9095
+ */
9096
+ var LogChannelLevelSchema = _enum([
9097
+ "info",
9098
+ "warn",
9099
+ "error"
9100
+ ]);
9101
+ /**
9102
+ * What an addon declares about one channel. No value, no state — a
9103
+ * declaration is inert.
9104
+ */
9105
+ var LogChannelDescriptorSchema = object({
9106
+ /**
9107
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
9108
+ * the addon's short name so an operator reading a channel list can tell who
9109
+ * owns it without a second lookup.
9110
+ */
9111
+ name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
9112
+ /** One sentence: what the operator will SEE after arming it. */
9113
+ description: string().min(1),
9114
+ /** The level its lines are emitted at. Never below `info`. */
9115
+ defaultLevel: LogChannelLevelSchema,
9116
+ /**
9117
+ * Whether this channel can be narrowed to a camera.
9118
+ *
9119
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
9120
+ * consulted with the numeric device id, AND every line the channel admits
9121
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
9122
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
9123
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
9124
+ * the body is the only way to filter.
9125
+ *
9126
+ * A channel whose lines carry the device only in `meta` (or not at all) is
9127
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
9128
+ * the operator narrows to one camera, sees nothing, and concludes the code
9129
+ * path was never taken.
9130
+ */
9131
+ perDevice: boolean()
9132
+ });
9133
+ /**
9134
+ * An armed window over one channel, as the document hands it to a mirror.
9135
+ *
9136
+ * A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
9137
+ * expires by itself, which is the one failure a boolean cannot avoid.
9138
+ */
9139
+ var LogChannelWindowSchema = object({
9140
+ channel: string().min(1),
9141
+ /** Epoch ms the window closes at. */
9142
+ armedUntilMs: number(),
9143
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
9144
+ deviceIds: array(number().int()).readonly().nullable()
9145
+ });
9064
9146
  function pickPreferredRtspEntry(entries, pref, deviceId, options = {}) {
9065
9147
  if (entries.length === 0) return null;
9066
9148
  const prefix = `${deviceId}/`;
@@ -21868,7 +21950,7 @@ method(object({
21868
21950
  downloadId: string(),
21869
21951
  offset: number(),
21870
21952
  length: number()
21871
- }), _instanceof(Uint8Array)), method(object({ downloadId: string() }), _void(), { kind: "mutation" }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(object({ type: StorageLocationTypeSchema }), StorageLocationSchema.nullable()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
21953
+ }), _instanceof(Uint8Array)), method(object({ downloadId: string() }), _void(), { kind: "mutation" }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
21872
21954
  createdAt: true,
21873
21955
  updatedAt: true
21874
21956
  }), StorageLocationSchema, {
@@ -21880,7 +21962,7 @@ method(object({
21880
21962
  }), _void(), {
21881
21963
  kind: "mutation",
21882
21964
  auth: "admin"
21883
- }), method(object({ id: string() }), object({
21965
+ }), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
21884
21966
  ok: boolean(),
21885
21967
  error: string().optional()
21886
21968
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
@@ -21950,6 +22032,51 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
21950
22032
  kind: "mutation",
21951
22033
  auth: "admin"
21952
22034
  }), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
22035
+ /**
22036
+ * `storage-occupancy` — how many bytes an addon actually HOLDS on a storage
22037
+ * location (D388).
22038
+ *
22039
+ * ## Why this is not `storage-evictable`
22040
+ *
22041
+ * `storage-evictable.getEvictableUsage` looks like the same question and is
22042
+ * not, in two ways that both matter and both bite hardest on the locations an
22043
+ * operator most wants a figure for:
22044
+ *
22045
+ * - it reports the whole eviction DOMAIN, not the location. `recordings:default`
22046
+ * and `recordingsLow:default` deliberately share one root and evict as one
22047
+ * oldest-first pool, so both answer with the SAME combined total. As an
22048
+ * occupancy figure that double-counts the disk.
22049
+ * - it reports ZERO for a location whose eviction policy is `never` (D385) —
22050
+ * a `readonly` or `disabled` disk. Those are exactly the disks an operator
22051
+ * is retiring and staring at.
22052
+ *
22053
+ * So this is its own contract with its own quantity, and the quantity is
22054
+ * OCCUPIED: every byte the addon holds on that location, whether or not it
22055
+ * would ever be willing to delete it. A provider that can only answer
22056
+ * "evictable" must not register here — a number that silently means different
22057
+ * things per class is worse than no number.
22058
+ *
22059
+ * ## Absence is an answer
22060
+ *
22061
+ * A location nobody reports for is UNKNOWN, never zero (D315). The orchestrator
22062
+ * stamps `StorageLocation.owned` only for locations it has a report for, and
22063
+ * the field is an OBJECT rather than a bare number so that a `?? 0` on the
22064
+ * consuming side has to be written out loud instead of appearing by accident.
22065
+ *
22066
+ * `internal: true` — consumed by the orchestrator's `listLocations` stamp, never
22067
+ * a public client surface. Clients read the stamped `StorageLocation.owned`.
22068
+ */
22069
+ /** One provider's occupancy answer for one location. */
22070
+ var StorageOccupancyReportSchema = object({
22071
+ locationId: string(),
22072
+ /** Bytes this provider holds on THAT location — not its eviction domain, and
22073
+ * not net of what it is willing to delete. */
22074
+ ownedBytes: number().int().nonnegative(),
22075
+ /** When the provider last actually measured this. The orchestrator carries it
22076
+ * through so a UI can say how old the figure is instead of implying "now". */
22077
+ measuredAtMs: number().int().nonnegative()
22078
+ });
22079
+ method(object({ locationIds: array(string()).readonly() }), array(StorageOccupancyReportSchema).readonly(), { auth: "admin" });
21953
22080
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
21954
22081
  providerId: string().min(1),
21955
22082
  displayName: string().min(1),
@@ -23585,39 +23712,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23585
23712
  deviceId: number(),
23586
23713
  status: BatteryStatusSchema
23587
23714
  });
23588
- /**
23589
- * Network-link snapshot. Same shape for every provider (a Reolink wifi
23590
- * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23591
- * plug): one slice under `device.runtimeState['network-link']`, one badge,
23592
- * one Home Assistant projection.
23593
- */
23594
- var NetworkLinkStatusSchema = object({
23595
- /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23596
- type: _enum([
23597
- "wifi",
23598
- "ethernet",
23599
- "cellular",
23600
- "unknown"
23601
- ]),
23602
- /**
23603
- * Link quality, 0..100 inclusive, normalised by the provider from whatever
23604
- * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23605
- * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23606
- * one whose reading has not landed must not be drawn at 0 %. Consumers
23607
- * SKIP a null rather than coerce it.
23608
- */
23609
- signalPercent: number().min(0).max(100).nullable(),
23610
- /** Raw received signal strength in dBm, when the firmware reports one. */
23611
- rssiDbm: number().optional(),
23612
- /** Network name of a wireless link, when the firmware reports it. */
23613
- ssid: string().optional(),
23614
- /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23615
- lastUpdated: number()
23616
- });
23617
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23618
- deviceId: number(),
23619
- status: NetworkLinkStatusSchema
23620
- });
23621
23715
  object({
23622
23716
  on: boolean(),
23623
23717
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -25971,6 +26065,236 @@ DeviceType.Camera, method(object({
25971
26065
  detection: NativeDetectionSchema
25972
26066
  });
25973
26067
  /**
26068
+ * `navigation` — a device-scoped capability that natively expresses the FULL
26069
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
26070
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
26071
+ *
26072
+ * Why a NEW cap rather than overloading `ptz`:
26073
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
26074
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
26075
+ * The two are different physical models: PTZ is absolute-position + presets,
26076
+ * navigation is momentary drive nudges + discrete robot ACTIONS
26077
+ * (dock / spot-clean / follow-pet / go-to-point / …).
26078
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
26079
+ * the reverse:
26080
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
26081
+ * / `getOptions`), and
26082
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
26083
+ * robot camera shows up in the existing PTZ control path without every
26084
+ * PTZ provider learning about robots. The mapping lives in the adapter,
26085
+ * not here (see the addon design note):
26086
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
26087
+ * ptz.stop() → navigation.stop()
26088
+ * ptz.goHome() → navigation.runAction('goHome')
26089
+ * ptz.getPresets() → navigation.listActions() (id→preset)
26090
+ * ptz.goToPreset(id) → navigation.runAction(id)
26091
+ *
26092
+ * ## Continuous drive
26093
+ *
26094
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
26095
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
26096
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
26097
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
26098
+ * coalesce them. The UI owns the cadence.
26099
+ *
26100
+ * ## The action dictionary
26101
+ *
26102
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
26103
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
26104
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
26105
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
26106
+ * vendor-specific list. `kind: 'action'` entries are triggered with
26107
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
26108
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
26109
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
26110
+ *
26111
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
26112
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
26113
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
26114
+ * every device handle. A future nodedreame publish adds a typed
26115
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
26116
+ * provider can then swap the raw calls for the typed methods with no change to
26117
+ * THIS contract.
26118
+ */
26119
+ /**
26120
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
26121
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
26122
+ * halts it.
26123
+ *
26124
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
26125
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
26126
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
26127
+ * vector by it (drivers without proportional drive ignore it).
26128
+ *
26129
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
26130
+ * axis alone; an all-undefined nudge is a no-op.
26131
+ */
26132
+ var NavigationMoveCommandSchema = object({
26133
+ pan: number().min(-1).max(1).optional(),
26134
+ tilt: number().min(-1).max(1).optional(),
26135
+ speed: number().min(0).max(1).optional()
26136
+ });
26137
+ /**
26138
+ * The enumerated discrete actions a navigation-capable robot can perform via
26139
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
26140
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
26141
+ * `playSound` (see the `sound` dictionary entries).
26142
+ */
26143
+ var NavigationActionIdSchema = _enum([
26144
+ "goHome",
26145
+ "locate",
26146
+ "spotClean",
26147
+ "findPet",
26148
+ "personFollow",
26149
+ "stop",
26150
+ "startClean",
26151
+ "pauseClean",
26152
+ "dockWash",
26153
+ "autoEmpty",
26154
+ "flashOn",
26155
+ "flashOff"
26156
+ ]);
26157
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
26158
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
26159
+ /**
26160
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
26161
+ * native panel and the PTZ mimic render as a button.
26162
+ *
26163
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
26164
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
26165
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
26166
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
26167
+ * - `label` — operator-facing English label.
26168
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
26169
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
26170
+ * PTZ render ONLY enabled entries. Data-driven: the provider
26171
+ * flips it from config, never by editing code.
26172
+ */
26173
+ var NavigationActionEntrySchema = object({
26174
+ id: string(),
26175
+ kind: NavigationEntryKindSchema,
26176
+ label: string(),
26177
+ icon: string(),
26178
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
26179
+ soundId: number().int().optional(),
26180
+ /** Per-device feature flag — render this entry only when true. */
26181
+ enabled: boolean()
26182
+ });
26183
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
26184
+ var NavigationPointSchema = object({
26185
+ x: number(),
26186
+ y: number()
26187
+ });
26188
+ /**
26189
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
26190
+ * The cap reports which are enabled so the UI / PTZ render only the controls
26191
+ * that are turned on for THIS device. Data-driven: the provider derives these
26192
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
26193
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
26194
+ * that are not dictionary entries.
26195
+ *
26196
+ * - `move` / `stop` — the momentary drive joystick.
26197
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
26198
+ * map-coordinate plumbing is wired.
26199
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
26200
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
26201
+ * - `light` — the on/off fill-light toggle (works anytime).
26202
+ * - `lightMode` — the auto/manual selector + manual level slider (a
26203
+ * camera-service control; needs an active stream).
26204
+ */
26205
+ var NavigationFeaturesSchema = object({
26206
+ move: boolean(),
26207
+ stop: boolean(),
26208
+ goToPoint: boolean(),
26209
+ runAction: boolean(),
26210
+ playSound: boolean(),
26211
+ light: boolean(),
26212
+ lightMode: boolean()
26213
+ });
26214
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
26215
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
26216
+ /**
26217
+ * Live navigation state so the UI can reflect what the robot is doing:
26218
+ * - `mode` — coarse activity (idle / cleaning / following / …).
26219
+ * - `following` — person/pet follow is currently armed.
26220
+ * - `flash` — the on-camera fill light is on.
26221
+ * - `lightMode` — auto vs manual fill-light mode.
26222
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
26223
+ * `lightMode === 'manual'`.
26224
+ */
26225
+ var NavigationStatusSchema = object({
26226
+ mode: _enum([
26227
+ "idle",
26228
+ "cleaning",
26229
+ "spot",
26230
+ "following",
26231
+ "goto",
26232
+ "returning",
26233
+ "paused",
26234
+ "unknown"
26235
+ ]),
26236
+ following: boolean(),
26237
+ flash: boolean(),
26238
+ lightMode: NavigationLightModeSchema,
26239
+ lightLevel: number().min(40).max(100),
26240
+ /** Ms epoch when the slice was last updated. */
26241
+ lastChangedAt: number()
26242
+ });
26243
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
26244
+ DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
26245
+ deviceId: number(),
26246
+ actionId: NavigationActionIdSchema
26247
+ }), _void(), { kind: "mutation" }), method(object({
26248
+ deviceId: number(),
26249
+ soundId: number().int()
26250
+ }), _void(), { kind: "mutation" }), method(object({
26251
+ deviceId: number(),
26252
+ on: boolean()
26253
+ }), _void(), { kind: "mutation" }), method(object({
26254
+ deviceId: number(),
26255
+ mode: NavigationLightModeSchema,
26256
+ level: number().min(40).max(100).optional()
26257
+ }), _void(), { kind: "mutation" }), method(object({
26258
+ deviceId: number(),
26259
+ level: number().min(40).max(100)
26260
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
26261
+ deviceId: number(),
26262
+ status: NavigationStatusSchema
26263
+ });
26264
+ /**
26265
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
26266
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
26267
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
26268
+ * one Home Assistant projection.
26269
+ */
26270
+ var NetworkLinkStatusSchema = object({
26271
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
26272
+ type: _enum([
26273
+ "wifi",
26274
+ "ethernet",
26275
+ "cellular",
26276
+ "unknown"
26277
+ ]),
26278
+ /**
26279
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
26280
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
26281
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
26282
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
26283
+ * SKIP a null rather than coerce it.
26284
+ */
26285
+ signalPercent: number().min(0).max(100).nullable(),
26286
+ /** Raw received signal strength in dBm, when the firmware reports one. */
26287
+ rssiDbm: number().optional(),
26288
+ /** Network name of a wireless link, when the firmware reports it. */
26289
+ ssid: string().optional(),
26290
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
26291
+ lastUpdated: number()
26292
+ });
26293
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
26294
+ deviceId: number(),
26295
+ status: NetworkLinkStatusSchema
26296
+ });
26297
+ /**
25974
26298
  * network-quality — system-scoped singleton capability tracking RTT,
25975
26299
  * jitter, and observed/peak bandwidth per device + per client.
25976
26300
  *
@@ -27212,203 +27536,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
27212
27536
  deviceId: number(),
27213
27537
  status: PtzAutotrackStatusSchema
27214
27538
  });
27215
- /**
27216
- * `navigation` — a device-scoped capability that natively expresses the FULL
27217
- * navigation / action surface of a robot that DRIVES ITSELF and carries an
27218
- * on-board camera (the Dreame robot-vacuum camera is the first provider).
27219
- *
27220
- * Why a NEW cap rather than overloading `ptz`:
27221
- * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
27222
- * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
27223
- * The two are different physical models: PTZ is absolute-position + presets,
27224
- * navigation is momentary drive nudges + discrete robot ACTIONS
27225
- * (dock / spot-clean / follow-pet / go-to-point / …).
27226
- * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
27227
- * the reverse:
27228
- * 1. a native CamStack navigation panel (data-driven from `listActions`
27229
- * / `getOptions`), and
27230
- * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
27231
- * robot camera shows up in the existing PTZ control path without every
27232
- * PTZ provider learning about robots. The mapping lives in the adapter,
27233
- * not here (see the addon design note):
27234
- * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
27235
- * ptz.stop() → navigation.stop()
27236
- * ptz.goHome() → navigation.runAction('goHome')
27237
- * ptz.getPresets() → navigation.listActions() (id→preset)
27238
- * ptz.goToPreset(id) → navigation.runAction(id)
27239
- *
27240
- * ## Continuous drive
27241
- *
27242
- * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
27243
- * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
27244
- * one `stop()` on release — exactly like the robot app's remote-drive joystick.
27245
- * The provider forwards EACH `move` to one drive write; it must NOT debounce or
27246
- * coalesce them. The UI owns the cadence.
27247
- *
27248
- * ## The action dictionary
27249
- *
27250
- * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
27251
- * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
27252
- * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
27253
- * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
27254
- * vendor-specific list. `kind: 'action'` entries are triggered with
27255
- * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
27256
- * (the entry carries the `soundId` to pass). The general primitives — `move`,
27257
- * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
27258
- *
27259
- * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
27260
- * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
27261
- * that the currently-published `@apocaliss92/nodedreame` already exposes on
27262
- * every device handle. A future nodedreame publish adds a typed
27263
- * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
27264
- * provider can then swap the raw calls for the typed methods with no change to
27265
- * THIS contract.
27266
- */
27267
- /**
27268
- * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
27269
- * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
27270
- * halts it.
27271
- *
27272
- * - `pan` — turn: negative = left, positive = right, 0 = straight.
27273
- * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
27274
- * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
27275
- * vector by it (drivers without proportional drive ignore it).
27276
- *
27277
- * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
27278
- * axis alone; an all-undefined nudge is a no-op.
27279
- */
27280
- var NavigationMoveCommandSchema = object({
27281
- pan: number().min(-1).max(1).optional(),
27282
- tilt: number().min(-1).max(1).optional(),
27283
- speed: number().min(0).max(1).optional()
27284
- });
27285
- /**
27286
- * The enumerated discrete actions a navigation-capable robot can perform via
27287
- * `runAction`. This is the CLOSED vocabulary; a given device advertises the
27288
- * subset it supports through `listActions`. Sounds are NOT here — they go through
27289
- * `playSound` (see the `sound` dictionary entries).
27290
- */
27291
- var NavigationActionIdSchema = _enum([
27292
- "goHome",
27293
- "locate",
27294
- "spotClean",
27295
- "findPet",
27296
- "personFollow",
27297
- "stop",
27298
- "startClean",
27299
- "pauseClean",
27300
- "dockWash",
27301
- "autoEmpty",
27302
- "flashOn",
27303
- "flashOff"
27304
- ]);
27305
- /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
27306
- var NavigationEntryKindSchema = _enum(["action", "sound"]);
27307
- /**
27308
- * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
27309
- * native panel and the PTZ mimic render as a button.
27310
- *
27311
- * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
27312
- * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
27313
- * (`sound:meow`) whose `soundId` is passed to `playSound`.
27314
- * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
27315
- * - `label` — operator-facing English label.
27316
- * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
27317
- * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
27318
- * PTZ render ONLY enabled entries. Data-driven: the provider
27319
- * flips it from config, never by editing code.
27320
- */
27321
- var NavigationActionEntrySchema = object({
27322
- id: string(),
27323
- kind: NavigationEntryKindSchema,
27324
- label: string(),
27325
- icon: string(),
27326
- /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
27327
- soundId: number().int().optional(),
27328
- /** Per-device feature flag — render this entry only when true. */
27329
- enabled: boolean()
27330
- });
27331
- /** Coordinates for `goToPoint` — a point on the robot's live map. */
27332
- var NavigationPointSchema = object({
27333
- x: number(),
27334
- y: number()
27335
- });
27336
- /**
27337
- * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
27338
- * The cap reports which are enabled so the UI / PTZ render only the controls
27339
- * that are turned on for THIS device. Data-driven: the provider derives these
27340
- * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
27341
- * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
27342
- * that are not dictionary entries.
27343
- *
27344
- * - `move` / `stop` — the momentary drive joystick.
27345
- * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
27346
- * map-coordinate plumbing is wired.
27347
- * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
27348
- * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
27349
- * - `light` — the on/off fill-light toggle (works anytime).
27350
- * - `lightMode` — the auto/manual selector + manual level slider (a
27351
- * camera-service control; needs an active stream).
27352
- */
27353
- var NavigationFeaturesSchema = object({
27354
- move: boolean(),
27355
- stop: boolean(),
27356
- goToPoint: boolean(),
27357
- runAction: boolean(),
27358
- playSound: boolean(),
27359
- light: boolean(),
27360
- lightMode: boolean()
27361
- });
27362
- /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27363
- var NavigationLightModeSchema = _enum(["auto", "manual"]);
27364
- /**
27365
- * Live navigation state so the UI can reflect what the robot is doing:
27366
- * - `mode` — coarse activity (idle / cleaning / following / …).
27367
- * - `following` — person/pet follow is currently armed.
27368
- * - `flash` — the on-camera fill light is on.
27369
- * - `lightMode` — auto vs manual fill-light mode.
27370
- * - `lightLevel` — manual fill-light level (40..100); meaningful when
27371
- * `lightMode === 'manual'`.
27372
- */
27373
- var NavigationStatusSchema = object({
27374
- mode: _enum([
27375
- "idle",
27376
- "cleaning",
27377
- "spot",
27378
- "following",
27379
- "goto",
27380
- "returning",
27381
- "paused",
27382
- "unknown"
27383
- ]),
27384
- following: boolean(),
27385
- flash: boolean(),
27386
- lightMode: NavigationLightModeSchema,
27387
- lightLevel: number().min(40).max(100),
27388
- /** Ms epoch when the slice was last updated. */
27389
- lastChangedAt: number()
27390
- });
27391
- NavigationStatusSchema.extend({ lastFetchedAt: number() });
27392
- DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
27393
- deviceId: number(),
27394
- actionId: NavigationActionIdSchema
27395
- }), _void(), { kind: "mutation" }), method(object({
27396
- deviceId: number(),
27397
- soundId: number().int()
27398
- }), _void(), { kind: "mutation" }), method(object({
27399
- deviceId: number(),
27400
- on: boolean()
27401
- }), _void(), { kind: "mutation" }), method(object({
27402
- deviceId: number(),
27403
- mode: NavigationLightModeSchema,
27404
- level: number().min(40).max(100).optional()
27405
- }), _void(), { kind: "mutation" }), method(object({
27406
- deviceId: number(),
27407
- level: number().min(40).max(100)
27408
- }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27409
- deviceId: number(),
27410
- status: NavigationStatusSchema
27411
- });
27412
27539
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
27413
27540
  kind: "mutation",
27414
27541
  auth: "admin"
@@ -34717,13 +34844,13 @@ Object.freeze({
34717
34844
  addonId: null,
34718
34845
  access: "view"
34719
34846
  },
34720
- "storage.getDefaultLocation": {
34847
+ "storage.list": {
34721
34848
  capName: "storage",
34722
34849
  capScope: "system",
34723
34850
  addonId: null,
34724
34851
  access: "view"
34725
34852
  },
34726
- "storage.list": {
34853
+ "storage.listDrainProgress": {
34727
34854
  capName: "storage",
34728
34855
  capScope: "system",
34729
34856
  addonId: null,
@@ -34873,6 +35000,12 @@ Object.freeze({
34873
35000
  addonId: null,
34874
35001
  access: "view"
34875
35002
  },
35003
+ "storageOccupancy.getOccupancy": {
35004
+ capName: "storage-occupancy",
35005
+ capScope: "system",
35006
+ addonId: null,
35007
+ access: "view"
35008
+ },
34876
35009
  "storageProvider.abortUpload": {
34877
35010
  capName: "storage-provider",
34878
35011
  capScope: "system",