@camstack/addon-pipeline-orchestrator 1.2.166 → 1.2.167

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.
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ node_fs = __toESM(node_fs);
30
30
  let node_path = require("node:path");
31
31
  node_path = __toESM(node_path);
32
32
  let node_url = require("node:url");
33
- //#region ../types/dist/event-category-zAv7pMUz.mjs
33
+ //#region ../types/dist/event-category-CnLqLOKs.mjs
34
34
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
35
35
  EventCategory["SystemBoot"] = "system.boot";
36
36
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -225,6 +225,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
225
225
  EventCategory["ProcessCrashed"] = "process.crashed";
226
226
  EventCategory["ProcessRestartScheduled"] = "process.restart_scheduled";
227
227
  EventCategory["ProcessRestarted"] = "process.restarted";
228
+ /**
229
+ * The SET of storage locations changed — one was created, edited, enabled,
230
+ * disabled or deleted through `storage.upsertLocation` / `deleteLocation`.
231
+ *
232
+ * Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
233
+ * it must also converge on its own periodic path, because a dropped event
234
+ * must not leave a node writing to yesterday's disk set forever. It exists
235
+ * because there was NO signal at all — an operator who added a second
236
+ * recordings disk in the admin UI got nothing, and the recorder kept its
237
+ * resolved locations until something else happened to re-resolve them
238
+ * (D387). Payload `StorageLocationsChangedPayload`.
239
+ */
240
+ EventCategory["StorageLocationsChanged"] = "storage.locations-changed";
228
241
  EventCategory["RecordingStarted"] = "recording.started";
229
242
  EventCategory["RecordingStopped"] = "recording.stopped";
230
243
  EventCategory["RecordingError"] = "recording.error";
@@ -9412,6 +9425,21 @@ var StorageCleanupJobSchema = object({
9412
9425
  });
9413
9426
  var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
9414
9427
  /**
9428
+ * The one typed state of a storage location. Authoritative Zod schema — the TS
9429
+ * alias below is `z.infer<>` of it, never a second spelling.
9430
+ */
9431
+ var StorageLocationModeSchema = _enum([
9432
+ "active",
9433
+ "readonly",
9434
+ "drain",
9435
+ "disabled"
9436
+ ]);
9437
+ _enum([
9438
+ "normal",
9439
+ "never",
9440
+ "drain"
9441
+ ]);
9442
+ /**
9415
9443
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
9416
9444
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
9417
9445
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -9477,6 +9505,21 @@ var StorageLocationSchema = object({
9477
9505
  * stops existing rather than being re-derived on every read.
9478
9506
  */
9479
9507
  enabled: boolean().optional(),
9508
+ /**
9509
+ * THE state of this location (D385), and the only authority on what may be
9510
+ * written, read or evicted here. Interpreted in exactly one place —
9511
+ * `storage-location-mode.ts` — which also folds the legacy
9512
+ * `enabled` / `config.readOnly` pair into a mode so an old row is never
9513
+ * ambiguous.
9514
+ *
9515
+ * OPTIONAL only for the wire and for rows written before D385: absence is
9516
+ * resolved by `resolveLocationMode`, and the orchestrator stamps every
9517
+ * unstamped row ONCE at hydrate so absence stops existing rather than being
9518
+ * re-derived on every read. `enabled` survives one release as a DERIVED
9519
+ * mirror (`mode === 'active'`); `withLocationMode` is the only writer of
9520
+ * either, so the two cannot disagree.
9521
+ */
9522
+ mode: StorageLocationModeSchema.optional(),
9480
9523
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
9481
9524
  * for node-local locations it can reach) — never persisted, absent when the
9482
9525
  * volume is remote/unreachable. The single capacity truth every UI reads. */
@@ -9484,11 +9527,46 @@ var StorageLocationSchema = object({
9484
9527
  totalBytes: number(),
9485
9528
  availableBytes: number()
9486
9529
  }).nullable().optional(),
