@camstack/types 1.2.260 → 1.2.261

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 (48) hide show
  1. package/dist/addon/base-addon.d.ts +9 -1
  2. package/dist/addon.js +4 -6
  3. package/dist/addon.mjs +4 -6
  4. package/dist/capabilities/capability-definition.d.ts +9 -0
  5. package/dist/capabilities/custom-model-registry.cap.d.ts +18 -0
  6. package/dist/capabilities/device-extension.cap.d.ts +370 -0
  7. package/dist/capabilities/device-manager.cap.d.ts +45 -5
  8. package/dist/capabilities/doorbell.cap.d.ts +41 -0
  9. package/dist/capabilities/embedding-encoder.cap.d.ts +175 -0
  10. package/dist/capabilities/index.d.ts +15 -12
  11. package/dist/capabilities/llm-runtime.cap.d.ts +4 -4
  12. package/dist/capabilities/llm-shared.d.ts +6 -6
  13. package/dist/capabilities/llm.cap.d.ts +6 -6
  14. package/dist/capabilities/model-convert.cap.d.ts +9 -0
  15. package/dist/capabilities/model-distributor.cap.d.ts +18 -0
  16. package/dist/capabilities/notification-rules.cap.d.ts +32 -14
  17. package/dist/capabilities/osd-manager.cap.d.ts +6 -6
  18. package/dist/capabilities/pipeline-analytics.cap.d.ts +80 -2
  19. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +62 -1
  20. package/dist/capabilities/pipeline-runner.cap.d.ts +24 -0
  21. package/dist/capabilities/vector-store.cap.d.ts +14 -0
  22. package/dist/device-extension/index.d.ts +3 -0
  23. package/dist/device-extension/slot-descriptor.d.ts +62 -0
  24. package/dist/device-extension/slot-provider.d.ts +68 -0
  25. package/dist/generated/addon-api.d.ts +50 -0
  26. package/dist/generated/capability-router-map.d.ts +5 -2
  27. package/dist/generated/collection-array-methods.d.ts +1 -1
  28. package/dist/generated/device-proxy.d.ts +7 -7
  29. package/dist/generated/device-scoped-caps.d.ts +1 -1
  30. package/dist/generated/extension-slots.d.ts +39 -0
  31. package/dist/generated/method-access-map.d.ts +1 -1
  32. package/dist/generated/method-device-selectors.d.ts +1 -1
  33. package/dist/generated/system-proxy.d.ts +4 -2
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.js +1439 -475
  36. package/dist/index.mjs +1379 -476
  37. package/dist/interfaces/addon.d.ts +41 -0
  38. package/dist/interfaces/event-bus.d.ts +10 -0
  39. package/dist/node.d.ts +1 -0
  40. package/dist/node.js +73 -7
  41. package/dist/node.mjs +73 -8
  42. package/dist/pipeline/cluster-model-scope.d.ts +59 -3
  43. package/dist/{sleep-BaRu0bS5.mjs → sleep-BXv0ZcRa.mjs} +49 -33
  44. package/dist/{sleep-BDYxE1ax.js → sleep-QxyLiUId.js} +54 -32
  45. package/dist/storage/atomic-write.d.ts +25 -0
  46. package/dist/types/clip-model.d.ts +64 -0
  47. package/dist/types/models.d.ts +18 -0
  48. package/package.json +1 -1
@@ -489,6 +489,17 @@ export interface AddonContext<TConfig = Record<string, unknown>> {
489
489
  * allowlisted engine-cascade holdouts are the only callers.
490
490
  */
491
491
  readAddonStore(): Promise<Record<string, unknown>>;
492
+ /**
493
+ * The same read, THREE-WAY: a stored blob, no blob, or a read that FAILED.
494
+ * `readAddonStore` folds a failure into `{}`, which a caller cannot tell
495
+ * from "never written"; this one does not (D651). Optional: a settings view
496
+ * that predates it answers nothing, and the caller must treat that as
497
+ * unreadable, never as empty.
498
+ *
499
+ * @internal Same rule as `readAddonStore`: addons reach it only through
500
+ * `BaseAddon.readStoredGlobalKey`.
501
+ */
502
+ readAddonStoreResult?(): Promise<AddonStoreRead>;
492
503
  /**
493
504
  * Write a patch into the addon store. Keys not in the patch are
494
505
  * preserved. Keys explicitly set to `null` are preserved as-is (they
@@ -1409,3 +1420,33 @@ export interface InternalAddonContext extends AddonContext {
1409
1420
  /** Resolve the preferred provider for a capability. Kernel-only. */
1410
1421
  resolveProvider<T = unknown>(capabilityName: string): T | null;
1411
1422
  }
