@camstack/system 1.2.202 → 1.2.203

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 (60) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +12 -0
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +37 -3
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +37 -3
  10. package/dist/builtins/console-logging/index.js +1 -1
  11. package/dist/builtins/console-logging/index.mjs +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  15. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  18. package/dist/builtins/hub-forwarder/index.js +1 -1
  19. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  24. package/dist/builtins/local-network/local-network.addon.js +1 -1
  25. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  26. package/dist/builtins/loki-logging/index.js +1 -1
  27. package/dist/builtins/loki-logging/index.mjs +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  30. package/dist/builtins/platform-probe/index.js +1 -1
  31. package/dist/builtins/platform-probe/index.mjs +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  34. package/dist/builtins/snapshot/index.js +1 -1
  35. package/dist/builtins/snapshot/index.mjs +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  38. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  40. package/dist/builtins/storage-orchestrator/drain-ratchet.d.ts +104 -0
  41. package/dist/builtins/storage-orchestrator/drain-state-store.d.ts +13 -0
  42. package/dist/builtins/storage-orchestrator/location-occupancy-slice.d.ts +47 -0
  43. package/dist/builtins/storage-orchestrator/location-protection.d.ts +20 -0
  44. package/dist/builtins/storage-orchestrator/location-root-collision.d.ts +44 -0
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +47 -0
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +714 -58
  47. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +714 -58
  48. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +32 -11
  49. package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +45 -0
  50. package/dist/builtins/system-config/system-config.addon.js +1 -1
  51. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  52. package/dist/builtins/winston-logging/index.js +1 -1
  53. package/dist/builtins/winston-logging/index.mjs +1 -1
  54. package/dist/{dist-jZb-HKI5.js → dist-BFvWv_bz.js} +730 -372
  55. package/dist/{dist-CiHYspij.mjs → dist-DcMUcdeq.mjs} +671 -373
  56. package/dist/index.js +1 -1
  57. package/dist/index.mjs +1 -1
  58. package/dist/{retired-settings-keys-DeRxo3vg.mjs → retired-settings-keys-CJfgnUSb.mjs} +1 -1
  59. package/dist/{retired-settings-keys-Dxv9S_lO.js → retired-settings-keys-Dd7K3K3r.js} +1 -1
  60. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- //#region ../types/dist/event-category-zAv7pMUz.mjs
2
+ //#region ../types/dist/event-category-CnLqLOKs.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -194,6 +194,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
194
194
  EventCategory["ProcessCrashed"] = "process.crashed";
195
195
  EventCategory["ProcessRestartScheduled"] = "process.restart_scheduled";
196
196
  EventCategory["ProcessRestarted"] = "process.restarted";
197
+ /**
198
+ * The SET of storage locations changed — one was created, edited, enabled,
199
+ * disabled or deleted through `storage.upsertLocation` / `deleteLocation`.
200
+ *
201
+ * Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
202
+ * it must also converge on its own periodic path, because a dropped event
203
+ * must not leave a node writing to yesterday's disk set forever. It exists
204
+ * because there was NO signal at all — an operator who added a second
205
+ * recordings disk in the admin UI got nothing, and the recorder kept its
206
+ * resolved locations until something else happened to re-resolve them
207
+ * (D387). Payload `StorageLocationsChangedPayload`.
208
+ */
209
+ EventCategory["StorageLocationsChanged"] = "storage.locations-changed";
197
210
  EventCategory["RecordingStarted"] = "recording.started";
198
211
  EventCategory["RecordingStopped"] = "recording.stopped";
199
212
  EventCategory["RecordingError"] = "recording.error";
@@ -4768,6 +4781,144 @@ var StorageCleanupJobSchema = z.object({
4768
4781
  });
4769
4782
  var StorageCleanupStatusInputSchema = z.object({ jobId: z.string().optional() });
4770
4783
  /**
4784
+ * The storage-location STATE MODEL (D385) — one typed state, one policy module.
4785
+ *
4786
+ * A location's state used to be split across two authorities: the typed
4787
+ * `enabled` field (THE write switch since D383) and an untyped `config.readOnly`
4788
+ * key. They did not mean the same thing — `enabled: false` was still evicted
4789
+ * under disk pressure while `config.readOnly` was deliberately excluded — and
4790
+ * neither name said which. Every consumer re-derived the difference, and the
4791
+ * three questions that actually matter were answered in six places.
4792
+ *
4793
+ * This module is the ONLY place in the repo allowed to interpret the state. It
4794
+ * answers three questions and nothing else:
4795
+ *
4796
+ * - may this location be WRITTEN to? {@link modeMayWrite}
4797
+ * - may this location be READ? {@link modeMayRead}
4798
+ * - what is its eviction policy? {@link evictionPolicyForMode}
4799
+ *
4800
+ * | mode | write | read | eviction |
4801
+ * | ---------- | ----- | ---- | ------------------------------ |
4802
+ * | `active` | yes | yes | `normal` (pressure + usage cap) |
4803
+ * | `readonly` | no | yes | `never` |
4804
+ * | `drain` | no | yes | `drain` (paced, until empty) |
4805
+ * | `disabled` | no | no | `never` |
4806
+ *
4807
+ * `scripts/check-storage-location-mode-single-owner.ts` fails the build when
4808
+ * anything outside this module reads `config['readOnly']` or compares `enabled`
4809
+ * directly. A rule nothing checks has already been broken somewhere.
4810
+ */
4811
+ var STORAGE_LOCATION_MODES = [
4812
+ "active",
4813
+ "readonly",
4814
+ "drain",
4815
+ "disabled"
4816
+ ];
4817
+ /**
4818
+ * The one typed state of a storage location. Authoritative Zod schema — the TS
4819
+ * alias below is `z.infer<>` of it, never a second spelling.
4820
+ */
4821
+ var StorageLocationModeSchema = z.enum(STORAGE_LOCATION_MODES);
4822
+ z.enum([
4823
+ "normal",
4824
+ "never",
4825
+ "drain"
4826
+ ]);
4827
+ /**
4828
+ * The legacy untyped drain key. Named ONCE, here, so the guard has exactly one
4829
+ * sanctioned reader and the string never appears anywhere else.
4830
+ */
4831
+ var LEGACY_READ_ONLY_CONFIG_KEY = "readOnly";
4832
+ /** Is this mode a write target? Only `active` is. */
4833
+ function modeMayWrite(mode) {
4834
+ return mode === "active";
4835
+ }
4836
+ /** May this mode be read (playback, timeline, scrub, relocate source)? */
4837
+ function modeMayRead(mode) {
4838
+ return mode !== "disabled";
4839
+ }
4840
+ /** What eviction may do here. See {@link StorageEvictionPolicy}. */
4841
+ function evictionPolicyForMode(mode) {
4842
+ switch (mode) {
4843
+ case "active": return "normal";
4844
+ case "drain": return "drain";
4845
+ case "readonly":
4846
+ case "disabled": return "never";
4847
+ }
4848
+ }
4849
+ /**
4850
+ * The mode a LEGACY row implies, or `null` when it implies nothing — the row is
4851
+ * already stamped, or it carried neither flag.
4852
+ *
4853
+ * Both legacy flags fold to `readonly`, which is the CONSERVATIVE direction: a
4854
+ * state change must never start deleting footage on its own, and it must never
4855
+ * make footage that was still being served disappear. `enabled: false` used to
4856
+ * leave the location evictable under pressure; folding it to `readonly` stops
4857
+ * that, which is a strictly safer answer than the one it replaces.
4858
+ */
4859
+ function legacyModeOf(location) {
4860
+ if (location.mode !== void 0) return null;
4861
+ if (location.config["readOnly"] === true) return "readonly";
4862
+ if (location.enabled === false) return "readonly";
4863
+ return null;
4864
+ }
4865
+ /**
4866
+ * The state of a location, stamped or folded. THE one interpretation: a row
4867
+ * that predates D385 is never ambiguous, and a stamped `mode` always wins over
4868
+ * whatever the legacy pair still says.
4869
+ */
4870
+ function resolveLocationMode(location) {
4871
+ return (isStorageLocationMode(location.mode) ? location.mode : void 0) ?? legacyModeOf(location) ?? "active";
4872
+ }
4873
+ /** Is this one of the four states? The stamped value crosses a wire, and a
4874
+ * value nobody defined must not be rendered as if it were a state. */
4875
+ function isStorageLocationMode(value) {
4876
+ return STORAGE_LOCATION_MODES.some((mode) => mode === value);
4877
+ }
4878
+ /** May this location be written to? */
4879
+ function mayWriteToLocation(location) {
4880
+ return modeMayWrite(resolveLocationMode(location));
4881
+ }
4882
+ /** May this location be read? A `disabled` one may not — and that is an
4883
+ * operator CHOICE, which callers must report as unavailable rather than as an
4884
+ * unknown-location fault. */
4885
+ function mayReadLocation(location) {
4886
+ return modeMayRead(resolveLocationMode(location));
4887
+ }
4888
+ /** What eviction may do to this location. */
4889
+ function evictionPolicyOfLocation(location) {
4890
+ return evictionPolicyForMode(resolveLocationMode(location));
4891
+ }
4892
+ /**
4893
+ * The config blob with the retired drain key removed. Returns the SAME object
4894
+ * when there was nothing to strip, so a caller can tell "changed" from
4895
+ * "unchanged" by identity and skip a pointless persist.
4896
+ */
4897
+ function strippedOfLegacyReadOnly(config) {
4898
+ if (!("readOnly" in config)) return config;
4899
+ const next = { ...config };
4900
+ delete next[LEGACY_READ_ONLY_CONFIG_KEY];
4901
+ return next;
4902
+ }
4903
+ /**
4904
+ * Set a location's mode, and with it everything that must agree with it.
4905
+ *
4906
+ * `enabled` stays readable for one release as a DERIVED value (`mode ===
4907
+ * 'active'`) so consumers that have not moved yet keep working, and the legacy
4908
+ * `config.readOnly` key is DELETED. This function is the only way to write
4909
+ * either, which is what makes it impossible for the two to disagree.
4910
+ *
4911
+ * Returns a new object — the input is never mutated.
4912
+ */
4913
+ function withLocationMode(location, mode) {
4914
+ return {
4915
+ ...location,
4916
+ mode,
4917
+ enabled: modeMayWrite(mode),
4918
+ config: strippedOfLegacyReadOnly(location.config)
4919
+ };
4920
+ }
4921
+ /**
4771
4922
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
4772
4923
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
4773
4924
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -4833,6 +4984,21 @@ var StorageLocationSchema = z.object({
4833
4984
  * stops existing rather than being re-derived on every read.
4834
4985
  */