9530
+ /**
9531
+ * How much of that volume CamStack ITSELF holds on this location (D388) —
9532
+ * COMPUTED at read time from the `storage-occupancy` providers' own figures,
9533
+ * never persisted, never a filesystem walk.
9534
+ *
9535
+ * **ABSENT MEANS UNKNOWN, never zero.** No provider has reported for this
9536
+ * location yet — nobody stores here, the owning addon is down, or the first
9537
+ * refresh has not completed. A UI must omit the segment rather than draw it
9538
+ * at zero, which would claim we occupy nothing (D315). It is an OBJECT and
9539
+ * not a bare number precisely so that a `?? 0` on the consuming side has to
9540
+ * be spelled out loud instead of appearing by accident.
9541
+ *
9542
+ * `measuredAtMs` is the OLDEST contributing measurement, so it is honest
9543
+ * about the whole figure rather than about its freshest part.
9544
+ */
9545
+ owned: object({
9546
+ bytes: number().int().nonnegative(),
9547
+ measuredAtMs: number().int().nonnegative()
9548
+ }).optional(),
9487
9549
  createdAt: number(),
9488
9550
  updatedAt: number()
9489
9551
  });
9490
9552
  object({ isDefault: boolean().optional() });
9491
9553
  /**
9554
+ * How far a `drain` has got (D386) — the read a UI renders, and nothing more.
9555
+ *
9556
+ * `estimatedEmptyAtMs` is derived from the growth the ratchet has actually
9557
+ * OBSERVED and is `null` when it has observed none. Never a fabricated date: a
9558
+ * drain with no observed growth has no honest ETA, and inventing one is how an
9559
+ * operator learns not to believe the screen.
9560
+ */
9561
+ var StorageDrainProgressSchema = object({
9562
+ locationId: string(),
9563
+ startedAtMs: number(),
9564
+ startBytes: number(),
9565
+ bytesRemaining: number(),
9566
+ drained: boolean(),
9567
+ estimatedEmptyAtMs: number().nullable()
9568
+ });
9569
+ /**
9492
9570
  * Reference accepted by consumer-facing `api.storage.*` calls.
9493
9571
  * Either:
9494
9572
  * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
@@ -23240,7 +23318,7 @@ method(object({
23240
23318
  }), _void(), {
23241
23319
  kind: "mutation",
23242
23320
  auth: "admin"
23243
- }), method(object({ id: string() }), object({
23321
+ }), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
23244
23322
  ok: boolean(),
23245
23323
  error: string().optional()
23246
23324
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
@@ -23310,6 +23388,51 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
23310
23388
  kind: "mutation",
23311
23389
  auth: "admin"
23312
23390
  }), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
23391
+ /**
23392
+ * `storage-occupancy` — how many bytes an addon actually HOLDS on a storage
23393
+ * location (D388).
23394
+ *
23395
+ * ## Why this is not `storage-evictable`
23396
+ *
23397
+ * `storage-evictable.getEvictableUsage` looks like the same question and is
23398
+ * not, in two ways that both matter and both bite hardest on the locations an
23399
+ * operator most wants a figure for:
23400
+ *
23401
+ * - it reports the whole eviction DOMAIN, not the location. `recordings:default`
23402
+ * and `recordingsLow:default` deliberately share one root and evict as one
23403
+ * oldest-first pool, so both answer with the SAME combined total. As an
23404
+ * occupancy figure that double-counts the disk.
23405
+ * - it reports ZERO for a location whose eviction policy is `never` (D385) —
23406
+ * a `readonly` or `disabled` disk. Those are exactly the disks an operator
23407
+ * is retiring and staring at.
23408
+ *
23409
+ * So this is its own contract with its own quantity, and the quantity is
23410
+ * OCCUPIED: every byte the addon holds on that location, whether or not it
23411
+ * would ever be willing to delete it. A provider that can only answer
23412
+ * "evictable" must not register here — a number that silently means different
23413
+ * things per class is worse than no number.
23414
+ *
23415
+ * ## Absence is an answer
23416
+ *
23417
+ * A location nobody reports for is UNKNOWN, never zero (D315). The orchestrator
23418
+ * stamps `StorageLocation.owned` only for locations it has a report for, and
23419
+ * the field is an OBJECT rather than a bare number so that a `?? 0` on the
23420
+ * consuming side has to be written out loud instead of appearing by accident.
23421
+ *
23422
+ * `internal: true` — consumed by the orchestrator's `listLocations` stamp, never
23423
+ * a public client surface. Clients read the stamped `StorageLocation.owned`.
23424
+ */
23425
+ /** One provider's occupancy answer for one location. */
23426
+ var StorageOccupancyReportSchema = object({
23427
+ locationId: string(),
23428
+ /** Bytes this provider holds on THAT location — not its eviction domain, and
23429
+ * not net of what it is willing to delete. */
23430
+ ownedBytes: number().int().nonnegative(),
23431
+ /** When the provider last actually measured this. The orchestrator carries it
23432
+ * through so a UI can say how old the figure is instead of implying "now". */
23433
+ measuredAtMs: number().int().nonnegative()
23434
+ });
23435
+ method(object({ locationIds: array(string()).readonly() }), array(StorageOccupancyReportSchema).readonly(), { auth: "admin" });
23313
23436
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
23314
23437
  providerId: string().min(1),