1423
+ /**
1424
+ * A three-way read of an addon's own store (D651). `missing` and `failed` are
1425
+ * distinct on purpose: folding them is how a failed read becomes "defaults".
1426
+ */
1427
+ export type AddonStoreRead = {
1428
+ readonly kind: 'value';
1429
+ readonly value: Readonly<Record<string, unknown>>;
1430
+ } | {
1431
+ readonly kind: 'missing';
1432
+ } | {
1433
+ readonly kind: 'failed';
1434
+ readonly error: string;
1435
+ };
1436
+ /** One stored key of an addon's own store, read three-way (D651). */
1437
+ export type StoredKeyRead =
1438
+ /** The key was written; `value` is what is stored. */
1439
+ {
1440
+ readonly kind: 'present';
1441
+ readonly value: unknown;
1442
+ }
1443
+ /** The store answered and the key was never written. */
1444
+ | {
1445
+ readonly kind: 'absent';
1446
+ }
1447
+ /** The store did not answer, or this node's settings view cannot say. */
1448
+ | {
1449
+ readonly kind: 'unreadable';
1450
+ readonly reason: 'store-read-failed' | 'three-way-read-unavailable';
1451
+ readonly error?: string;
1452
+ };
@@ -165,6 +165,16 @@ export interface PipelineAnalyticsFrameTrackedPayload {
165
165
  * a dropped frame just lets the session close slightly earlier.
166
166
  */
167
167
  readonly hasLiveTrack?: boolean;
168
+ /**
169
+ * How many objects the TRACKER holds on this frame — moving OR still,
170
+ * i.e. `result.tracked.length` (D652). Diagnostics only: it decides nothing.
171
+ * {@link hasLiveTrack} deliberately subtracts a still subject, so a session
172
+ * that closed on a parked car and one that closed on an empty street both
173
+ * saw `false` last; this count is what tells them apart on the orchestrator's
174
+ * close line (`liveTracks`). Absent ⇒ unknown (an older post-analysis),
175
+ * which the consumer reports as `null`, never as 0.
176
+ */
177
+ readonly liveTrackCount?: number;
168
178
  [key: string]: unknown;
169
179
  }
170
180
  /** Lifecycle phase of a tracked object: it appears (`start`), its best
package/dist/node.d.ts CHANGED
@@ -13,6 +13,7 @@ export { FfmpegProcess } from './ffmpeg/process.js';
13
13
  export type { ChildCostClaimHandle, ChildCostClaimInput, ProcStatReader, } from './process/child-cost-registry.js';
14
14
  export { ChildCostRegistry, NO_COST_CLAIM, nodeProcStatReader, parseProcCpuSeconds, parseProcRssBytes, readProcessCost, } from './process/child-cost-registry.js';
15
15
  export { FilesystemStorageProvider } from './storage/filesystem-storage-provider.js';
16
+ export { writeFileAtomic } from './storage/atomic-write.js';
16
17
  export type { PhysicalRoot, RealpathSync } from './storage/physical-root.js';
17
18
  export { containsOrEquals, physicalRootOf } from './storage/physical-root.js';
18
19
  export { canonicalHash } from './utils/canonical-hash.js';
package/dist/node.js CHANGED
@@ -1744,6 +1744,69 @@ var ChildCostRegistry = class {
1744
1744
  }
1745
1745
  };
1746
1746
  //#endregion
1747
+ //#region src/storage/atomic-write.ts
1748
+ /** Errors a Windows rename over a file another process has open can raise. */
1749
+ var WINDOWS_RENAME_BUSY = new Set([
1750
+ "EPERM",
1751
+ "EACCES",
1752
+ "EBUSY"
1753
+ ]);
1754
+ var WINDOWS_RENAME_RETRIES = 3;
1755
+ var WINDOWS_RENAME_RETRY_MS = 25;
1756
+ function errnoCode(err) {
1757
+ return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : void 0;
1758
+ }
1759
+ /**
1760
+ * Replace `filePath` with `data` so that a concurrent reader sees EITHER the
1761
+ * old complete file OR the new complete file — never a prefix (D654).
1762
+ *
1763
+ * A plain `writeFile` truncates the target and then writes it in chunks, so a
1764
+ * reader in between gets a torn file: a fixed-path single-instance media kind
1765
+ * (`lastFrame`, `lastSceneFrame`) is overwritten every roll while a
1766
+ * notification may be reading it. Here the bytes go to a temp file in the SAME
1767
+ * directory (same filesystem, so the rename cannot degrade to a copy), and a
1768
+ * `rename` swaps it in — atomic on POSIX. A reader that already opened the old
1769
+ * file keeps reading the old inode to the end.
1770
+ *
1771
+ * The temp name is DOT-prefixed so every directory scan that skips dot entries
1772
+ * (occupancy, D327) never counts it; it is removed on any failure. A process
1773
+ * killed between the write and the rename leaves one `.<name>.<uuid>.tmp`.
1774
+ *
1775
+ * Windows: a rename over a file another process holds open can fail with
1776
+ * EPERM/EACCES/EBUSY. It is retried briefly and then falls back to copying the
1777
+ * temp over the target — non-atomic, exactly the pre-D654 behaviour, rather
1778
+ * than losing the write.
1779
+ *
1780
+ * Cost: one extra `rename` (and, on failure, one `rm`) per write — a metadata
1781
+ * operation, independent of the payload size.
1782
+ */
1783
+ async function writeFileAtomic(filePath, data) {
1784
+ const tmp = node_path.join(node_path.dirname(filePath), `.${node_path.basename(filePath)}.${(0, node_crypto.randomUUID)()}.tmp`);
1785
+ try {
1786
+ await node_fs.promises.writeFile(tmp, data);
1787
+ await renameOver(tmp, filePath);
1788
+ } catch (err) {
1789
+ await node_fs.promises.rm(tmp, { force: true }).catch(() => void 0);
1790
+ throw err;
1791
+ }
1792
+ }
1793
+ async function renameOver(tmp, filePath) {
1794
+ for (let attempt = 0;; attempt += 1) try {
1795
+ await node_fs.promises.rename(tmp, filePath);
1796
+ return;
1797
+ } catch (err) {
1798
+ const code = errnoCode(err);
1799
+ if (!(process.platform === "win32" && code !== void 0 && WINDOWS_RENAME_BUSY.has(code))) throw err;
1800
+ if (attempt < WINDOWS_RENAME_RETRIES) {
1801
+ await new Promise((resolve) => setTimeout(resolve, WINDOWS_RENAME_RETRY_MS));
1802
+ continue;
1803
+ }
1804
+ await node_fs.promises.copyFile(tmp, filePath);
1805
+ await node_fs.promises.rm(tmp, { force: true });
1806
+ return;
1807
+ }
1808
+ }
1809
+ //#endregion
1747
1810
  //#region src/storage/filesystem-storage-provider.ts
