@camstack/addon-pipeline-orchestrator 1.1.19 → 1.1.20

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
@@ -4631,7 +4631,7 @@ function _instanceof(cls, params = {}) {
4631
4631
  return inst;
4632
4632
  }
4633
4633
  //#endregion
4634
- //#region ../types/dist/sleep-BO1nweKv.mjs
4634
+ //#region ../types/dist/sleep-BiDFW0E7.mjs
4635
4635
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4636
4636
  EventCategory["SystemBoot"] = "system.boot";
4637
4637
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5460,6 +5460,100 @@ function createDurableState(deps) {
5460
5460
  };
5461
5461
  }
5462
5462
  /**
5463
+ * Per-node scoping for the shared addon-settings blob.
5464
+ *
5465
+ * An addon's settings store is hub-central (the `addon-settings` cap is
5466
+ * hub-routed — the hub instance answers for every node), so fields whose
5467
+ * value is a NODE fact (decoder backend, engine pick, probed hardware
5468
+ * capabilities) persist under the node-scoped key `<key>@<nodeId>` inside
5469
+ * the single shared blob. The SCHEMA field key stays bare: the write path
5470
+ * ({@link scopePatch}) maps the bare field onto the target node's scoped
5471
+ * key; the read/hydrate path ({@link projectStore}) maps THIS node's value
5472
+ * back, so UI forms and `resolveConfig` only ever see the bare key.
5473
+ *
5474
+ * Fields opt in via the `perNode: true` schema marker on `ConfigFieldBase`
5475
+ * (`interfaces/config-ui.ts`); `BaseAddon` derives the key set from the
5476
+ * schema and routes reads/writes through these helpers.
5477
+ *
5478
+ * ## No bare-key fallback — deliberate
5479
+ *
5480
+ * {@link readNodeValue} reads the node-scoped key ONLY. A node with no
5481
+ * scoped key resolves to `undefined` so the field's schema `default` wins —
5482
+ * NEVER `store[base]` and never another node's value. A bare legacy key in
5483
+ * the store is invisible to every node, hub included, so one node's
5484
+ * selection can never leak onto another. (This generalizes the
5485
+ * `decoder-backend-keys.ts` semantics — the no-fallback variant — over an
5486
+ * arbitrary set of per-node field keys.)
5487
+ *
5488
+ * Pure functions only — no I/O, no imports beyond the language. This is a
5489
+ * LEAF module: import it via its deep path, never from the root barrel.
5490
+ */
5491
+ /**
5492
+ * Normalize a raw kernel node id to the bare node id used for scoping.
5493
+ * `localNodeId` can carry a `<node>/<addon>` suffix on forked child
5494
+ * processes; per-node settings are per-NODE, so strip the addon segment.
5495
+ * `undefined` / `null` / empty falls back to `'hub'`.
5496
+ */
5497
+ function normalizeNodeId(raw) {
5498
+ if (raw === void 0 || raw === null || raw === "") return "hub";
5499
+ const slashIdx = raw.indexOf("/");
5500
+ if (slashIdx < 0) return raw;
5501
+ const bare = raw.slice(0, slashIdx);
5502
+ return bare === "" ? "hub" : bare;
5503
+ }
5504
+ /** The node-scoped store key for a per-node field: `<base>@<nodeId>`. */
5505
+ function nodeScopedKey(base, nodeId) {
5506
+ return `${base}@${normalizeNodeId(nodeId)}`;
5507
+ }
5508
+ /**
5509
+ * Read a node's value for a per-node field from the raw shared store:
5510
+ * the node-scoped key when present, otherwise `undefined`.
5511
+ * Deliberately NO bare-key fallback (see module doc) — the caller lets the
5512
+ * schema `default` win on `undefined`.
5513
+ */
5514
+ function readNodeValue(store, base, nodeId) {
5515
+ return store[nodeScopedKey(base, nodeId)];
5516
+ }
5517
+ /**
5518
+ * Re-map a UI/settings patch so every bare perNode field persists under the
5519
+ * TARGET node's scoped key; all other keys pass through unchanged. Used on
5520
+ * the write path so a save for one node never clobbers another node's value
5521
+ * (and the bare key is never written). Returns a new object — the input
5522
+ * patch is not mutated.
5523
+ */
5524
+ function scopePatch(patch, perNodeKeys, nodeId) {
5525
+ const out = {};
5526
+ for (const [key, value] of Object.entries(patch)) out[perNodeKeys.has(key) ? nodeScopedKey(key, nodeId) : key] = value;
5527
+ return out;
5528
+ }
5529
+ /**
5530
+ * Project the raw shared store onto the bare schema keys for ONE node so a
5531
+ * UI schema (whose field keys are bare) hydrates from that node's own
5532
+ * values:
5533
+ *
5534
+ * - EVERY `@`-scoped key (any node's) is dropped from the pass-through.
5535
+ * - Every bare perNode key is dropped from the pass-through (a stray bare
5536
+ * legacy key must never hydrate any node — no bare fallback).
5537
+ * - THIS node's effective value ({@link readNodeValue}) is then laid onto
5538
+ * each bare perNode key; when the node has no scoped key the bare key is
5539
+ * left ABSENT so the field's schema `default` wins.
5540
+ *
5541
+ * Returns a new object — the input store is not mutated.
5542
+ */
5543
+ function projectStore(store, perNodeKeys, nodeId) {
5544
+ const out = {};
5545
+ for (const [key, value] of Object.entries(store)) {
5546
+ if (key.includes("@")) continue;
5547
+ if (perNodeKeys.has(key)) continue;
5548
+ out[key] = value;
5549
+ }
5550
+ for (const base of perNodeKeys) {
5551
+ const value = readNodeValue(store, base, nodeId);
5552
+ if (value !== void 0) out[base] = value;
5553
+ }
5554
+ return out;
5555
+ }
5556
+ /**
5463
5557
  * Base class for CamStack addons. Eliminates settings boilerplate:
5464
5558
  *
5465
5559
  * - Typed `config` property with automatic resolution from store + defaults
@@ -5627,23 +5721,63 @@ var BaseAddon = class {
5627
5721
  deviceSettingsSchema() {
5628
5722
  return null;
5629
5723
  }
5630
- async getGlobalSettings(overlay, cap, _nodeId) {
5724
+ async getGlobalSettings(overlay, cap, nodeId) {
5631
5725
  const schema = this.globalSettingsSchema(cap);
5632
5726
  if (!schema) return { sections: [] };
5633
- const raw = await this._ctx?.settings?.readAddonStore() ?? {};
5727
+ const projected = await this.resolveGlobalStore(nodeId, cap);
5634
5728
  return hydrateSchema(schema, overlay ? {
5635
- ...raw,
5729
+ ...projected,
5636
5730
  ...overlay
5637
- } : raw);
5731
+ } : projected);
5638
5732
  }
5639
- async updateGlobalSettings(patch, _nodeId) {
5640
- await this._ctx?.settings?.writeAddonStore(patch);
5733
+ /**
5734
+ * The raw addon store PROJECTED onto the target node's bare per-node keys:
5735
+ * every `perNode: true` field carries THAT node's scoped value on its bare
5736
+ * key (absent scoped key ⇒ key absent, so the schema `default` wins — no
5737
+ * bare fallback), all `@`-scoped and stray bare per-node keys are dropped.
5738
+ * A no-op passthrough when the schema declares no `perNode` field.
5739
+ *
5740
+ * This is the sanctioned way for a `getGlobalSettings` OVERRIDE that needs
5741
+ * the store for custom option logic (option narrowing, value snapping) to
5742
+ * read it per-node — never `ctx.settings.readAddonStore()` directly.
5743
+ * `nodeId` omitted ⇒ the local node (node-blind injection from ctx).
5744
+ */
5745
+ async resolveGlobalStore(nodeId, cap) {
5746
+ const raw = await this._ctx?.settings?.readAddonStore() ?? {};
5747
+ const keys = this.perNodeKeys(cap);
5748
+ const node = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
5749
+ return keys.size > 0 ? projectStore(raw, keys, node) : raw;
5750
+ }
5751
+ async updateGlobalSettings(patch, nodeId) {
5752
+ const keys = this.perNodeKeys();
5753
+ const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
5754
+ const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
5755
+ const barePatch = patch;
5756
+ const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
5757
+ await this._ctx?.settings?.writeAddonStore(scoped);
5758
+ if (target !== localNode) return;
5641
5759
  await this.resolveConfig();
5642
5760
  await this.onConfigChanged();
5643
5761
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
5644
5762
  this.maybeAutoRestart(patch, this.globalSettingsSchema());
5645
5763
  }