4835
4986
  enabled: z.boolean().optional(),
4987
+ /**
4988
+ * THE state of this location (D385), and the only authority on what may be
4989
+ * written, read or evicted here. Interpreted in exactly one place —
4990
+ * `storage-location-mode.ts` — which also folds the legacy
4991
+ * `enabled` / `config.readOnly` pair into a mode so an old row is never
4992
+ * ambiguous.
4993
+ *
4994
+ * OPTIONAL only for the wire and for rows written before D385: absence is
4995
+ * resolved by `resolveLocationMode`, and the orchestrator stamps every
4996
+ * unstamped row ONCE at hydrate so absence stops existing rather than being
4997
+ * re-derived on every read. `enabled` survives one release as a DERIVED
4998
+ * mirror (`mode === 'active'`); `withLocationMode` is the only writer of
4999
+ * either, so the two cannot disagree.
5000
+ */
5001
+ mode: StorageLocationModeSchema.optional(),
4836
5002
  /** COMPUTED at read time by the orchestrator (statfs of the backing volume
4837
5003
  * for node-local locations it can reach) — never persisted, absent when the
4838
5004
  * volume is remote/unreachable. The single capacity truth every UI reads. */
@@ -4840,6 +5006,25 @@ var StorageLocationSchema = z.object({
4840
5006
  totalBytes: z.number(),
4841
5007
  availableBytes: z.number()
4842
5008
  }).nullable().optional(),
5009
+ /**
5010
+ * How much of that volume CamStack ITSELF holds on this location (D388) —
5011
+ * COMPUTED at read time from the `storage-occupancy` providers' own figures,
5012
+ * never persisted, never a filesystem walk.
5013
+ *
5014
+ * **ABSENT MEANS UNKNOWN, never zero.** No provider has reported for this
5015
+ * location yet — nobody stores here, the owning addon is down, or the first
5016
+ * refresh has not completed. A UI must omit the segment rather than draw it
5017
+ * at zero, which would claim we occupy nothing (D315). It is an OBJECT and
5018
+ * not a bare number precisely so that a `?? 0` on the consuming side has to
5019
+ * be spelled out loud instead of appearing by accident.
5020
+ *
5021
+ * `measuredAtMs` is the OLDEST contributing measurement, so it is honest
5022
+ * about the whole figure rather than about its freshest part.
5023
+ */
5024
+ owned: z.object({
5025
+ bytes: z.number().int().nonnegative(),
5026
+ measuredAtMs: z.number().int().nonnegative()
5027
+ }).optional(),
4843
5028
  createdAt: z.number(),
4844
5029
  updatedAt: z.number()
4845
5030
  });