1748
1811
  var STORAGE_LOCATION_TYPES = [
1749
1812
  "data",
@@ -1817,14 +1880,16 @@ var FilesystemStorageProvider = class {
1817
1880
  relativePath
1818
1881
  });
1819
1882
  await node_fs.promises.mkdir(node_path.dirname(filePath), { recursive: true });
1820
- if (Buffer.isBuffer(data)) await node_fs.promises.writeFile(filePath, data);
1883
+ if (Buffer.isBuffer(data)) await writeFileAtomic(filePath, data);
1821
1884
  else {
1822
- const writeStream = node_fs.createWriteStream(filePath);
1823
- await new Promise((resolve, reject) => {
1824
- data.pipe(writeStream);
1825
- writeStream.on("finish", resolve);
1826
- writeStream.on("error", reject);
1827
- });
1885
+ const tmp = node_path.join(node_path.dirname(filePath), `.${node_path.basename(filePath)}.${(0, node_crypto.randomUUID)()}.tmp`);
1886
+ try {
1887
+ await (0, node_stream_promises.pipeline)(data, node_fs.createWriteStream(tmp));
1888
+ await node_fs.promises.rename(tmp, filePath);
1889
+ } catch (err) {
1890
+ await node_fs.promises.rm(tmp, { force: true }).catch(() => void 0);
1891
+ throw err;
1892
+ }
1828
1893
  }
1829
1894
  }
1830
1895
  async read({ location, relativePath }) {
@@ -2090,3 +2155,4 @@ exports.readProcessCost = readProcessCost;
2090
2155
  exports.resolveExportFingerprint = resolveExportFingerprint;
2091
2156
  exports.signExpiringUrl = signExpiringUrl;
2092
2157
  exports.verifyExpiringUrl = verifyExpiringUrl;
2158
+ exports.writeFileAtomic = writeFileAtomic;
package/dist/node.mjs CHANGED
@@ -1721,6 +1721,69 @@ var ChildCostRegistry = class {
1721
1721
  }
1722
1722
  };
1723
1723
  //#endregion
1724
+ //#region src/storage/atomic-write.ts
1725
+ /** Errors a Windows rename over a file another process has open can raise. */
1726
+ var WINDOWS_RENAME_BUSY = new Set([
1727
+ "EPERM",
1728
+ "EACCES",
1729
+ "EBUSY"
1730
+ ]);
1731
+ var WINDOWS_RENAME_RETRIES = 3;
1732
+ var WINDOWS_RENAME_RETRY_MS = 25;
1733
+ function errnoCode(err) {
1734
+ return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : void 0;
1735
+ }
1736
+ /**
1737
+ * Replace `filePath` with `data` so that a concurrent reader sees EITHER the
1738
+ * old complete file OR the new complete file — never a prefix (D654).
1739
+ *
1740
+ * A plain `writeFile` truncates the target and then writes it in chunks, so a
1741
+ * reader in between gets a torn file: a fixed-path single-instance media kind
1742
+ * (`lastFrame`, `lastSceneFrame`) is overwritten every roll while a
1743
+ * notification may be reading it. Here the bytes go to a temp file in the SAME
1744
+ * directory (same filesystem, so the rename cannot degrade to a copy), and a
1745
+ * `rename` swaps it in — atomic on POSIX. A reader that already opened the old
1746
+ * file keeps reading the old inode to the end.
1747
+ *
1748
+ * The temp name is DOT-prefixed so every directory scan that skips dot entries
1749
+ * (occupancy, D327) never counts it; it is removed on any failure. A process
1750
+ * killed between the write and the rename leaves one `.<name>.<uuid>.tmp`.
1751
+ *
1752
+ * Windows: a rename over a file another process holds open can fail with
1753
+ * EPERM/EACCES/EBUSY. It is retried briefly and then falls back to copying the
1754
+ * temp over the target — non-atomic, exactly the pre-D654 behaviour, rather
1755
+ * than losing the write.
1756
+ *
1757
+ * Cost: one extra `rename` (and, on failure, one `rm`) per write — a metadata
1758
+ * operation, independent of the payload size.
1759
+ */
1760
+ async function writeFileAtomic(filePath, data) {
1761
+ const tmp = path$1.join(path$1.dirname(filePath), `.${path$1.basename(filePath)}.${randomUUID()}.tmp`);
1762
+ try {
1763
+ await fs.promises.writeFile(tmp, data);
1764
+ await renameOver(tmp, filePath);
1765
+ } catch (err) {
1766
+ await fs.promises.rm(tmp, { force: true }).catch(() => void 0);
1767
+ throw err;
1768
+ }
1769
+ }
1770
+ async function renameOver(tmp, filePath) {
1771
+ for (let attempt = 0;; attempt += 1) try {
1772
+ await fs.promises.rename(tmp, filePath);
1773
+ return;
1774
+ } catch (err) {
1775
+ const code = errnoCode(err);
1776
+ if (!(process.platform === "win32" && code !== void 0 && WINDOWS_RENAME_BUSY.has(code))) throw err;
1777
+ if (attempt < WINDOWS_RENAME_RETRIES) {
1778
+ await new Promise((resolve) => setTimeout(resolve, WINDOWS_RENAME_RETRY_MS));
1779
+ continue;
1780
+ }
1781
+ await fs.promises.copyFile(tmp, filePath);
1782
+ await fs.promises.rm(tmp, { force: true });
1783
+ return;
1784
+ }
1785
+ }
1786
+ //#endregion
1724
1787
  //#region src/storage/filesystem-storage-provider.ts