5646
5764
  /**
5765
+ * The set of field keys the global settings schema declares `perNode: true`
5766
+ * — derived once per `cap` argument and memoized (schemas are static
5767
+ * declarations). Empty set ⇒ every per-node code path is bypassed and the
5768
+ * settings API behaves exactly like the legacy node-agnostic one.
5769
+ */
5770
+ _perNodeKeysCache = /* @__PURE__ */ new Map();
5771
+ perNodeKeys(cap) {
5772
+ const cacheKey = cap ?? "";
5773
+ const cached = this._perNodeKeysCache.get(cacheKey);
5774
+ if (cached) return cached;
5775
+ const schema = this.globalSettingsSchema(cap);
5776
+ const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
5777
+ this._perNodeKeysCache.set(cacheKey, keys);
5778
+ return keys;
5779
+ }
5780
+ /**
5647
5781
  * If any field in `patch` is marked `requiresRestart` in `schema`,
5648
5782
  * schedule an addon restart for the next tick. Deferred via
5649
5783
  * `setImmediate` so the tRPC mutation that triggered the write has
@@ -5796,12 +5930,19 @@ var BaseAddon = class {
5796
5930
  * The merge is shallow: each key in `defaults` is checked against the store.
5797
5931
  * Only keys present in defaults are read — the store can contain extra keys
5798
5932
  * (e.g. from older versions) without polluting the typed config.
5933
+ *
5934
+ * Keys the global settings schema declares `perNode: true` resolve from
5935
+ * THIS node's scoped key (`<key>@<localNode>`) via `readNodeValue` — never
5936
+ * from the bare key — so a per-node field resolves to this node's own
5937
+ * selection at boot (absent scoped key ⇒ the constructor default wins).
5799
5938
  */