@@ -4855,16 +5040,45 @@ var StorageLocationSchema = z.object({
4855
5040
  * the repo may read it; `scripts/check-no-storage-default.ts` enforces that.
4856
5041
  */
4857
5042
  var LegacyStorageLocationDefaultSchema = z.object({ isDefault: z.boolean().optional() });
5043
+ function isLocationEnabled(location) {
5044
+ return mayWriteToLocation({
5045
+ ...location,
5046
+ config: location.config ?? {}
5047
+ });
5048
+ }
4858
5049
  /**
4859
- * Is this location a write target? THE one place that decides what an absent
4860
- * `enabled` means on a persisted row — legacy rows predate the flag and were
4861
- * active, so absence is enabled. Every consumer asks here rather than spelling
4862
- * `enabled !== false` again, so the tri-state has exactly one interpretation.
5050
+ * Bytes in one GB, for every storage figure an operator types.
5051
+ *
5052
+ * BINARY (1024³), everywhere. The repo had both: the orchestrator's
5053
+ * `maxUsedGb` → bytes conversion used 1024³ while the recorder's placement
5054
+ * headroom used 10⁹ for the SAME stored key, so a location with a cap set was
5055
+ * silently 7.4% off depending on which side of the pipe asked. The persisted
5056
+ * values were entered against the binary unit and the admin UI reads it, so
5057
+ * that is the one that stays. Every GB knob — `maxUsedGb`, `minFreeGb` —
5058
+ * converts through {@link gbToBytes} and nowhere else.
4863
5059
  */
4864
- function isLocationEnabled(location) {
4865
- return location.enabled !== false;
5060
+ var STORAGE_BYTES_PER_GB = 1024 ** 3;
5061
+ /** GB → bytes, binary. Fractional GB is admitted and floored. */
5062
+ function gbToBytes(gb) {
5063
+ return Math.floor(gb * STORAGE_BYTES_PER_GB);
4866
5064
  }
4867
5065
  /**
5066
+ * How far a `drain` has got (D386) — the read a UI renders, and nothing more.
5067
+ *
5068
+ * `estimatedEmptyAtMs` is derived from the growth the ratchet has actually
5069
+ * OBSERVED and is `null` when it has observed none. Never a fabricated date: a
5070
+ * drain with no observed growth has no honest ETA, and inventing one is how an
5071
+ * operator learns not to believe the screen.
5072
+ */
5073
+ var StorageDrainProgressSchema = z.object({
5074
+ locationId: z.string(),
5075
+ startedAtMs: z.number(),
5076
+ startBytes: z.number(),
5077
+ bytesRemaining: z.number(),
5078
+ drained: z.boolean(),
5079
+ estimatedEmptyAtMs: z.number().nullable()
5080
+ });
5081
+ /**
4868
5082
  * Reference accepted by consumer-facing `api.storage.*` calls.
4869
5083
  * Either:
4870
5084
  * - a `StorageLocationType` (e.g. `'backups'`) → the sole location of that type
@@ -22143,6 +22357,12 @@ var storageCapability = {
22143
22357
  kind: "mutation",
22144
22358
  auth: "admin"
22145
22359
  }),
22360
+ /**
22361
+ * How far each draining location has got (D386). A pure READ of what the
22362
+ * last pressure sweep computed — it starts no work, and a location that is
22363
+ * not draining simply does not appear.
22364
+ */
22365
+ listDrainProgress: method(z.void(), z.array(StorageDrainProgressSchema).readonly()),
22146
22366
  testLocation: method(z.object({ id: z.string() }), z.object({
22147
22367
  ok: z.boolean(),
22148
22368
  error: z.string().optional()
@@ -22290,6 +22510,71 @@ var storageMigrationCapability = {
22290
22510
  history: method(z.object({}), z.array(StorageMigrationJobSchema).readonly(), { auth: "admin" })
22291
22511
  }
22292
22512
  };
22513
+ /**
22514
+ * `storage-occupancy` — how many bytes an addon actually HOLDS on a storage
22515
+ * location (D388).
22516
+ *
22517
+ * ## Why this is not `storage-evictable`
22518
+ *
22519
+ * `storage-evictable.getEvictableUsage` looks like the same question and is
22520
+ * not, in two ways that both matter and both bite hardest on the locations an
22521
+ * operator most wants a figure for:
22522
+ *
22523
+ * - it reports the whole eviction DOMAIN, not the location. `recordings:default`
22524
+ * and `recordingsLow:default` deliberately share one root and evict as one
22525
+ * oldest-first pool, so both answer with the SAME combined total. As an
22526
+ * occupancy figure that double-counts the disk.
22527
+ * - it reports ZERO for a location whose eviction policy is `never` (D385) —
22528
+ * a `readonly` or `disabled` disk. Those are exactly the disks an operator
22529
+ * is retiring and staring at.
22530
+ *
22531
+ * So this is its own contract with its own quantity, and the quantity is
22532
+ * OCCUPIED: every byte the addon holds on that location, whether or not it
22533
+ * would ever be willing to delete it. A provider that can only answer
22534
+ * "evictable" must not register here — a number that silently means different
22535
+ * things per class is worse than no number.
22536
+ *
22537
+ * ## Absence is an answer
22538
+ *
22539
+ * A location nobody reports for is UNKNOWN, never zero (D315). The orchestrator
22540
+ * stamps `StorageLocation.owned` only for locations it has a report for, and
22541
+ * the field is an OBJECT rather than a bare number so that a `?? 0` on the
22542
+ * consuming side has to be written out loud instead of appearing by accident.
22543
+ *
22544
+ * `internal: true` — consumed by the orchestrator's `listLocations` stamp, never
22545
+ * a public client surface. Clients read the stamped `StorageLocation.owned`.
22546
+ */
22547
+ /** One provider's occupancy answer for one location. */
22548
+ var StorageOccupancyReportSchema = z.object({
22549
+ locationId: z.string(),
22550
+ /** Bytes this provider holds on THAT location — not its eviction domain, and
22551
+ * not net of what it is willing to delete. */
22552
+ ownedBytes: z.number().int().nonnegative(),
22553
+ /** When the provider last actually measured this. The orchestrator carries it
22554
+ * through so a UI can say how old the figure is instead of implying "now". */
22555
+ measuredAtMs: z.number().int().nonnegative()
22556
+ });
22557
+ var storageOccupancyCapability = {
22558
+ name: "storage-occupancy",
22559
+ scope: "system",
22560
+ mode: "collection",
22561
+ internal: true,
22562
+ methods: {
22563
+ /**
22564
+ * Occupancy for the given locations, in ONE round trip.
22565
+ *
22566
+ * A provider answers only for the locations it actually holds bytes on and
22567
+ * OMITS the rest — an omitted location is "I hold nothing measurable here",
22568
+ * which the orchestrator merges as a contribution of nothing rather than as
22569
+ * a claim that the location is empty. Only a location no provider reports
22570
+ * at all stays unknown.
22571
+ *
22572
+ * This must be CHEAP and must never walk a filesystem: it is on the admin
22573
+ * UI's `listLocations` path. The owner keeps its own figure fresh (D224) and
22574
+ * answers from what it already has.
22575
+ */
22576
+ getOccupancy: method(z.object({ locationIds: z.array(z.string()).readonly() }), z.array(StorageOccupancyReportSchema).readonly(), { auth: "admin" }) }
22577
+ };
22293
22578
  var ProviderInfoSchema = z.discriminatedUnion("shouldSaveDiskSpace", [z.object({
22294
22579
  providerId: z.string().min(1),
22295
22580
  displayName: z.string().min(1),
@@ -24654,88 +24939,6 @@ onStatusChanged: { data: z.object({
24654
24939
  volatileStateFields: ["lastUpdated"]
24655
24940
  };
24656
24941
  /**
24657
- * Network-link snapshot. Same shape for every provider (a Reolink wifi
24658
- * camera, a Home Assistant device with a signal-strength sensor, a Tapo
24659
- * plug): one slice under `device.runtimeState['network-link']`, one badge,
24660
- * one Home Assistant projection.
24661
- */
24662
- var NetworkLinkStatusSchema = z.object({
24663
- /** The link the device is on. `'unknown'` = not read yet, not "no link". */
24664
- type: z.enum([
24665
- "wifi",
24666
- "ethernet",
24667
- "cellular",
24668
- "unknown"
24669
- ]),
24670
- /**
24671
- * Link quality, 0..100 inclusive, normalised by the provider from whatever
24672
- * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
24673
- * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
24674
- * one whose reading has not landed must not be drawn at 0 %. Consumers
24675
- * SKIP a null rather than coerce it.
24676
- */
24677
- signalPercent: z.number().min(0).max(100).nullable(),
24678
- /** Raw received signal strength in dBm, when the firmware reports one. */
24679
- rssiDbm: z.number().optional(),
24680
- /** Network name of a wireless link, when the firmware reports it. */
24681
- ssid: z.string().optional(),
24682
- /** Ms epoch of the last observation. Lets consumers reason about freshness. */
24683
- lastUpdated: z.number()
24684
- });
24685
- var networkLinkCapability = {
24686
- name: "network-link",
24687
- scope: "device",
24688
- deviceNative: true,
24689
- mode: "singleton",
24690
- deviceTypes: [
24691
- DeviceType.Camera,
24692
- DeviceType.Sensor,
24693
- DeviceType.Button,
24694
- DeviceType.Switch,
24695
- DeviceType.Light,
24696
- DeviceType.Lock,
24697
- DeviceType.Siren
24698
- ],
24699
- methods: {},
24700
- events: {
24701
- /**
24702
- * Emitted whenever the cached status changes (a link switch, a signal
24703
- * reading that moved). Mirrored on the parent chain by the
24704
- * DeviceEventPropagator like `battery.onStatusChanged`.
24705
- */
24706
- onStatusChanged: { data: z.object({
24707
- deviceId: z.number(),
24708
- status: NetworkLinkStatusSchema
24709
- }) } },
24710
- status: {
24711
- schema: NetworkLinkStatusSchema,
24712
- kind: "push",
24713
- empty: {
24714
- type: "unknown",
24715
- signalPercent: null,
24716
- lastUpdated: 0
24717
- }
24718
- },
24719
- /**
24720
- * Runtime-state slice — every provider stores the same shape under
24721
- * `device.runtimeState['network-link']`, read once by the badge and the
24722
- * Home Assistant projector regardless of the driver.
24723
- */
24724
- runtimeState: NetworkLinkStatusSchema,
24725
- /**
24726
- * Runtime-state durability: **restored** — a link reading is slow to
24727
- * change and a sleeping battery camera may not report for hours; the
24728
- * restored slice is what the badge shows until the next read.
24729
- *
24730
- * See `RuntimeStateDurability`. Enforced by
24731
- * `scripts/check-runtime-state-durability.ts`.
24732
- */
24733
- durability: "restored",
24734
- /** Clock fields: written, but excluded from the compare that decides
24735
- * whether persisting is worth a SQLite commit. */
24736
- volatileStateFields: ["lastUpdated"]
24737
- };
24738
- /**
24739
24942
  * Generic boolean sensor — last-resort fallback when no domain-
24740
24943
  * specific binary cap fits (Home Assistant `binary_sensor` without a
24741
24944
  * known `device_class`, or a domain we haven't typed yet). Pure
@@ -28613,6 +28816,371 @@ var nativeObjectDetectionCapability = {
28613
28816
  volatileStateFields: ["lastFetchedAt"]
28614
28817
  };
28615
28818
  /**
28819
+ * `navigation` — a device-scoped capability that natively expresses the FULL
28820
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
28821
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
28822
+ *
28823
+ * Why a NEW cap rather than overloading `ptz`:
28824
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
28825
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
28826
+ * The two are different physical models: PTZ is absolute-position + presets,
28827
+ * navigation is momentary drive nudges + discrete robot ACTIONS
28828
+ * (dock / spot-clean / follow-pet / go-to-point / …).
28829
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
28830
+ * the reverse:
28831
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
28832
+ * / `getOptions`), and
28833
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
28834
+ * robot camera shows up in the existing PTZ control path without every
28835
+ * PTZ provider learning about robots. The mapping lives in the adapter,
28836
+ * not here (see the addon design note):
28837
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
28838
+ * ptz.stop() → navigation.stop()
28839
+ * ptz.goHome() → navigation.runAction('goHome')
28840
+ * ptz.getPresets() → navigation.listActions() (id→preset)
28841
+ * ptz.goToPreset(id) → navigation.runAction(id)
28842
+ *
28843
+ * ## Continuous drive
28844
+ *
28845
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
28846
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
28847
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
28848
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
28849
+ * coalesce them. The UI owns the cadence.
28850
+ *
28851
+ * ## The action dictionary
28852
+ *
28853
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
28854
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
28855
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
28856
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
28857
+ * vendor-specific list. `kind: 'action'` entries are triggered with
28858
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
28859
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
28860
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
28861
+ *
28862
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
28863
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
28864
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
28865
+ * every device handle. A future nodedreame publish adds a typed
28866
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
28867
+ * provider can then swap the raw calls for the typed methods with no change to
28868
+ * THIS contract.
28869
+ */
28870
+ /**
28871
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
28872
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
28873
+ * halts it.
28874
+ *
28875
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
28876
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
28877
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
28878
+ * vector by it (drivers without proportional drive ignore it).
28879
+ *
28880
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
28881
+ * axis alone; an all-undefined nudge is a no-op.
28882
+ */
28883
+ var NavigationMoveCommandSchema = z.object({
28884
+ pan: z.number().min(-1).max(1).optional(),
28885
+ tilt: z.number().min(-1).max(1).optional(),
28886
+ speed: z.number().min(0).max(1).optional()
28887
+ });
28888
+ /**
28889
+ * The enumerated discrete actions a navigation-capable robot can perform via
28890
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
28891
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
28892
+ * `playSound` (see the `sound` dictionary entries).
28893
+ */
28894
+ var NavigationActionIdSchema = z.enum([
28895
+ "goHome",
28896
+ "locate",
28897
+ "spotClean",
28898
+ "findPet",
28899
+ "personFollow",
28900
+ "stop",
28901
+ "startClean",
28902
+ "pauseClean",
28903
+ "dockWash",
28904
+ "autoEmpty",
28905
+ "flashOn",
28906
+ "flashOff"
28907
+ ]);
28908
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
28909
+ var NavigationEntryKindSchema = z.enum(["action", "sound"]);
28910
+ /**
28911
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
28912
+ * native panel and the PTZ mimic render as a button.
28913
+ *
28914
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
28915
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
28916
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
28917
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
28918
+ * - `label` — operator-facing English label.
28919
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
28920
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
28921
+ * PTZ render ONLY enabled entries. Data-driven: the provider
28922
+ * flips it from config, never by editing code.
28923
+ */
28924
+ var NavigationActionEntrySchema = z.object({
28925
+ id: z.string(),
28926
+ kind: NavigationEntryKindSchema,
28927
+ label: z.string(),
28928
+ icon: z.string(),
28929
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
28930
+ soundId: z.number().int().optional(),
28931
+ /** Per-device feature flag — render this entry only when true. */
28932
+ enabled: z.boolean()
28933
+ });
28934
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
28935
+ var NavigationPointSchema = z.object({
28936
+ x: z.number(),
28937
+ y: z.number()
28938
+ });
28939
+ /**
28940
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
28941
+ * The cap reports which are enabled so the UI / PTZ render only the controls
28942
+ * that are turned on for THIS device. Data-driven: the provider derives these
28943
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
28944
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
28945
+ * that are not dictionary entries.
28946
+ *
28947
+ * - `move` / `stop` — the momentary drive joystick.
28948
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
28949
+ * map-coordinate plumbing is wired.
28950
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
28951
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
28952
+ * - `light` — the on/off fill-light toggle (works anytime).
28953
+ * - `lightMode` — the auto/manual selector + manual level slider (a
28954
+ * camera-service control; needs an active stream).
28955
+ */
28956
+ var NavigationFeaturesSchema = z.object({
28957
+ move: z.boolean(),
28958
+ stop: z.boolean(),
28959
+ goToPoint: z.boolean(),
28960
+ runAction: z.boolean(),
28961
+ playSound: z.boolean(),
28962
+ light: z.boolean(),
28963
+ lightMode: z.boolean()
28964
+ });
28965
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
28966
+ var NavigationLightModeSchema = z.enum(["auto", "manual"]);
28967
+ /** Coarse work mode the robot reports — drives the UI's active-state chips. */
28968
+ var NavigationModeSchema = z.enum([
28969
+ "idle",
28970
+ "cleaning",
28971
+ "spot",
28972
+ "following",
28973
+ "goto",
28974
+ "returning",
28975
+ "paused",
28976
+ "unknown"
28977
+ ]);
28978
+ /**
28979
+ * Live navigation state so the UI can reflect what the robot is doing:
28980
+ * - `mode` — coarse activity (idle / cleaning / following / …).
28981
+ * - `following` — person/pet follow is currently armed.
28982
+ * - `flash` — the on-camera fill light is on.
28983
+ * - `lightMode` — auto vs manual fill-light mode.
28984
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
28985
+ * `lightMode === 'manual'`.
28986
+ */
28987
+ var NavigationStatusSchema = z.object({
28988
+ mode: NavigationModeSchema,
28989
+ following: z.boolean(),
28990
+ flash: z.boolean(),
28991
+ lightMode: NavigationLightModeSchema,
28992
+ lightLevel: z.number().min(40).max(100),
28993
+ /** Ms epoch when the slice was last updated. */
28994
+ lastChangedAt: z.number()
28995
+ });
28996
+ /**
28997
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
28998
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
28999
+ * convention.
29000
+ */
29001
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: z.number() });
29002
+ var navigationCapability = {
29003
+ name: "navigation",
29004
+ scope: "device",
29005
+ deviceNative: true,
29006
+ mode: "singleton",
29007
+ deviceTypes: [DeviceType.Camera],
29008
+ deviceConfig: { ui: {
29009
+ kind: "widget",
29010
+ widgetId: "host/navigation-panel",
29011
+ tab: "navigation",
29012
+ topTab: true,
29013
+ label: "Navigation",
29014
+ order: 0
29015
+ } },
29016
+ methods: {
29017
+ /**
29018
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29019
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29020
+ * path) works for any authenticated user, not admin-only. The UI sends
29021
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29022
+ * a single drive write WITHOUT debouncing.
29023
+ */
29024
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: z.number() }), z.void(), { kind: "mutation" }),
29025
+ /** Halt all motion immediately (zero drive vector). */
29026
+ stop: method(z.object({ deviceId: z.number() }), z.void(), { kind: "mutation" }),
29027
+ /** Send the robot to a point on its live map. */
29028
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: z.number() }), z.void(), { kind: "mutation" }),
29029
+ /**
29030
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29031
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29032
+ */
29033
+ listActions: method(z.object({ deviceId: z.number() }), z.array(NavigationActionEntrySchema)),
29034
+ /**
29035
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29036
+ * unsupported action ids are rejected by the provider.
29037
+ */
29038
+ runAction: method(z.object({
29039
+ deviceId: z.number(),
29040
+ actionId: NavigationActionIdSchema
29041
+ }), z.void(), { kind: "mutation" }),
29042
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29043
+ playSound: method(z.object({
29044
+ deviceId: z.number(),
29045
+ soundId: z.number().int()
29046
+ }), z.void(), { kind: "mutation" }),
29047
+ /**
29048
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29049
+ * works anytime, no active stream required).
29050
+ */
29051
+ setLightOn: method(z.object({
29052
+ deviceId: z.number(),
29053
+ on: z.boolean()
29054
+ }), z.void(), { kind: "mutation" }),
29055
+ /**
29056
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29057
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29058
+ * action that generally needs an active camera stream/monitor session — the
29059
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29060
+ */
29061
+ setLightMode: method(z.object({
29062
+ deviceId: z.number(),
29063
+ mode: NavigationLightModeSchema,
29064
+ level: z.number().min(40).max(100).optional()
29065
+ }), z.void(), { kind: "mutation" }),
29066
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29067
+ setLightLevel: method(z.object({
29068
+ deviceId: z.number(),
29069
+ level: z.number().min(40).max(100)
29070
+ }), z.void(), { kind: "mutation" }),
29071
+ /**
29072
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29073
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29074
+ * `listActions`).
29075
+ */
29076
+ getFeatures: method(z.object({ deviceId: z.number() }), NavigationFeaturesSchema)
29077
+ },
29078
+ events: { onStatusChanged: { data: z.object({
29079
+ deviceId: z.number(),
29080
+ status: NavigationStatusSchema
29081
+ }) } },
29082
+ status: {
29083
+ schema: NavigationStatusSchema,
29084
+ kind: "push"
29085
+ },
29086
+ /**
29087
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29088
+ * for live mode / follow / flash changes.
29089
+ */
29090
+ runtimeState: NavigationRuntimeStateSchema,
29091
+ /**
29092
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29093
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29094
+ * that. The live handle re-publishes on connect.
29095
+ *
29096
+ * See `RuntimeStateDurability`. Enforced by
29097
+ * `scripts/check-runtime-state-durability.ts`.
29098
+ */
29099
+ durability: "session"
29100
+ };
29101
+ /**
29102
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
29103
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
29104
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
29105
+ * one Home Assistant projection.
29106
+ */
29107
+ var NetworkLinkStatusSchema = z.object({
29108
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
29109
+ type: z.enum([
29110
+ "wifi",
29111
+ "ethernet",
29112
+ "cellular",
29113
+ "unknown"
29114
+ ]),
29115
+ /**
29116
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
29117
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
29118
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
29119
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
29120
+ * SKIP a null rather than coerce it.
29121
+ */
29122
+ signalPercent: z.number().min(0).max(100).nullable(),
29123
+ /** Raw received signal strength in dBm, when the firmware reports one. */
29124
+ rssiDbm: z.number().optional(),
29125
+ /** Network name of a wireless link, when the firmware reports it. */
29126
+ ssid: z.string().optional(),
29127
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
29128
+ lastUpdated: z.number()
29129
+ });
29130
+ var networkLinkCapability = {
29131
+ name: "network-link",
29132
+ scope: "device",
29133
+ deviceNative: true,
29134
+ mode: "singleton",
29135
+ deviceTypes: [
29136
+ DeviceType.Camera,
29137
+ DeviceType.Sensor,
29138
+ DeviceType.Button,
29139
+ DeviceType.Switch,
29140
+ DeviceType.Light,
29141
+ DeviceType.Lock,
29142
+ DeviceType.Siren
29143
+ ],
29144
+ methods: {},
29145
+ events: {
29146
+ /**
29147
+ * Emitted whenever the cached status changes (a link switch, a signal
29148
+ * reading that moved). Mirrored on the parent chain by the
29149
+ * DeviceEventPropagator like `battery.onStatusChanged`.
29150
+ */
29151
+ onStatusChanged: { data: z.object({
29152
+ deviceId: z.number(),
29153
+ status: NetworkLinkStatusSchema
29154
+ }) } },
29155
+ status: {
29156
+ schema: NetworkLinkStatusSchema,
29157
+ kind: "push",
29158
+ empty: {
29159
+ type: "unknown",
29160
+ signalPercent: null,
29161
+ lastUpdated: 0
29162
+ }
29163
+ },
29164
+ /**
29165
+ * Runtime-state slice — every provider stores the same shape under
29166
+ * `device.runtimeState['network-link']`, read once by the badge and the
29167
+ * Home Assistant projector regardless of the driver.
29168
+ */
29169
+ runtimeState: NetworkLinkStatusSchema,
29170
+ /**
29171
+ * Runtime-state durability: **restored** — a link reading is slow to
29172
+ * change and a sleeping battery camera may not report for hours; the
29173
+ * restored slice is what the badge shows until the next read.
29174
+ *
29175
+ * See `RuntimeStateDurability`. Enforced by
29176
+ * `scripts/check-runtime-state-durability.ts`.
29177
+ */
29178
+ durability: "restored",
29179
+ /** Clock fields: written, but excluded from the compare that decides
29180
+ * whether persisting is worth a SQLite commit. */
29181
+ volatileStateFields: ["lastUpdated"]
29182
+ };
29183
+ /**
28616
29184
  * network-quality — system-scoped singleton capability tracking RTT,
28617
29185
  * jitter, and observed/peak bandwidth per device + per client.
28618
29186
  *
@@ -30463,289 +31031,6 @@ var ptzAutotrackCapability = {
30463
31031
  durability: "session"
30464
31032
  };
30465
31033
  /**
30466
- * `navigation` — a device-scoped capability that natively expresses the FULL
30467
- * navigation / action surface of a robot that DRIVES ITSELF and carries an
30468
- * on-board camera (the Dreame robot-vacuum camera is the first provider).
30469
- *
30470
- * Why a NEW cap rather than overloading `ptz`:
30471
- * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
30472
- * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
30473
- * The two are different physical models: PTZ is absolute-position + presets,
30474
- * navigation is momentary drive nudges + discrete robot ACTIONS
30475
- * (dock / spot-clean / follow-pet / go-to-point / …).
30476
- * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
30477
- * the reverse:
30478
- * 1. a native CamStack navigation panel (data-driven from `listActions`
30479
- * / `getOptions`), and
30480
- * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
30481
- * robot camera shows up in the existing PTZ control path without every
30482
- * PTZ provider learning about robots. The mapping lives in the adapter,
30483
- * not here (see the addon design note):
30484
- * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
30485
- * ptz.stop() → navigation.stop()
30486
- * ptz.goHome() → navigation.runAction('goHome')
30487
- * ptz.getPresets() → navigation.listActions() (id→preset)
30488
- * ptz.goToPreset(id) → navigation.runAction(id)
30489
- *
30490
- * ## Continuous drive
30491
- *
30492
- * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
30493
- * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
30494
- * one `stop()` on release — exactly like the robot app's remote-drive joystick.
30495
- * The provider forwards EACH `move` to one drive write; it must NOT debounce or
30496
- * coalesce them. The UI owns the cadence.
30497
- *
30498
- * ## The action dictionary
30499
- *
30500
- * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
30501
- * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
30502
- * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
30503
- * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
30504
- * vendor-specific list. `kind: 'action'` entries are triggered with
30505
- * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
30506
- * (the entry carries the `soundId` to pass). The general primitives — `move`,
30507
- * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
30508
- *
30509
- * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
30510
- * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
30511
- * that the currently-published `@apocaliss92/nodedreame` already exposes on
30512
- * every device handle. A future nodedreame publish adds a typed
30513
- * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
30514
- * provider can then swap the raw calls for the typed methods with no change to
30515
- * THIS contract.
30516
- */
30517
- /**
30518
- * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
30519
- * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
30520
- * halts it.
30521
- *
30522
- * - `pan` — turn: negative = left, positive = right, 0 = straight.
30523
- * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
30524
- * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
30525
- * vector by it (drivers without proportional drive ignore it).
30526
- *
30527
- * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
30528
- * axis alone; an all-undefined nudge is a no-op.
30529
- */
30530
- var NavigationMoveCommandSchema = z.object({
30531
- pan: z.number().min(-1).max(1).optional(),
30532
- tilt: z.number().min(-1).max(1).optional(),
30533
- speed: z.number().min(0).max(1).optional()
30534
- });
30535
- /**
30536
- * The enumerated discrete actions a navigation-capable robot can perform via
30537
- * `runAction`. This is the CLOSED vocabulary; a given device advertises the
30538
- * subset it supports through `listActions`. Sounds are NOT here — they go through
30539
- * `playSound` (see the `sound` dictionary entries).
30540
- */
30541
- var NavigationActionIdSchema = z.enum([
30542
- "goHome",
30543
- "locate",
30544
- "spotClean",
30545
- "findPet",
30546
- "personFollow",
30547
- "stop",
30548
- "startClean",
30549
- "pauseClean",
30550
- "dockWash",
30551
- "autoEmpty",
30552
- "flashOn",
30553
- "flashOff"
30554
- ]);
30555
- /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
30556
- var NavigationEntryKindSchema = z.enum(["action", "sound"]);
30557
- /**
30558
- * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
30559
- * native panel and the PTZ mimic render as a button.
30560
- *
30561
- * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
30562
- * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
30563
- * (`sound:meow`) whose `soundId` is passed to `playSound`.
30564
- * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
30565
- * - `label` — operator-facing English label.
30566
- * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
30567
- * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
30568
- * PTZ render ONLY enabled entries. Data-driven: the provider
30569
- * flips it from config, never by editing code.
30570
- */
30571
- var NavigationActionEntrySchema = z.object({
30572
- id: z.string(),
30573
- kind: NavigationEntryKindSchema,
30574
- label: z.string(),
30575
- icon: z.string(),
30576
- /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
30577
- soundId: z.number().int().optional(),
30578
- /** Per-device feature flag — render this entry only when true. */
30579
- enabled: z.boolean()
30580
- });
30581
- /** Coordinates for `goToPoint` — a point on the robot's live map. */
30582
- var NavigationPointSchema = z.object({
30583
- x: z.number(),
30584
- y: z.number()
30585
- });
30586
- /**
30587
- * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
30588
- * The cap reports which are enabled so the UI / PTZ render only the controls
30589
- * that are turned on for THIS device. Data-driven: the provider derives these
30590
- * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
30591
- * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
30592
- * that are not dictionary entries.
30593
- *
30594
- * - `move` / `stop` — the momentary drive joystick.
30595
- * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
30596
- * map-coordinate plumbing is wired.
30597
- * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
30598
- * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
30599
- * - `light` — the on/off fill-light toggle (works anytime).
30600
- * - `lightMode` — the auto/manual selector + manual level slider (a
30601
- * camera-service control; needs an active stream).
30602
- */
30603
- var NavigationFeaturesSchema = z.object({
30604
- move: z.boolean(),
30605
- stop: z.boolean(),
30606
- goToPoint: z.boolean(),
30607
- runAction: z.boolean(),
30608
- playSound: z.boolean(),
30609
- light: z.boolean(),
30610
- lightMode: z.boolean()
30611
- });
30612
- /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
30613
- var NavigationLightModeSchema = z.enum(["auto", "manual"]);
30614
- /** Coarse work mode the robot reports — drives the UI's active-state chips. */
30615
- var NavigationModeSchema = z.enum([
30616
- "idle",
30617
- "cleaning",
30618
- "spot",
30619
- "following",
30620
- "goto",
30621
- "returning",
30622
- "paused",
30623
- "unknown"
30624
- ]);
30625
- /**
30626
- * Live navigation state so the UI can reflect what the robot is doing:
30627
- * - `mode` — coarse activity (idle / cleaning / following / …).
30628
- * - `following` — person/pet follow is currently armed.
30629
- * - `flash` — the on-camera fill light is on.
30630
- * - `lightMode` — auto vs manual fill-light mode.
30631
- * - `lightLevel` — manual fill-light level (40..100); meaningful when
30632
- * `lightMode === 'manual'`.
30633
- */
30634
- var NavigationStatusSchema = z.object({
30635
- mode: NavigationModeSchema,
30636
- following: z.boolean(),
30637
- flash: z.boolean(),
30638
- lightMode: NavigationLightModeSchema,
30639
- lightLevel: z.number().min(40).max(100),
30640
- /** Ms epoch when the slice was last updated. */
30641
- lastChangedAt: z.number()
30642
- });
30643
- /**
30644
- * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
30645
- * observable). Adds `lastFetchedAt` on top of the status shape per the
30646
- * convention.
30647
- */
30648
- var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: z.number() });
30649
- var navigationCapability = {
30650
- name: "navigation",
30651
- scope: "device",
30652
- deviceNative: true,
30653
- mode: "singleton",
30654
- deviceTypes: [DeviceType.Camera],
30655
- deviceConfig: { ui: {
30656
- kind: "widget",
30657
- widgetId: "host/navigation-panel",
30658
- tab: "navigation",
30659
- topTab: true,
30660
- label: "Navigation",
30661
- order: 0
30662
- } },
30663
- methods: {
30664
- /**
30665
- * Momentary drive nudge (the robot moves). `protected` — mirrors
30666
- * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
30667
- * path) works for any authenticated user, not admin-only. The UI sends
30668
- * these at ~1 Hz while a control is held; the provider forwards each one to
30669
- * a single drive write WITHOUT debouncing.
30670
- */
30671
- move: method(NavigationMoveCommandSchema.extend({ deviceId: z.number() }), z.void(), { kind: "mutation" }),
30672
- /** Halt all motion immediately (zero drive vector). */
30673
- stop: method(z.object({ deviceId: z.number() }), z.void(), { kind: "mutation" }),
30674
- /** Send the robot to a point on its live map. */
30675
- goToPoint: method(NavigationPointSchema.extend({ deviceId: z.number() }), z.void(), { kind: "mutation" }),
30676
- /**
30677
- * Enumerate the discrete controls THIS device supports (data-driven UI +
30678
- * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30679
- */
30680
- listActions: method(z.object({ deviceId: z.number() }), z.array(NavigationActionEntrySchema)),
30681
- /**
30682
- * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30683
- * unsupported action ids are rejected by the provider.
30684
- */
30685
- runAction: method(z.object({
30686
- deviceId: z.number(),
30687
- actionId: NavigationActionIdSchema
30688
- }), z.void(), { kind: "mutation" }),
30689
- /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30690
- playSound: method(z.object({
30691
- deviceId: z.number(),
30692
- soundId: z.number().int()
30693
- }), z.void(), { kind: "mutation" }),
30694
- /**
30695
- * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30696
- * works anytime, no active stream required).
30697
- */
30698
- setLightOn: method(z.object({
30699
- deviceId: z.number(),
30700
- on: z.boolean()
30701
- }), z.void(), { kind: "mutation" }),
30702
- /**
30703
- * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30704
- * initial `level`. The auto/manual + level control is a CAMERA-service
30705
- * action that generally needs an active camera stream/monitor session — the
30706
- * UI shows the manual level slider ONLY when `mode === 'manual'`.
30707
- */
30708
- setLightMode: method(z.object({
30709
- deviceId: z.number(),
30710
- mode: NavigationLightModeSchema,
30711
- level: z.number().min(40).max(100).optional()
30712
- }), z.void(), { kind: "mutation" }),
30713
- /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30714
- setLightLevel: method(z.object({
30715
- deviceId: z.number(),
30716
- level: z.number().min(40).max(100)
30717
- }), z.void(), { kind: "mutation" }),
30718
- /**
30719
- * Per-device FEATURE-FLAG report for the general primitives — drives which
30720
- * controls the UI shows (the per-entry flags for the dictionary come back on
30721
- * `listActions`).
30722
- */
30723
- getFeatures: method(z.object({ deviceId: z.number() }), NavigationFeaturesSchema)
30724
- },
30725
- events: { onStatusChanged: { data: z.object({
30726
- deviceId: z.number(),
30727
- status: NavigationStatusSchema
30728
- }) } },
30729
- status: {
30730
- schema: NavigationStatusSchema,
30731
- kind: "push"
30732
- },
30733
- /**
30734
- * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30735
- * for live mode / follow / flash changes.
30736
- */
30737
- runtimeState: NavigationRuntimeStateSchema,
30738
- /**
30739
- * Runtime-state durability: **session** — like `vacuum-control`, a restored
30740
- * `mode: cleaning` / `following: true` is a robot that is not actually doing
30741
- * that. The live handle re-publishes on connect.
30742
- *
30743
- * See `RuntimeStateDurability`. Enforced by
30744
- * `scripts/check-runtime-state-durability.ts`.
30745
- */
30746
- durability: "session"
30747
- };
30748
- /**
30749
31034
  * reboot — device-scoped capability for "soft" device reboots (firmware
30750
31035
  * reboot via vendor protocol; cameras, NVRs, doorbells). Surfaces a
30751
31036
  * single mutation so the UI can offer a confirm-and-reboot button for
@@ -35326,6 +35611,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
35326
35611
  storageCapability,
35327
35612
  storageEvictableCapability,
35328
35613
  storageMigrationCapability,
35614
+ storageOccupancyCapability,
35329
35615
  storageProviderCapability,
35330
35616
  streamBrokerCapability,
35331
35617
  streamCatalogCapability,
@@ -40298,6 +40584,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
40298
40584
  addonId: null,
40299
40585
  access: "view"
40300
40586
  },
40587
+ "storage.listDrainProgress": {
40588
+ capName: "storage",
40589
+ capScope: "system",
40590
+ addonId: null,
40591
+ access: "view"
40592
+ },
40301
40593
  "storage.listLocationDeclarations": {
40302
40594
  capName: "storage",
40303
40595
  capScope: "system",
@@ -40442,6 +40734,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
40442
40734
  addonId: null,
40443
40735
  access: "view"
40444
40736
  },
40737
+ "storageOccupancy.getOccupancy": {
40738
+ capName: "storage-occupancy",
40739
+ capScope: "system",
40740
+ addonId: null,
40741
+ access: "view"
40742
+ },
40445
40743
  "storageProvider.abortUpload": {
40446
40744
  capName: "storage-provider",
40447
40745
  capScope: "system",
@@ -44282,4 +44580,4 @@ function enumerateInferenceDevices(hw) {
44282
44580
  return out;
44283
44581
  }
44284
44582
  //#endregion
44285
- export { filesystemBrowseCapability as $, emitReadiness as $t, addonWidgetsCapability as A, storageCapability as At, coreBlocksCapability as B, DEVICE_SETTINGS_CONTRIBUTION_METHODS as Bt, ScopedTokenSchema as C, resolveMethodAuth as Ct, UserRecordSchema as D, scoreRuntimes as Dt, StorageMigrationJobSchema as E, scopesAllowDeviceCap as Et, batteryCapability as F, vectorDimFromBase64 as Ft, deviceStateCapability as G, ReadinessRegistry as Gt, decodeVectorBase64 as H, DeviceFeature as Ht, buildStreamParamsConfigSchema as I, vectorStoreCapability as It, enumerateInferenceDevices as J, asJsonObject as Jt, deviceStatusCapability as K, ReadinessTimeoutError as Kt, collectSecretConfigKeys as L, errMsg as Lt, authProviderCapability as M, storageProviderCapability as Mt, backupCapability as N, streamQualityLabel as Nt, addonPagesCapability as O, settingsStoreCapability as Ot, bareAddonId as P, userManagementCapability as Pt, extractNestedAddonId as Q, emitDownForOwnedCaps as Qt, coreBlockAddonId as R, BaseAddon as Rt, STREAM_PROFILE_META as S, resolveContainerPrimaryChild as St, StorageLocationTypeSchema as T, scopesAllowAddon as Tt, deriveBatteryPresence as U, DeviceRole as Ut, dataStoreProviderCapability as V, DEVICE_STATUS_METHOD as Vt, deviceManagerCapability as W, DeviceType as Wt, enumerateSchemaFields as X, asString as Xt, enumerateItemArrayFields as Y, asNumber as Yt, evaluateSensorEdge as Z, createEvent as Zt, RUNTIME_DEFAULTS as _, parseStreamParamsFormPatch as _t, CAMERA_SWITCH_ORDER as a, parseJsonUnknown as an, isSameAddonId as at, SOURCE_DEVICE_TYPES as b, reducePoints as bt, CORE_BLOCK_ADDON_PREFIX as c, scopeKey as cn, kebabToCamel as ct, DeclaredDevices as d, logDestinationCapability as dt, expandCapMethods as en, foldSnapshotByFunction as et, DeviceStatusSchema as f, logLevelAtMost as ft, REDACTED_SECRET as g, objectInputDeclaresAddonId as gt, METHOD_ACCESS_MAP as h, normalizeUnit as ht, BatteryStatusSchema as i, parseJsonObject as in, isObjectInput as it, alertsCapability as j, storageMigrationCapability as jt, addonSettingsCapability as k, snapshotCapability as kt, CoreBlockSchema as l, sleep as ln, lifecycleJobSchema as lt, LegacyStorageLocationDefaultSchema as m, metricsProviderCapability as mt, AlertSchema as n, isDeviceConfigCap as nn, isCollectionArrayMethod as nt, CAP_NAMES_WITH_STATUS as o, readinessKey as on, isSourceCap as ot, LOG_LEVEL_RANK as p, looseSchema as pt, doorbellCapability as q, WELL_KNOWN_TAB_MAP as qt, ApiKeyRecordSchema as r, nodePin as rn, isLocationEnabled as rt, CORE_BLOCKS_ADDON_ID as s, resolveCapMount as sn, isVoidInput as st, ALL_CAPABILITY_DEFINITIONS as t, hydrateSchema as tn, isArrayOutputSchema as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, EventCategory as un, localNetworkCapability as ut, RecordingStatusSchema as v, platformProbeCapability as vt, StorageCleanupJobSchema as w, runtimeStatePolicyFor as wt, STORAGE_ACCESS_FALLBACK as x, resolveBucketMs as xt, SOURCE_CAPS as y, procedureAuthKey as yt, coreBlockIdFromAddonId as z, DATAPLANE_SECRET_HEADER as zt };
44583
+ export { evictionPolicyOfLocation as $, DeviceRole as $t, addonSettingsCapability as A, resolveLocationMode as At, coreBlockIdFromAddonId as B, storageOccupancyCapability as Bt, ScopedTokenSchema as C, objectInputDeclaresAddonId as Ct, StorageMigrationJobSchema as D, reducePoints as Dt, StorageLocationTypeSchema as E, procedureAuthKey as Et, bareAddonId as F, scoreRuntimes as Ft, deviceManagerCapability as G, vectorStoreCapability as Gt, dataStoreProviderCapability as H, streamQualityLabel as Ht, batteryCapability as I, settingsStoreCapability as It, doorbellCapability as J, BaseAddon as Jt, deviceStateCapability as K, withLocationMode as Kt, buildStreamParamsConfigSchema as L, snapshotCapability as Lt, alertsCapability as M, runtimeStatePolicyFor as Mt, authProviderCapability as N, scopesAllowAddon as Nt, UserRecordSchema as O, resolveBucketMs as Ot, backupCapability as P, scopesAllowDeviceCap as Pt, evaluateSensorEdge as Q, DeviceFeature as Qt, collectSecretConfigKeys as R, storageCapability as Rt, STREAM_PROFILE_META as S, normalizeUnit as St, StorageLocationModeSchema as T, platformProbeCapability as Tt, decodeVectorBase64 as U, userManagementCapability as Ut, coreBlocksCapability as V, storageProviderCapability as Vt, deriveBatteryPresence as W, vectorDimFromBase64 as Wt, enumerateItemArrayFields as X, DEVICE_SETTINGS_CONTRIBUTION_METHODS as Xt, enumerateInferenceDevices as Y, DATAPLANE_SECRET_HEADER as Yt, enumerateSchemaFields as Z, DEVICE_STATUS_METHOD as Zt, RUNTIME_DEFAULTS as _, resolveCapMount as _n, looseSchema as _t, CAMERA_SWITCH_ORDER as a, asNumber as an, isCollectionArrayMethod as at, SOURCE_DEVICE_TYPES as b, EventCategory as bn, metricsProviderCapability as bt, CORE_BLOCK_ADDON_PREFIX as c, emitDownForOwnedCaps as cn, isSameAddonId as ct, DeclaredDevices as d, hydrateSchema as dn, kebabToCamel as dt, DeviceType as en, extractNestedAddonId as et, DeviceStatusSchema as f, isDeviceConfigCap as fn, legacyModeOf as ft, REDACTED_SECRET as g, readinessKey as gn, logLevelAtMost as gt, METHOD_ACCESS_MAP as h, parseJsonUnknown as hn, logDestinationCapability as ht, BatteryStatusSchema as i, asJsonObject as in, isArrayOutputSchema as it, addonWidgetsCapability as j, resolveMethodAuth as jt, addonPagesCapability as k, resolveContainerPrimaryChild as kt, CoreBlockSchema as l, emitReadiness as ln, isSourceCap as lt, LegacyStorageLocationDefaultSchema as m, parseJsonObject as mn, localNetworkCapability as mt, AlertSchema as n, ReadinessTimeoutError as nn, foldSnapshotByFunction as nt, CAP_NAMES_WITH_STATUS as o, asString as on, isLocationEnabled as ot, LOG_LEVEL_RANK as p, nodePin as pn, lifecycleJobSchema as pt, deviceStatusCapability as q, errMsg as qt, ApiKeyRecordSchema as r, WELL_KNOWN_TAB_MAP as rn, gbToBytes as rt, CORE_BLOCKS_ADDON_ID as s, createEvent as sn, isObjectInput as st, ALL_CAPABILITY_DEFINITIONS as t, ReadinessRegistry as tn, filesystemBrowseCapability as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, expandCapMethods as un, isVoidInput as ut, RecordingStatusSchema as v, scopeKey as vn, mayReadLocation as vt, StorageCleanupJobSchema as w, parseStreamParamsFormPatch as wt, STORAGE_ACCESS_FALLBACK as x, modeMayWrite as xt, SOURCE_CAPS as y, sleep as yn, mayWriteToLocation as yt, coreBlockAddonId as z, storageMigrationCapability as zt };