1725
1788
  var STORAGE_LOCATION_TYPES = [
1726
1789
  "data",
@@ -1794,14 +1857,16 @@ var FilesystemStorageProvider = class {
1794
1857
  relativePath
1795
1858
  });
1796
1859
  await fs.promises.mkdir(path$1.dirname(filePath), { recursive: true });
1797
- if (Buffer.isBuffer(data)) await fs.promises.writeFile(filePath, data);
1860
+ if (Buffer.isBuffer(data)) await writeFileAtomic(filePath, data);
1798
1861
  else {
1799
- const writeStream = fs.createWriteStream(filePath);
1800
- await new Promise((resolve, reject) => {
1801
- data.pipe(writeStream);
1802
- writeStream.on("finish", resolve);
1803
- writeStream.on("error", reject);
1804
- });
1862
+ const tmp = path$1.join(path$1.dirname(filePath), `.${path$1.basename(filePath)}.${randomUUID()}.tmp`);
1863
+ try {
1864
+ await pipeline(data, fs.createWriteStream(tmp));
1865
+ await fs.promises.rename(tmp, filePath);
1866
+ } catch (err) {
1867
+ await fs.promises.rm(tmp, { force: true }).catch(() => void 0);
1868
+ throw err;
1869
+ }
1805
1870
  }
1806
1871
  }