5800
5939
  async resolveConfig() {
5801
5940
  const stored = await this.readAddonStoreWithRetry();
5941
+ const perNode = this.perNodeKeys();
5942
+ const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
5802
5943
  const resolved = { ...this.defaults };
5803
5944
  for (const key of Object.keys(this.defaults)) {
5804
- const storedValue = stored[key];
5945
+ const storedValue = perNode.has(key) ? readNodeValue(stored, key, localNode) : stored[key];
5805
5946
  if (storedValue !== void 0 && storedValue !== null) {
5806
5947
  const defaultType = typeof this.defaults[key];
5807
5948
  if (typeof storedValue === defaultType) resolved[key] = storedValue;
@@ -5885,6 +6026,27 @@ var BaseAddon = class {
5885
6026
  }
5886
6027
  };
5887
6028
  /**
6029
+ * Collect the keys of every field marked `perNode: true`, recursing into
6030
+ * layout containers (`group` fields and `sub-tabs` tabs) the same way
6031
+ * `hydrateSchema` does. Valueless structural fields (separator/info/…)
6032
+ * don't declare `perNode` and are excluded by the `in` narrowing.
6033
+ */
6034
+ function collectPerNodeFieldKeys(fields) {
6035
+ const collected = [];
6036
+ for (const field of fields) {
6037
+ if (field.type === "group") {
6038
+ collected.push(...collectPerNodeFieldKeys(field.fields));
6039
+ continue;
6040
+ }
6041
+ if (field.type === "sub-tabs") {
6042
+ for (const tab of field.tabs) collected.push(...collectPerNodeFieldKeys(tab.fields));
6043
+ continue;
6044
+ }
6045
+ if ("perNode" in field && field.perNode === true) collected.push(field.key);
6046
+ }
6047
+ return collected;
6048
+ }
6049
+ /**
5888
6050
  * Normalize an `ICamstackAddon.initialize()` return value into the
5889
6051
  * `AddonInitResult` envelope. Arrays are wrapped into `{ providers }`;
5890
6052
  * envelopes pass through; void stays void.
@@ -6652,6 +6814,13 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
6652
6814
  /** Single still-image entity (HA `image.*`). Read-only display of an
6653
6815
  * `entity_picture` signed URL the browser loads directly. `image` cap. */
6654
6816
  DeviceType["Image"] = "image";
6817
+ /** Smart pet feeder — cloud-connected food dispenser with a bowl food
6818
+ * level, battery, desiccant life, feeding state and manual-feed /
6819
+ * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
6820
+ * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
6821
+ * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
6822
+ * integrations sharing the same food/desiccant/hopper surface. */
6823
+ DeviceType["PetFeeder"] = "pet-feeder";
6655
6824
  return DeviceType;
6656
6825
  }({});
6657
6826
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -10364,7 +10533,8 @@ var MotionAnalysisResultSchema = object({
10364
10533
  });
10365
10534
  method(object({
10366
10535
  deviceId: number(),
10367
- frame: FrameInputSchema
10536
+ frame: FrameInputSchema.optional(),
10537
+ frameHandle: FrameHandleSchema.optional()
10368
10538
  }), MotionAnalysisResultSchema, { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), object({
10369
10539
  deviceId: number(),
10370
10540
  detected: boolean(),
@@ -10671,11 +10841,20 @@ var pipelineExecutorCapability = {
10671
10841
  * legacy call shape used by existing benchmark code; once all
10672
10842
  * callers pass it explicitly we make it required.
10673
10843
  *
10674
- * Exactly one of `frame`, `imageBase64`, `referenceImage` must be
10675
- * provided:
10844
+ * Exactly one of `frame`, `frameHandle`, `imageBase64`,
10845
+ * `referenceImage` must be provided:
10676
10846
  * - `frame`: runtime dispatch path (runner → decoded broker frame).
10677
10847
  * Carries the raw buffer, dimensions, and format; the executor
10678
10848
  * uses it directly without base64 round-tripping.
10849
+ * - `frameHandle` (CB5): a zero-pixel shm `FrameHandle` for the SAME
10850
+ * decoded frame. Both runner and executor are hub-local processes
10851
+ * sharing `/dev/shm`, so the executor maps the named segment and
10852
+ * reads the pixels back zero-copy — eliminating the ~1.2MB
10853
+ * re-serialisation over UDS/MsgPack the `frame` path pays per call.
10854
+ * High-risk: the FrameRing is a latest-wins seqlock with no
10855
+ * refcount, so a recycled slot yields a null read; the executor
10856
+ * then degrades to an empty result and the runner ships pixels via
10857
+ * `frame` as the fallback (queue-depth gated on the runner side).
10679
10858
  * - `imageBase64`: one-shot test path (benchmark ImageTab).
10680
10859
  * - `referenceImage`: named file from the reference-image store.
10681
10860
  */
@@ -10683,6 +10862,12 @@ var pipelineExecutorCapability = {
10683
10862
  engine: PipelineEngineChoiceSchema.optional(),
10684
10863
  steps: array(PipelineStepInputSchema).min(1),
10685
10864
  frame: FrameInputSchema.optional(),
10865
+ /**
10866
+ * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
10867
+ * the decoded pixels live in. One more member of the one-of
10868
+ * frame/frameHandle/image/imageBase64/referenceImage group.
10869
+ */
10870
+ frameHandle: FrameHandleSchema.optional(),
10686
10871
  imageBase64: string().optional(),
10687
10872
  /**
10688
10873
  * Binary JPEG bytes — preferred over `imageBase64` on internal
@@ -11576,6 +11761,113 @@ object({
11576
11761
  lastFetchedAt: number()
11577
11762
  });
11578
11763
  DeviceType.Sensor;
11764
+ /**
11765
+ * Feeder connectivity / power status — mirrors the HA petkit device-status
11766
+ * enum: `normal` (online, mains), `offline` (not reaching PetKit cloud),
11767
+ * `on_batteries` (running on battery backup). `null` until first reported.
11768
+ */
11769
+ var PetFeederDeviceStatusSchema = _enum([
11770
+ "normal",
11771
+ "offline",
11772
+ "on_batteries"
11773
+ ]);
11774
+ var gramsPortion = number().int().min(4).max(200);
11775
+ object({
11776
+ /** Food currently in the bowl (grams). Null when the device has not
11777
+ * reported a reading yet. On dual-hopper models this is the combined
11778
+ * bowl reading; per-hopper levels live in `food1`/`food2`. */
11779
+ foodLevel: number().nullable(),
11780
+ /** Hopper-1 food level (grams) on dual-hopper feeders; null on
11781
+ * single-hopper models. */
11782
+ food1: number().nullable(),
11783
+ /** Hopper-2 food level (grams) on dual-hopper feeders; null on
11784
+ * single-hopper models. */
11785
+ food2: number().nullable(),
11786
+ /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
11787
+ * (`device_class: problem`, on = low). True when the bowl is empty /
11788
+ * below the feeder's low threshold. */
11789
+ lowFood: boolean(),
11790
+ /** Battery charge 0..100 (%). Null on mains-powered models or when the
11791
+ * device has no battery reading. */
11792
+ batteryPower: number().min(0).max(100).nullable(),
11793
+ /** Days of desiccant life remaining. Null when the model has no
11794
+ * desiccant sensor. */
11795
+ desiccantLeftDays: number().nullable(),
11796
+ /** True while a feed is in progress. */
11797
+ feeding: boolean(),
11798
+ /** Decoded connectivity / power status (HA petkit device-status enum).
11799
+ * Null until the device has reported a status. */
11800
+ status: PetFeederDeviceStatusSchema.nullable(),
11801
+ /** Decoded human-readable fault message. Null (or the device's `no_error`
11802
+ * sentinel) means healthy; a non-null string is an active fault. Pairs
11803
+ * with `errorCode` for consumers that want the raw integer. */
11804
+ error: string().nullable(),
11805
+ /** Raw device error code (0 / null = no error). */
11806
+ errorCode: number().nullable(),
11807
+ /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
11808
+ isDualHopper: boolean(),
11809
+ /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
11810
+ childLock: boolean(),
11811
+ /** Front indicator-light setting. */
11812
+ indicatorLight: boolean(),
11813
+ /** Play a chime when dispensing. */
11814
+ feedSound: boolean(),
11815
+ /** Speaker / prompt volume level (device-scaled integer). */
11816
+ volume: number(),
11817
+ /** Ms epoch when the slice was last refreshed from the cloud. */
11818
+ lastFetchedAt: number()
11819
+ });
11820
+ DeviceType.PetFeeder, method(object({
11821
+ deviceId: number().int().nonnegative(),
11822
+ grams: gramsPortion.optional(),
11823
+ hopper1: gramsPortion.optional(),
11824
+ hopper2: gramsPortion.optional()
11825
+ }), _void(), {
11826
+ kind: "mutation",
11827
+ auth: "admin"
11828
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
11829
+ kind: "mutation",
11830
+ auth: "admin"
11831
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
11832
+ kind: "mutation",
11833
+ auth: "admin"
11834
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
11835
+ kind: "mutation",
11836
+ auth: "admin"
11837
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
11838
+ kind: "mutation",
11839
+ auth: "admin"
11840
+ }), method(object({
11841
+ deviceId: number().int().nonnegative(),
11842
+ soundId: number().int().nonnegative()
11843
+ }), _void(), {
11844
+ kind: "mutation",
11845
+ auth: "admin"
11846
+ }), method(object({
11847
+ deviceId: number().int().nonnegative(),
11848
+ on: boolean()
11849
+ }), _void(), {
11850
+ kind: "mutation",
11851
+ auth: "admin"
11852
+ }), method(object({
11853
+ deviceId: number().int().nonnegative(),
11854
+ on: boolean()
11855
+ }), _void(), {
11856
+ kind: "mutation",
11857
+ auth: "admin"
11858
+ }), method(object({
11859
+ deviceId: number().int().nonnegative(),
11860
+ on: boolean()
11861
+ }), _void(), {
11862
+ kind: "mutation",
11863
+ auth: "admin"
11864
+ }), method(object({
11865
+ deviceId: number().int().nonnegative(),
11866
+ level: number().int().nonnegative()
11867
+ }), _void(), {
11868
+ kind: "mutation",
11869
+ auth: "admin"
11870
+ });
11579
11871
  object({
11580
11872
  /** Instantaneous power draw in watts. */
11581
11873
  watts: number().optional(),
@@ -13509,10 +13801,12 @@ method(object({ codec: string() }), boolean()), method(_void(), object({
13509
13801
  url: string()
13510
13802
  }), _void()), method(object({
13511
13803
  sessionId: string(),
13512
- maxCount: number().default(1)
13804
+ maxCount: number().default(1),
13805
+ waitMs: number().optional()
13513
13806
  }), array(DecodedFrameSchema)), method(object({
13514
13807
  sessionId: string(),
13515
- maxCount: number().default(1)
13808
+ maxCount: number().default(1),
13809
+ waitMs: number().optional()
13516
13810
  }), array(FrameHandleSchema)), method(object({ handle: FrameHandleSchema }), DecodedFrameSchema.nullable()), method(object({ sessionId: string() }), ShmRingStatsSchema.nullable()), method(object({
13517
13811
  sessionId: string(),
13518
13812
  config: DecoderSessionConfigSchema.partial()
@@ -18177,7 +18471,10 @@ var HwAccelBackendInputSchema = _enum([
18177
18471
  "webgpu",
18178
18472
  "none"
18179
18473
  ]).nullable().optional();
18180
- var HwAccelResolutionSchema = object({ preferred: array(string()).readonly() });
18474
+ var HwAccelResolutionSchema = object({
18475
+ preferred: array(string()).readonly(),
18476
+ rationale: string()
18477
+ });
18181
18478
  var HardwareEncoderIdSchema = _enum([
18182
18479
  "h264_videotoolbox",
18183
18480
  "hevc_videotoolbox",
@@ -18282,10 +18579,7 @@ var ResolvedInferenceConfigSchema = object({
18282
18579
  format: ModelFormatSchema,
18283
18580
  reason: string()
18284
18581
  });
18285
- method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema), method(object({ requirements: array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema), method(object({
18286
- prefer: HwAccelBackendInputSchema,
18287
- nodeId: string().optional()
18288
- }), HwAccelResolutionSchema), method(_void(), HardwareEncodersSchema), method(_void(), HardwareEncodersSchema, {
18582
+ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema), method(object({ requirements: array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema), method(object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema), method(_void(), HardwareEncodersSchema), method(_void(), HardwareEncodersSchema, {
18289
18583
  kind: "mutation",
18290
18584
  auth: "admin"
18291
18585
  }), method(_void(), HardwareDecodeAccelsSchema), method(_void(), HardwareDecodeAccelsSchema, {
@@ -21250,6 +21544,66 @@ Object.freeze({
21250
21544
  addonId: null,
21251
21545
  access: "create"
21252
21546
  },
21547
+ "petFeeder.callPet": {
21548
+ capName: "pet-feeder",
21549
+ capScope: "device",
21550
+ addonId: null,
21551
+ access: "create"
21552
+ },
21553
+ "petFeeder.cancelFeed": {
21554
+ capName: "pet-feeder",
21555
+ capScope: "device",
21556
+ addonId: null,
21557
+ access: "create"
21558
+ },
21559
+ "petFeeder.feed": {
21560
+ capName: "pet-feeder",
21561
+ capScope: "device",
21562
+ addonId: null,
21563
+ access: "create"
21564
+ },
21565
+ "petFeeder.markFoodReplenished": {
21566
+ capName: "pet-feeder",
21567
+ capScope: "device",
21568
+ addonId: null,
21569
+ access: "create"
21570
+ },
21571
+ "petFeeder.playSound": {
21572
+ capName: "pet-feeder",
21573
+ capScope: "device",
21574
+ addonId: null,
21575
+ access: "create"
21576
+ },
21577
+ "petFeeder.resetDesiccant": {
21578
+ capName: "pet-feeder",
21579
+ capScope: "device",
21580
+ addonId: null,
21581
+ access: "delete"
21582
+ },
21583
+ "petFeeder.setChildLock": {
21584
+ capName: "pet-feeder",
21585
+ capScope: "device",
21586
+ addonId: null,
21587
+ access: "create"
21588
+ },
21589
+ "petFeeder.setFeedSound": {
21590
+ capName: "pet-feeder",
21591
+ capScope: "device",
21592
+ addonId: null,
21593
+ access: "create"
21594
+ },
21595
+ "petFeeder.setIndicatorLight": {
21596
+ capName: "pet-feeder",
21597
+ capScope: "device",
21598
+ addonId: null,
21599
+ access: "create"
21600
+ },
21601
+ "petFeeder.setVolume": {
21602
+ capName: "pet-feeder",
21603
+ capScope: "device",
21604
+ addonId: null,
21605
+ access: "create"
21606
+ },
21253
21607
  "pipelineAnalytics.clearTracks": {
21254
21608
  capName: "pipeline-analytics",
21255
21609
  capScope: "device",
@@ -24622,6 +24976,14 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
24622
24976
  if (!this._cameraSettingsState) this._cameraSettingsState = this.state("cameraSettings", CameraSettingsMapSchema, {});
24623
24977
  return this._cameraSettingsState;
24624
24978
  }
24979
+ /** One-shot `migrateLegacyFlagsToBindings` guard flag. Absent or
24980
+ * corrupt ⇒ `false` (the migration runs — same fallback as the old
24981
+ * raw `store[key] === true` check). */
24982
+ _bindingsMigrationDoneState = null;
24983
+ get bindingsMigrationDoneState() {
24984
+ if (!this._bindingsMigrationDoneState) this._bindingsMigrationDoneState = this.state("bindingsMigration_v1_done", boolean(), false);
24985
+ return this._bindingsMigrationDoneState;
24986
+ }
24625
24987
  /**
24626
24988
  * Per-camera zones CRUD provider. Constructed lazily in `onInitialize`
24627
24989
  * because it captures `this.ctx` for settings + api access; cleared
@@ -24801,7 +25163,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
24801
25163
  async onInitialize() {
24802
25164
  this.initTimestamp = Date.now();
24803
25165
  try {
24804
- const stored = await this.ctx.settings?.readAddonStore() ?? {};
25166
+ const stored = await this.resolveGlobalStore();
24805
25167
  this.globalSettings = { ...stored };
24806
25168
  this.applyRuntimeSettings(this.globalSettings);
24807
25169
  } catch (err) {
@@ -25013,7 +25375,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
25013
25375
  this.pendingRetryTimer = setInterval(() => void this.retryPendingDispatches(), PENDING_RETRY_INTERVAL_MS);
25014
25376
  this.ctx.logger.info("Pipeline orchestrator detection-wiring subscriptions installed");
25015
25377
  this.migrateLegacyFlagsToBindings().catch((err) => {
25016
- this.ctx.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg(err) } });
25378
+ this.ctxIfReady?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg(err) } });
25017
25379
  });
25018
25380
  this.zoneRulesProvider = new ZoneRulesProvider({
25019
25381
  logger: this.ctx.logger.child("zone-rules"),
@@ -25143,9 +25505,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
25143
25505
  * persist the flag after a successful pass.
25144
25506
  */
25145
25507
  async migrateLegacyFlagsToBindings() {
25146
- const MIGRATION_KEY = "bindingsMigration_v1_done";
25147
- const store = await this.ctx.settings?.readAddonStore() ?? {};
25148
- if (store[MIGRATION_KEY] === true) return;
25508
+ if (await this.bindingsMigrationDoneState.get()) return;
25149
25509
  let api = this.api;
25150
25510
  for (let i = 0; !api && i < 30; i++) {
25151
25511
  await new Promise((r) => setTimeout(r, 200));
@@ -25201,10 +25561,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
25201
25561
  });
25202
25562
  detectionDisabled++;
25203
25563
  }
25204
- await this.ctx.settings?.writeAddonStore({
25205
- ...store,
25206
- [MIGRATION_KEY]: true
25207
- });
25564
+ await this.bindingsMigrationDoneState.set(true);
25208
25565
  this.ctx.logger.info("bindings migration complete", { meta: {
25209
25566
  cameras: cameras.length,
25210
25567
  audioDisabled,
@@ -27830,8 +28187,8 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
27830
28187
  ] });
27831
28188
  }
27832
28189
  async updateGlobalSettings(patch) {
27833
- await this.ctx.settings.writeAddonStore(patch);
27834
- const full = await this.ctx.settings?.readAddonStore() ?? {};
28190
+ await super.updateGlobalSettings(patch);
28191
+ const full = await this.resolveGlobalStore();
27835
28192
  this.globalSettings = { ...full };
27836
28193
  this.applyRuntimeSettings(full);
27837
28194
  const pausedIds = [...this.loadShedState.entries()].filter(([, s]) => s.pausedAt !== null).map(([id]) => id);