23315
23438
  displayName: string().min(1),
@@ -24945,39 +25068,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
24945
25068
  deviceId: number(),
24946
25069
  status: BatteryStatusSchema
24947
25070
  });
24948
- /**
24949
- * Network-link snapshot. Same shape for every provider (a Reolink wifi
24950
- * camera, a Home Assistant device with a signal-strength sensor, a Tapo
24951
- * plug): one slice under `device.runtimeState['network-link']`, one badge,
24952
- * one Home Assistant projection.
24953
- */
24954
- var NetworkLinkStatusSchema = object({
24955
- /** The link the device is on. `'unknown'` = not read yet, not "no link". */
24956
- type: _enum([
24957
- "wifi",
24958
- "ethernet",
24959
- "cellular",
24960
- "unknown"
24961
- ]),
24962
- /**
24963
- * Link quality, 0..100 inclusive, normalised by the provider from whatever
24964
- * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
24965
- * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
24966
- * one whose reading has not landed must not be drawn at 0 %. Consumers
24967
- * SKIP a null rather than coerce it.
24968
- */
24969
- signalPercent: number().min(0).max(100).nullable(),
24970
- /** Raw received signal strength in dBm, when the firmware reports one. */
24971
- rssiDbm: number().optional(),
24972
- /** Network name of a wireless link, when the firmware reports it. */
24973
- ssid: string().optional(),
24974
- /** Ms epoch of the last observation. Lets consumers reason about freshness. */
24975
- lastUpdated: number()
24976
- });
24977
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
24978
- deviceId: number(),
24979
- status: NetworkLinkStatusSchema
24980
- });
24981
25071
  object({
24982
25072
  on: boolean(),
24983
25073
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -27331,6 +27421,236 @@ DeviceType.Camera, method(object({
27331
27421
  detection: NativeDetectionSchema
27332
27422
  });
27333
27423
  /**
27424
+ * `navigation` — a device-scoped capability that natively expresses the FULL
27425
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
27426
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
27427
+ *
27428
+ * Why a NEW cap rather than overloading `ptz`:
27429
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
27430
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
27431
+ * The two are different physical models: PTZ is absolute-position + presets,
27432
+ * navigation is momentary drive nudges + discrete robot ACTIONS
27433
+ * (dock / spot-clean / follow-pet / go-to-point / …).
27434
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
27435
+ * the reverse:
27436
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
27437
+ * / `getOptions`), and
27438
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
27439
+ * robot camera shows up in the existing PTZ control path without every
27440
+ * PTZ provider learning about robots. The mapping lives in the adapter,
27441
+ * not here (see the addon design note):
27442
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
27443
+ * ptz.stop() → navigation.stop()
27444
+ * ptz.goHome() → navigation.runAction('goHome')
27445
+ * ptz.getPresets() → navigation.listActions() (id→preset)
27446
+ * ptz.goToPreset(id) → navigation.runAction(id)
27447
+ *
27448
+ * ## Continuous drive
27449
+ *
27450
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
27451
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
27452
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
27453
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
27454
+ * coalesce them. The UI owns the cadence.
27455
+ *
27456
+ * ## The action dictionary
27457
+ *
27458
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
27459
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
27460
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
27461
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
27462
+ * vendor-specific list. `kind: 'action'` entries are triggered with
27463
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
27464
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
27465
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
27466
+ *
27467
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
27468
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
27469
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
27470
+ * every device handle. A future nodedreame publish adds a typed
27471
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
27472
+ * provider can then swap the raw calls for the typed methods with no change to
27473
+ * THIS contract.
27474
+ */
27475
+ /**
27476
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
27477
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
27478
+ * halts it.
27479
+ *
27480
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
27481
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
27482
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
27483
+ * vector by it (drivers without proportional drive ignore it).
27484
+ *
27485
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
27486
+ * axis alone; an all-undefined nudge is a no-op.
27487
+ */
27488
+ var NavigationMoveCommandSchema = object({
27489
+ pan: number().min(-1).max(1).optional(),
27490
+ tilt: number().min(-1).max(1).optional(),
27491
+ speed: number().min(0).max(1).optional()
27492
+ });
27493
+ /**
27494
+ * The enumerated discrete actions a navigation-capable robot can perform via
27495
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
27496
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
27497
+ * `playSound` (see the `sound` dictionary entries).
27498
+ */
27499
+ var NavigationActionIdSchema = _enum([
27500
+ "goHome",
27501
+ "locate",
27502
+ "spotClean",
27503
+ "findPet",
27504
+ "personFollow",
27505
+ "stop",
27506
+ "startClean",
27507
+ "pauseClean",
27508
+ "dockWash",
27509
+ "autoEmpty",
27510
+ "flashOn",
27511
+ "flashOff"
27512
+ ]);
27513
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
27514
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
27515
+ /**
27516
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
27517
+ * native panel and the PTZ mimic render as a button.
27518
+ *
27519
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
27520
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
27521
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
27522
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
27523
+ * - `label` — operator-facing English label.
27524
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
27525
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
27526
+ * PTZ render ONLY enabled entries. Data-driven: the provider
27527
+ * flips it from config, never by editing code.
27528
+ */
27529
+ var NavigationActionEntrySchema = object({
27530
+ id: string(),
27531
+ kind: NavigationEntryKindSchema,
27532
+ label: string(),
27533
+ icon: string(),
27534
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
27535
+ soundId: number().int().optional(),
27536
+ /** Per-device feature flag — render this entry only when true. */
27537
+ enabled: boolean()
27538
+ });
27539
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
27540
+ var NavigationPointSchema = object({
27541
+ x: number(),
27542
+ y: number()
27543
+ });
27544
+ /**
27545
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
27546
+ * The cap reports which are enabled so the UI / PTZ render only the controls
27547
+ * that are turned on for THIS device. Data-driven: the provider derives these
27548
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
27549
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
27550
+ * that are not dictionary entries.
27551
+ *
27552
+ * - `move` / `stop` — the momentary drive joystick.
27553
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
27554
+ * map-coordinate plumbing is wired.
27555
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
27556
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
27557
+ * - `light` — the on/off fill-light toggle (works anytime).
27558
+ * - `lightMode` — the auto/manual selector + manual level slider (a
27559
+ * camera-service control; needs an active stream).
27560
+ */
27561
+ var NavigationFeaturesSchema = object({
27562
+ move: boolean(),
27563
+ stop: boolean(),
27564
+ goToPoint: boolean(),
27565
+ runAction: boolean(),
27566
+ playSound: boolean(),
27567
+ light: boolean(),
27568
+ lightMode: boolean()
27569
+ });
27570
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27571
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27572
+ /**
27573
+ * Live navigation state so the UI can reflect what the robot is doing:
27574
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27575
+ * - `following` — person/pet follow is currently armed.
27576
+ * - `flash` — the on-camera fill light is on.
27577
+ * - `lightMode` — auto vs manual fill-light mode.
27578
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27579
+ * `lightMode === 'manual'`.
27580
+ */
27581
+ var NavigationStatusSchema = object({
27582
+ mode: _enum([
27583
+ "idle",
27584
+ "cleaning",
27585
+ "spot",
27586
+ "following",
27587
+ "goto",
27588
+ "returning",
27589
+ "paused",
27590
+ "unknown"
27591
+ ]),
27592
+ following: boolean(),
27593
+ flash: boolean(),
27594
+ lightMode: NavigationLightModeSchema,
27595
+ lightLevel: number().min(40).max(100),
27596
+ /** Ms epoch when the slice was last updated. */
27597
+ lastChangedAt: number()
27598
+ });
27599
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27600
+ 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({
27601
+ deviceId: number(),
27602
+ actionId: NavigationActionIdSchema
27603
+ }), _void(), { kind: "mutation" }), method(object({
27604
+ deviceId: number(),
27605
+ soundId: number().int()
27606
+ }), _void(), { kind: "mutation" }), method(object({
27607
+ deviceId: number(),
27608
+ on: boolean()
27609
+ }), _void(), { kind: "mutation" }), method(object({
27610
+ deviceId: number(),
27611
+ mode: NavigationLightModeSchema,
27612
+ level: number().min(40).max(100).optional()
27613
+ }), _void(), { kind: "mutation" }), method(object({
27614
+ deviceId: number(),
27615
+ level: number().min(40).max(100)
27616
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27617
+ deviceId: number(),
27618
+ status: NavigationStatusSchema
27619
+ });
27620
+ /**
27621
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
27622
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
27623
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
27624
+ * one Home Assistant projection.
27625
+ */
27626
+ var NetworkLinkStatusSchema = object({
27627
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
27628
+ type: _enum([
27629
+ "wifi",
27630
+ "ethernet",
27631
+ "cellular",
27632
+ "unknown"
27633
+ ]),
27634
+ /**
27635
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
27636
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
27637
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
27638
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
27639
+ * SKIP a null rather than coerce it.
27640
+ */
27641
+ signalPercent: number().min(0).max(100).nullable(),
27642
+ /** Raw received signal strength in dBm, when the firmware reports one. */
27643
+ rssiDbm: number().optional(),
27644
+ /** Network name of a wireless link, when the firmware reports it. */
27645
+ ssid: string().optional(),
27646
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
27647
+ lastUpdated: number()
27648
+ });
27649
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
27650
+ deviceId: number(),
27651
+ status: NetworkLinkStatusSchema
27652
+ });
27653
+ /**
27334
27654
  * network-quality — system-scoped singleton capability tracking RTT,
27335
27655
  * jitter, and observed/peak bandwidth per device + per client.
27336
27656
  *
@@ -28572,203 +28892,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
28572
28892
  deviceId: number(),
28573
28893
  status: PtzAutotrackStatusSchema
28574
28894
  });
28575
- /**
28576
- * `navigation` — a device-scoped capability that natively expresses the FULL
28577
- * navigation / action surface of a robot that DRIVES ITSELF and carries an
28578
- * on-board camera (the Dreame robot-vacuum camera is the first provider).
28579
- *
28580
- * Why a NEW cap rather than overloading `ptz`:
28581
- * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
28582
- * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
28583
- * The two are different physical models: PTZ is absolute-position + presets,
28584
- * navigation is momentary drive nudges + discrete robot ACTIONS
28585
- * (dock / spot-clean / follow-pet / go-to-point / …).
28586
- * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
28587
- * the reverse:
28588
- * 1. a native CamStack navigation panel (data-driven from `listActions`
28589
- * / `getOptions`), and
28590
- * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
28591
- * robot camera shows up in the existing PTZ control path without every
28592
- * PTZ provider learning about robots. The mapping lives in the adapter,
28593
- * not here (see the addon design note):
28594
- * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
28595
- * ptz.stop() → navigation.stop()
28596
- * ptz.goHome() → navigation.runAction('goHome')
28597
- * ptz.getPresets() → navigation.listActions() (id→preset)
28598
- * ptz.goToPreset(id) → navigation.runAction(id)
28599
- *
28600
- * ## Continuous drive
28601
- *
28602
- * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
28603
- * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
28604
- * one `stop()` on release — exactly like the robot app's remote-drive joystick.
28605
- * The provider forwards EACH `move` to one drive write; it must NOT debounce or
28606
- * coalesce them. The UI owns the cadence.
28607
- *
28608
- * ## The action dictionary
28609
- *
28610
- * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
28611
- * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
28612
- * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
28613
- * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
28614
- * vendor-specific list. `kind: 'action'` entries are triggered with
28615
- * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
28616
- * (the entry carries the `soundId` to pass). The general primitives — `move`,
28617
- * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
28618
- *
28619
- * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
28620
- * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
28621
- * that the currently-published `@apocaliss92/nodedreame` already exposes on
28622
- * every device handle. A future nodedreame publish adds a typed
28623
- * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
28624
- * provider can then swap the raw calls for the typed methods with no change to
28625
- * THIS contract.
28626
- */
28627
- /**
28628
- * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
28629
- * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
28630
- * halts it.
28631
- *
28632
- * - `pan` — turn: negative = left, positive = right, 0 = straight.
28633
- * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
28634
- * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
28635
- * vector by it (drivers without proportional drive ignore it).
28636
- *
28637
- * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
28638
- * axis alone; an all-undefined nudge is a no-op.
28639
- */
28640
- var NavigationMoveCommandSchema = object({
28641
- pan: number().min(-1).max(1).optional(),
28642
- tilt: number().min(-1).max(1).optional(),
28643
- speed: number().min(0).max(1).optional()
28644
- });
28645
- /**
28646
- * The enumerated discrete actions a navigation-capable robot can perform via
28647
- * `runAction`. This is the CLOSED vocabulary; a given device advertises the
28648
- * subset it supports through `listActions`. Sounds are NOT here — they go through
28649
- * `playSound` (see the `sound` dictionary entries).
28650
- */
28651
- var NavigationActionIdSchema = _enum([
28652
- "goHome",
28653
- "locate",
28654
- "spotClean",
28655
- "findPet",
28656
- "personFollow",
28657
- "stop",
28658
- "startClean",
28659
- "pauseClean",
28660
- "dockWash",
28661
- "autoEmpty",
28662
- "flashOn",
28663
- "flashOff"
28664
- ]);
28665
- /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
28666
- var NavigationEntryKindSchema = _enum(["action", "sound"]);
28667
- /**
28668
- * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
28669
- * native panel and the PTZ mimic render as a button.
28670
- *
28671
- * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
28672
- * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
28673
- * (`sound:meow`) whose `soundId` is passed to `playSound`.
28674
- * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
28675
- * - `label` — operator-facing English label.
28676
- * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
28677
- * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
28678
- * PTZ render ONLY enabled entries. Data-driven: the provider
28679
- * flips it from config, never by editing code.
28680
- */
28681
- var NavigationActionEntrySchema = object({
28682
- id: string(),
28683
- kind: NavigationEntryKindSchema,
28684
- label: string(),
28685
- icon: string(),
28686
- /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
28687
- soundId: number().int().optional(),
28688
- /** Per-device feature flag — render this entry only when true. */
28689
- enabled: boolean()
28690
- });
28691
- /** Coordinates for `goToPoint` — a point on the robot's live map. */
28692
- var NavigationPointSchema = object({
28693
- x: number(),
28694
- y: number()
28695
- });
28696
- /**
28697
- * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
28698
- * The cap reports which are enabled so the UI / PTZ render only the controls
28699
- * that are turned on for THIS device. Data-driven: the provider derives these
28700
- * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
28701
- * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
28702
- * that are not dictionary entries.
28703
- *
28704
- * - `move` / `stop` — the momentary drive joystick.
28705
- * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
28706
- * map-coordinate plumbing is wired.
28707
- * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
28708
- * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
28709
- * - `light` — the on/off fill-light toggle (works anytime).
28710
- * - `lightMode` — the auto/manual selector + manual level slider (a
28711
- * camera-service control; needs an active stream).
28712
- */
28713
- var NavigationFeaturesSchema = object({
28714
- move: boolean(),
28715
- stop: boolean(),
28716
- goToPoint: boolean(),
28717
- runAction: boolean(),
28718
- playSound: boolean(),
28719
- light: boolean(),
28720
- lightMode: boolean()
28721
- });
28722
- /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
28723
- var NavigationLightModeSchema = _enum(["auto", "manual"]);
28724
- /**
28725
- * Live navigation state so the UI can reflect what the robot is doing:
28726
- * - `mode` — coarse activity (idle / cleaning / following / …).
28727
- * - `following` — person/pet follow is currently armed.
28728
- * - `flash` — the on-camera fill light is on.
28729
- * - `lightMode` — auto vs manual fill-light mode.
28730
- * - `lightLevel` — manual fill-light level (40..100); meaningful when
28731
- * `lightMode === 'manual'`.
28732
- */
28733
- var NavigationStatusSchema = object({
28734
- mode: _enum([
28735
- "idle",
28736
- "cleaning",
28737
- "spot",
28738
- "following",
28739
- "goto",
28740
- "returning",
28741
- "paused",
28742
- "unknown"
28743
- ]),
28744
- following: boolean(),
28745
- flash: boolean(),
28746
- lightMode: NavigationLightModeSchema,
28747
- lightLevel: number().min(40).max(100),
28748
- /** Ms epoch when the slice was last updated. */
28749
- lastChangedAt: number()
28750
- });
28751
- NavigationStatusSchema.extend({ lastFetchedAt: number() });
28752
- 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({
28753
- deviceId: number(),
28754
- actionId: NavigationActionIdSchema
28755
- }), _void(), { kind: "mutation" }), method(object({
28756
- deviceId: number(),
28757
- soundId: number().int()
28758
- }), _void(), { kind: "mutation" }), method(object({
28759
- deviceId: number(),
28760
- on: boolean()
28761
- }), _void(), { kind: "mutation" }), method(object({
28762
- deviceId: number(),
28763
- mode: NavigationLightModeSchema,
28764
- level: number().min(40).max(100).optional()
28765
- }), _void(), { kind: "mutation" }), method(object({
28766
- deviceId: number(),
28767
- level: number().min(40).max(100)
28768
- }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
28769
- deviceId: number(),
28770
- status: NavigationStatusSchema
28771
- });
28772
28895
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
28773
28896
  kind: "mutation",
28774
28897
  auth: "admin"
@@ -36167,6 +36290,12 @@ Object.freeze({
36167
36290
  addonId: null,
36168
36291
  access: "view"
36169
36292
  },
36293
+ "storage.listDrainProgress": {
36294
+ capName: "storage",
36295
+ capScope: "system",
36296
+ addonId: null,
36297
+ access: "view"
36298
+ },
36170
36299
  "storage.listLocationDeclarations": {
36171
36300
  capName: "storage",
36172
36301
  capScope: "system",
@@ -36311,6 +36440,12 @@ Object.freeze({
36311
36440
  addonId: null,
36312
36441
  access: "view"
36313
36442
  },
36443
+ "storageOccupancy.getOccupancy": {
36444
+ capName: "storage-occupancy",
36445
+ capScope: "system",
36446
+ addonId: null,
36447
+ access: "view"
36448
+ },
36314
36449
  "storageProvider.abortUpload": {
36315
36450
  capName: "storage-provider",
36316
36451
  capScope: "system",