1807
1872
  async read({ location, relativePath }) {
@@ -2033,4 +2098,4 @@ function resolveExportFingerprint(input) {
2033
2098
  return input.persisted ?? input.fresh;
2034
2099
  }
2035
2100
  //#endregion
2036
- export { ChildCostRegistry, FFMPEG_RELEASE, FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, NO_COST_CLAIM, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, containsOrEquals, diffExportTargets, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegArtifact, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements, nodeProcStatReader, parseFfmpegCapabilities, parseProcCpuSeconds, parseProcRssBytes, physicalRootOf, probeFfmpegBinary, readProcessCost, resolveExportFingerprint, signExpiringUrl, verifyExpiringUrl };
2101
+ export { ChildCostRegistry, FFMPEG_RELEASE, FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, NO_COST_CLAIM, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, containsOrEquals, diffExportTargets, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegArtifact, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements, nodeProcStatReader, parseFfmpegCapabilities, parseProcCpuSeconds, parseProcRssBytes, physicalRootOf, probeFfmpegBinary, readProcessCost, resolveExportFingerprint, signExpiringUrl, verifyExpiringUrl, writeFileAtomic };
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Why the orchestrator cannot say which model the operator chose for a
3
+ * cluster-scoped step (D651).
4
+ */
5
+ export declare const CLUSTER_MODEL_CHOICE_UNREADABLE_REASONS: readonly ["owner-not-ready", "store-read-failed", "three-way-read-unavailable", "invalid-stored", "not-cluster-scoped"];
1
6
  /**
2
7
  * How a step's model is chosen.
3
8
  *
@@ -66,13 +71,58 @@ export declare function isClusterScopedStep(stepId: string): boolean;
66
71
  export type ClusterStepModels = Readonly<Record<string, string>>;
67
72
  /** The cluster row when nobody has configured one — today's catalog defaults. */
68
73
  export declare const DEFAULT_CLUSTER_STEP_MODELS: ClusterStepModels;
74
+ /**
75
+ * What ONE stored `clusterModel:<stepId>` value means (D651 fix round 4).
76
+ *
77
+ * - `absent` — nobody chose. The key was never written, or the settings form
78
+ * cleared it: a select clears a field with a `null` patch, and `hydrateSchema`
79
+ * reads a stored `null` as `value ?? default` on every hydrated surface, so
80
+ * `null` and `''` are the same statement as "never written".
81
+ * - `chosen` — a non-empty string, taken VERBATIM. Not checked against the
82
+ * step's `options`: the runtime pins an id the catalog does not list on
83
+ * purpose (`cluster-model-resolution.ts`: a custom encoder from
84
+ * `custom-model-registry` is the operator's own compatibility problem), and
85
+ * the dropdown is guarded separately by `scripts/check-cluster-model-scope.ts`.
86
+ * An owner that called such an id invalid while the runner ran it would give
87
+ * the same stored key two meanings — the defect this function removes.
88
+ * - `invalid` — anything else (a number, an object). The form cannot write it;
89
+ * it is a hand edit or a corrupted row.
90
+ *
91
+ * Both readers of the key go through this function: the runner's
92
+ * {@link readClusterStepModels} and the owner's `decideClusterModelChoice`
93
+ * (addon-pipeline-orchestrator). They differ only in what they DO with
94
+ * `invalid`: a live row is total, so the runner falls to the catalog default
95
+ * and says so in its log (`ClusterModelSource`); a pass that would rewrite an
96
+ * index on it refuses (`invalid-stored`).
97
+ */
98
+ export type StoredClusterModelInterpretation = {
99
+ readonly kind: 'absent';
100
+ } | {
101
+ readonly kind: 'chosen';
102
+ readonly modelId: string;
103
+ } | {
104
+ readonly kind: 'invalid';
105
+ readonly value: unknown;
106
+ };
107
+ export declare function interpretStoredClusterModel(raw: unknown): StoredClusterModelInterpretation;
108
+ /** One stored cluster model value the interpretation rejected. */
109
+ export interface InvalidClusterStepModelEntry {
110
+ readonly stepId: string;
111
+ readonly value: unknown;
112
+ }
113
+ /**
114
+ * The cluster-scoped steps whose stored value is `invalid` under
115
+ * {@link interpretStoredClusterModel} — for the runtime's log line, so the
116
+ * default it runs on such a row is never run in silence.
117
+ */
118
+ export declare function invalidClusterStepModelEntries(config: Readonly<Record<string, unknown>>): readonly InvalidClusterStepModelEntry[];
69
119
  /**
70
120
  * Narrow a FLAT settings record to the cluster row.
71
121
  *
72
122
  * Per-FIELD fallback, deliberately (same rule as `readDetailCropConvention`): a
73
- * junk face model must not also discard a valid clip model. An absent, empty or
74
- * non-string value resolves to the step's catalog default — the historical
75
- * behaviour — never to a blank id, because a blank id downstream becomes
123
+ * junk face model must not also discard a valid clip model. An `absent` or
124
+ * `invalid` value ({@link interpretStoredClusterModel}) resolves to the step's
125
+ * catalog default — never to a blank id, because a blank id downstream becomes
76
126
  * "substitute the format default", which is precisely the substitution this
77
127
  * scope exists to forbid.
78
128
  */
@@ -98,6 +148,12 @@ export interface HydratedClusterView {
98
148
  * (addon mid-boot) is the defaults.
99
149
  */
100
150
  export declare function pickClusterStepModels(view: HydratedClusterView | null): ClusterStepModels;
151
+ /**
152
+ * The `invalid` stored cluster model values a hydrated view carries — what the
153
+ * runtime logs when it runs a default on a row it could not read as a choice.
154
+ * A `null` view (owner mid-boot) carries nothing to judge.
155
+ */
156
+ export declare function pickInvalidClusterStepModels(view: HydratedClusterView | null): readonly InvalidClusterStepModelEntry[];
101
157
  /**
102
158
  * The cluster model for `stepId`, or `null` when the step is node-scoped.
103
159
  *
@@ -917,6 +917,40 @@ var BaseAddon = class {
917
917
  const node = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
918
918
  return keys.size > 0 ? projectStore(raw, keys, node) : raw;
919
919
  }
920
+ /**
921
+ * Whether ONE global key of this addon's store was written, read three-way
922
+ * (D651). For a question a hydrated view cannot answer: `getGlobalSettings`
923
+ * reports `value ?? default`, so "the operator chose the default" and "nobody
924
+ * chose" look the same, and a failed read looks like both. A `perNode` key is
925
+ * not supported here: its stored name is scoped.
926
+ */
927
+ async readStoredGlobalKey(key) {
928
+ const read = this._ctx?.settings?.readAddonStoreResult;
929
+ if (read === void 0) return {
930
+ kind: "unreadable",
931
+ reason: "three-way-read-unavailable"
932
+ };
933
+ let result;
934
+ try {
935
+ result = await read.call(this._ctx?.settings);
936
+ } catch (err) {
937
+ return {
938
+ kind: "unreadable",
939
+ reason: "store-read-failed",
940
+ error: err instanceof Error ? err.message : String(err)
941
+ };
942
+ }
943
+ if (result.kind === "failed") return {
944
+ kind: "unreadable",
945
+ reason: "store-read-failed",
946
+ error: result.error
947
+ };
948
+ if (result.kind === "missing" || !Object.prototype.hasOwnProperty.call(result.value, key)) return { kind: "absent" };
949
+ return {
950
+ kind: "present",
951
+ value: result.value[key]
952
+ };
953
+ }
920
954
  async updateGlobalSettings(patch, nodeId) {
921
955
  const keys = this.perNodeKeys();
922
956
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
@@ -3560,6 +3594,11 @@ function createDeviceProxy(api, binding, opts) {
3560
3594
  getDeviceLiveContribution: (input) => dispatch("camera-grid-layout", "cameraGridLayout", "getDeviceLiveContribution", "query", input),
3561
3595
  applyDeviceSettingsPatch: (input) => dispatch("camera-grid-layout", "cameraGridLayout", "applyDeviceSettingsPatch", "mutation", input)
3562
3596
  },
3597
+ cameraPipelineConfig: {
3598
+ getDeviceSettingsContribution: (input) => dispatch("camera-pipeline-config", "cameraPipelineConfig", "getDeviceSettingsContribution", "query", input),
3599
+ getDeviceLiveContribution: (input) => dispatch("camera-pipeline-config", "cameraPipelineConfig", "getDeviceLiveContribution", "query", input),
3600
+ applyDeviceSettingsPatch: (input) => dispatch("camera-pipeline-config", "cameraPipelineConfig", "applyDeviceSettingsPatch", "mutation", input)
3601
+ },
3563
3602
  cameraStreams: {
3564
3603
  getCameraStreams: (input) => dispatch("camera-streams", "cameraStreams", "getCameraStreams", "query", input),
3565
3604
  getBrokerStreams: (input) => dispatch("camera-streams", "cameraStreams", "getBrokerStreams", "query", input),
@@ -3619,6 +3658,12 @@ function createDeviceProxy(api, binding, opts) {
3619
3658
  releaseDevice: (input) => dispatch("device-discovery", "deviceDiscovery", "releaseDevice", "mutation", input),
3620
3659
  getStatus: (input) => dispatch("device-discovery", "deviceDiscovery", "getStatus", "query", input)
3621
3660
  },
3661
+ deviceExtension: {
3662
+ listSources: (input) => dispatch("device-extension", "deviceExtension", "listSources", "query", input),
3663
+ listSlots: (input) => dispatch("device-extension", "deviceExtension", "listSlots", "query", input),
3664
+ listCandidates: (input) => dispatch("device-extension", "deviceExtension", "listCandidates", "query", input),
3665
+ setSlot: (input) => dispatch("device-extension", "deviceExtension", "setSlot", "mutation", input)
3666
+ },
3622
3667
  deviceOps: {
3623
3668
  getStreamSources: (input) => dispatch("device-ops", "deviceOps", "getStreamSources", "query", input),
3624
3669
  getConfigEntries: (input) => dispatch("device-ops", "deviceOps", "getConfigEntries", "query", input),
@@ -3737,30 +3782,6 @@ function createDeviceProxy(api, binding, opts) {
3737
3782
  getStatus: (input) => dispatch("navigation", "navigation", "getStatus", "query", input)
3738
3783
  },
3739
3784
  networkLink: { getStatus: (input) => dispatch("network-link", "networkLink", "getStatus", "query", input) },
3740
- notificationRules: {
3741
- listRules: (input) => dispatch("notification-rules", "notificationRules", "listRules", "query", input),
3742
- getRule: (input) => dispatch("notification-rules", "notificationRules", "getRule", "query", input),
3743
- createRule: (input) => dispatch("notification-rules", "notificationRules", "createRule", "mutation", input),
3744
- updateRule: (input) => dispatch("notification-rules", "notificationRules", "updateRule", "mutation", input),
3745
- deleteRule: (input) => dispatch("notification-rules", "notificationRules", "deleteRule", "mutation", input),
3746
- setRuleEnabled: (input) => dispatch("notification-rules", "notificationRules", "setRuleEnabled", "mutation", input),
3747
- getClipRetentionAsks: (input) => dispatch("notification-rules", "notificationRules", "getClipRetentionAsks", "query", input),
3748
- listDeviceMutes: (input) => dispatch("notification-rules", "notificationRules", "listDeviceMutes", "query", input),
3749
- setDeviceMuted: (input) => dispatch("notification-rules", "notificationRules", "setDeviceMuted", "mutation", input),
3750
- testRule: (input) => dispatch("notification-rules", "notificationRules", "testRule", "mutation", input),
3751
- previewStateSummary: (input) => dispatch("notification-rules", "notificationRules", "previewStateSummary", "mutation", input),
3752
- getConditionCatalog: (input) => dispatch("notification-rules", "notificationRules", "getConditionCatalog", "query", input),
3753
- getTemplateCatalog: (input) => dispatch("notification-rules", "notificationRules", "getTemplateCatalog", "query", input),
3754
- previewTemplate: (input) => dispatch("notification-rules", "notificationRules", "previewTemplate", "mutation", input),
3755
- getHistory: (input) => dispatch("notification-rules", "notificationRules", "getHistory", "query", input),
3756
- getRuleLastRuns: (input) => dispatch("notification-rules", "notificationRules", "getRuleLastRuns", "query", input),
3757
- resolveArtifactUrl: (input) => dispatch("notification-rules", "notificationRules", "resolveArtifactUrl", "query", input),
3758
- listSnoozes: (input) => dispatch("notification-rules", "notificationRules", "listSnoozes", "query", input),
3759
- createSnooze: (input) => dispatch("notification-rules", "notificationRules", "createSnooze", "mutation", input),
3760
- cancelSnooze: (input) => dispatch("notification-rules", "notificationRules", "cancelSnooze", "mutation", input),
3761
- getAlarmConfig: (input) => dispatch("notification-rules", "notificationRules", "getAlarmConfig", "query", input),
3762
- setAlarmConfig: (input) => dispatch("notification-rules", "notificationRules", "setAlarmConfig", "mutation", input)
3763
- },
3764
3785
  notifier: {
3765
3786
  send: (input) => dispatch("notifier", "notifier", "send", "mutation", input),
3766
3787
  cancel: (input) => dispatch("notifier", "notifier", "cancel", "mutation", input),
@@ -4018,11 +4039,6 @@ function createDeviceProxy(api, binding, opts) {
4018
4039
  updateDeviceSettings: (input) => dispatchSystem("addonSettings", "updateDeviceSettings", "mutation", input)
4019
4040
  },
4020
4041
  audioAnalyzer: { detachDevice: (input) => dispatchSystem("audioAnalyzer", "detachDevice", "mutation", input) },
4021
- cameraPipelineConfig: {
4022
- getDeviceSettingsContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceSettingsContribution", "query", input),
4023
- getDeviceLiveContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceLiveContribution", "query", input),
4024
- applyDeviceSettingsPatch: (input) => dispatchSystem("cameraPipelineConfig", "applyDeviceSettingsPatch", "mutation", input)
4025
- },
4026
4042
  deviceAdoption: { getStatus: (input) => dispatchSystem("deviceAdoption", "getStatus", "query", input) },
4027
4043
  deviceExport: {
4028
4044
  getDeviceSettingsContribution: (input) => dispatchSystem("deviceExport", "getDeviceSettingsContribution", "query", input),
@@ -4072,7 +4088,6 @@ function createDeviceProxy(api, binding, opts) {
4072
4088
  getDeviceLiveContribution: (input) => dispatchSystem("deviceManager", "getDeviceLiveContribution", "query", input),
4073
4089
  applyDeviceSettingsPatch: (input) => dispatchSystem("deviceManager", "applyDeviceSettingsPatch", "mutation", input)
4074
4090
  },
4075
- deviceProvider: { reloadDevice: (input) => dispatchSystem("deviceProvider", "reloadDevice", "mutation", input) },
4076
4091
  deviceState: {
4077
4092
  getSnapshot: (input) => dispatchSystem("deviceState", "getSnapshot", "query", input),
4078
4093
  getCapSlice: (input) => dispatchSystem("deviceState", "getCapSlice", "query", input),
@@ -4086,6 +4101,7 @@ function createDeviceProxy(api, binding, opts) {
4086
4101
  getDeviceStats: (input) => dispatchSystem("networkQuality", "getDeviceStats", "query", input),
4087
4102
  reportClientStats: (input) => dispatchSystem("networkQuality", "reportClientStats", "mutation", input)
4088
4103
  },
4104
+ notificationRules: { setDeviceMuted: (input) => dispatchSystem("notificationRules", "setDeviceMuted", "mutation", input) },
4089
4105
  osdManager: {
4090
4106
  getDeviceOsd: (input) => dispatchSystem("osdManager", "getDeviceOsd", "query", input),
4091
4107
  getSourceCatalog: (input) => dispatchSystem("osdManager", "getSourceCatalog", "query", input),
@@ -4108,7 +4124,6 @@ function createDeviceProxy(api, binding, opts) {
4108
4124
  assignAudio: (input) => dispatchSystem("pipelineOrchestrator", "assignAudio", "mutation", input),
4109
4125
  unassignAudio: (input) => dispatchSystem("pipelineOrchestrator", "unassignAudio", "mutation", input),
4110
4126
  getAudioAssignment: (input) => dispatchSystem("pipelineOrchestrator", "getAudioAssignment", "query", input),
4111
- getAudioAssignments: (input) => dispatchSystem("pipelineOrchestrator", "getAudioAssignments", "query", input),
4112
4127
  getCameraSettings: (input) => dispatchSystem("pipelineOrchestrator", "getCameraSettings", "query", input),
4113
4128
  setCameraStepToggle: (input) => dispatchSystem("pipelineOrchestrator", "setCameraStepToggle", "mutation", input),
4114
4129
  getCameraStepOverrides: (input) => dispatchSystem("pipelineOrchestrator", "getCameraStepOverrides", "query", input),
@@ -4180,7 +4195,7 @@ function createDeviceProxy(api, binding, opts) {
4180
4195
  * can answer "may a rule actuate this?" without importing the schema barrel
4181
4196
  * (~144MB RSS per runner, D28).
4182
4197
  *
4183
- * Coverage: 87 device-scoped capabilities.
4198
+ * Coverage: 88 device-scoped capabilities.
4184
4199
  */
4185
4200
  var DEVICE_SCOPED_CAPS = new Set([
4186
4201
  "accessories",
@@ -4210,6 +4225,7 @@ var DEVICE_SCOPED_CAPS = new Set([
4210
4225
  "detection-pipeline",
4211
4226
  "device-admin-link",
4212
4227
  "device-discovery",
4228
+ "device-extension",
4213
4229
  "device-ops",
4214
4230
  "device-status",
4215
4231
  "doorbell",
@@ -4433,4 +4449,4 @@ function sleepCancellable(ms, signal) {
4433
4449
  });
4434
4450
  }
4435
4451
  //#endregion
4436
- export { AudioStreamPickSchema as $, DeviceType as A, DATAPLANE_SECRET_HEADER as At, optionalMethod as B, WELL_KNOWN_TAB_MAP as Bt, customAction as C, parseProfileBrokerId as Ct, ChargingStatus as D, PEER_BYTES_STALL_MS as Dt, deviceCustomAction as E, PEER_BYTES_MAX_OUTSTANDING as Et, PROVIDER_PIN_FIELD as F, createDurableState as Ft, readNodePin as G, AUDIO_CHUNK_FORMATS as Gt, systemMethod as H, collectHydratedFieldValues as Ht, event as I, createEvent as It, ReadinessTimeoutError as J, isAudioChunkFormat as Jt, toNodeId as K, audioChunkBytesPerSample as Kt, expandCapMethods as L, emitReadiness as Lt, DEFAULT_RUNTIME_STATE_DURABILITY as M, DisposerChain as Mt, DEVICE_SETTINGS_CONTRIBUTION_METHODS as N, BaseAddon as Nt, DeviceFeature as O, PEER_BYTES_TICKET_TTL_MS as Ot, DEVICE_STATUS_METHOD as P, normalizeAddonInitResult as Pt, AudioChunkFormatSchema as Q, isDeviceConfigCap as R, isEvent as Rt, DEVICE_CHILDREN_BATCH_MAX as S, makeSourceBrokerId as St, describeCustomActions as T, PEER_BYTES_CHUNK_BYTES as Tt, CAP_NODE_PIN_CONTEXT_KEY as U, hydrateSchema as Ut, resolveCapMount as V, collectHydratedFieldEntries as Vt, nodePin as W, resolveHydratedFieldValue as Wt, readinessKey as X, emitDownForOwnedCaps as Y, scopeKey as Z, createMirrorSource as _, SubscribeAudioChunksInputSchema as _t, asJsonObject as a, CamStreamResolutionSchema as at, deviceOpsCapability as b, SubscribeFramesResultSchema as bt, parseJsonArray as c, DecodedFrameSchema as ct, BOOT_RECOVERY_BACKOFF_MS as d, FrameHandleSchema as dt, BrokerStatsSchema as et, DEVICE_SCOPED_CAPS as f, ProfileRtspEntrySchema as ft, createLazyTrpcSource as g, StreamSourceSchema as gt, createEventBusSliceSource as h, StreamSourceEntrySchema$1 as ht, asJsonArray as i, CamStreamKindSchema as it, adminUiCapability as j, SHARE_VIEW_KINDS as jt, DeviceRole as k, PeerBytesTicketSchema as kt, parseJsonObject as l, EncodedPacketSchema as lt, createDeviceProxy as m, ProfileSlotStatusSchema as mt, sleepCancellable as n, CAM_PROFILE_ORDER as nt, asNumber as o, CameraStreamSchema as ot, isDeviceScopedCap as p, ProfileSlotSchema as pt, ReadinessRegistry as q, expandAudioChunkToF32le as qt, asBoolean as r, CamProfileSchema as rt, asString as s, DecodedAudioChunkSchema as st, sleep as t, BrokerStatusSchema as tt, parseJsonUnknown as u, FrameHandleFormatSchema as ut, createSliceHandle as v, SubscribeAudioChunksResultSchema as vt, defineCustomActions as w, selectAssignedProfileSlots as wt, viewerUiCapability as x, makeProfileBrokerId as xt, RawStateResultSchema as y, SubscribeFramesInputSchema as yt, method as z, WELL_KNOWN_TABS as zt };
4452
+ export { AudioStreamPickSchema as $, DeviceType as A, DATAPLANE_SECRET_HEADER as At, optionalMethod as B, WELL_KNOWN_TABS as Bt, customAction as C, parseProfileBrokerId as Ct, ChargingStatus as D, PEER_BYTES_STALL_MS as Dt, deviceCustomAction as E, PEER_BYTES_MAX_OUTSTANDING as Et, PROVIDER_PIN_FIELD as F, normalizeNodeId as Ft, readNodePin as G, resolveHydratedFieldValue as Gt, systemMethod as H, collectHydratedFieldEntries as Ht, event as I, createDurableState as It, ReadinessTimeoutError as J, expandAudioChunkToF32le as Jt, toNodeId as K, AUDIO_CHUNK_FORMATS as Kt, expandCapMethods as L, createEvent as Lt, DEFAULT_RUNTIME_STATE_DURABILITY as M, DisposerChain as Mt, DEVICE_SETTINGS_CONTRIBUTION_METHODS as N, BaseAddon as Nt, DeviceFeature as O, PEER_BYTES_TICKET_TTL_MS as Ot, DEVICE_STATUS_METHOD as P, normalizeAddonInitResult as Pt, AudioChunkFormatSchema as Q, isDeviceConfigCap as R, emitReadiness as Rt, DEVICE_CHILDREN_BATCH_MAX as S, makeSourceBrokerId as St, describeCustomActions as T, PEER_BYTES_CHUNK_BYTES as Tt, CAP_NODE_PIN_CONTEXT_KEY as U, collectHydratedFieldValues as Ut, resolveCapMount as V, WELL_KNOWN_TAB_MAP as Vt, nodePin as W, hydrateSchema as Wt, readinessKey as X, emitDownForOwnedCaps as Y, isAudioChunkFormat as Yt, scopeKey as Z, createMirrorSource as _, SubscribeAudioChunksInputSchema as _t, asJsonObject as a, CamStreamResolutionSchema as at, deviceOpsCapability as b, SubscribeFramesResultSchema as bt, parseJsonArray as c, DecodedFrameSchema as ct, BOOT_RECOVERY_BACKOFF_MS as d, FrameHandleSchema as dt, BrokerStatsSchema as et, DEVICE_SCOPED_CAPS as f, ProfileRtspEntrySchema as ft, createLazyTrpcSource as g, StreamSourceSchema as gt, createEventBusSliceSource as h, StreamSourceEntrySchema$1 as ht, asJsonArray as i, CamStreamKindSchema as it, adminUiCapability as j, SHARE_VIEW_KINDS as jt, DeviceRole as k, PeerBytesTicketSchema as kt, parseJsonObject as l, EncodedPacketSchema as lt, createDeviceProxy as m, ProfileSlotStatusSchema as mt, sleepCancellable as n, CAM_PROFILE_ORDER as nt, asNumber as o, CameraStreamSchema as ot, isDeviceScopedCap as p, ProfileSlotSchema as pt, ReadinessRegistry as q, audioChunkBytesPerSample as qt, asBoolean as r, CamProfileSchema as rt, asString as s, DecodedAudioChunkSchema as st, sleep as t, BrokerStatusSchema as tt, parseJsonUnknown as u, FrameHandleFormatSchema as ut, createSliceHandle as v, SubscribeAudioChunksResultSchema as vt, defineCustomActions as w, selectAssignedProfileSlots as wt, viewerUiCapability as x, makeProfileBrokerId as xt, RawStateResultSchema as y, SubscribeFramesInputSchema as yt, method as z, isEvent as zt };