@camstack/types 1.2.260 → 1.2.262
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/addon/base-addon.d.ts +9 -1
- package/dist/addon.js +4 -6
- package/dist/addon.mjs +4 -6
- package/dist/capabilities/capability-definition.d.ts +9 -0
- package/dist/capabilities/custom-model-registry.cap.d.ts +18 -0
- package/dist/capabilities/device-extension.cap.d.ts +408 -0
- package/dist/capabilities/device-manager.cap.d.ts +59 -6
- package/dist/capabilities/doorbell.cap.d.ts +41 -1
- package/dist/capabilities/embedding-encoder.cap.d.ts +175 -0
- package/dist/capabilities/index.d.ts +15 -12
- package/dist/capabilities/llm-runtime.cap.d.ts +4 -4
- package/dist/capabilities/llm-shared.d.ts +6 -6
- package/dist/capabilities/llm.cap.d.ts +6 -6
- package/dist/capabilities/model-convert.cap.d.ts +9 -0
- package/dist/capabilities/model-distributor.cap.d.ts +18 -0
- package/dist/capabilities/notification-rules.cap.d.ts +32 -14
- package/dist/capabilities/osd-manager.cap.d.ts +6 -6
- package/dist/capabilities/pipeline-analytics.cap.d.ts +80 -2
- package/dist/capabilities/pipeline-executor.cap.d.ts +1 -0
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +62 -1
- package/dist/capabilities/pipeline-runner.cap.d.ts +24 -0
- package/dist/capabilities/vector-store.cap.d.ts +14 -0
- package/dist/device-extension/index.d.ts +3 -0
- package/dist/device-extension/slot-descriptor.d.ts +62 -0
- package/dist/device-extension/slot-provider.d.ts +70 -0
- package/dist/generated/addon-api.d.ts +39 -31
- package/dist/generated/capability-router-map.d.ts +5 -2
- package/dist/generated/collection-array-methods.d.ts +1 -1
- package/dist/generated/device-proxy.d.ts +8 -8
- package/dist/generated/device-scoped-caps.d.ts +1 -1
- package/dist/generated/extension-slots.d.ts +40 -0
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/method-device-selectors.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +4 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.js +1483 -507
- package/dist/index.mjs +1422 -508
- package/dist/interfaces/addon.d.ts +41 -0
- package/dist/interfaces/event-bus.d.ts +10 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.js +73 -7
- package/dist/node.mjs +73 -8
- package/dist/pipeline/cluster-model-scope.d.ts +59 -3
- package/dist/pipeline/step-format-runnable.d.ts +31 -0
- package/dist/{sleep-BDYxE1ax.js → sleep-8K9gue-H.js} +56 -42
- package/dist/{sleep-BaRu0bS5.mjs → sleep-DC-wdyeS.mjs} +51 -43
- package/dist/storage/atomic-write.d.ts +25 -0
- package/dist/types/clip-model.d.ts +64 -0
- package/dist/types/models.d.ts +18 -0
- 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
|
|
1883
|
+
if (Buffer.isBuffer(data)) await writeFileAtomic(filePath, data);
|
|
1821
1884
|
else {
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
data.
|
|
1825
|
-
|
|
1826
|
-
|
|
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
|
|
1860
|
+
if (Buffer.isBuffer(data)) await writeFileAtomic(filePath, data);
|
|
1798
1861
|
else {
|
|
1799
|
-
const
|
|
1800
|
-
|
|
1801
|
-
data.
|
|
1802
|
-
|
|
1803
|
-
|
|
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
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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
|
*
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE answer to "can a pool of `format` run this step?" (D657).
|
|
3
|
+
*
|
|
4
|
+
* A step can run on a format when the model it NAMES ships a build for that
|
|
5
|
+
* format, or when a NON-legacy model of the step does (the format-default
|
|
6
|
+
* resolution picks among those). A `legacy` build is a catalog stub kept for
|
|
7
|
+
* explicit selection — the Coral face SSD has no working postprocessor — so it
|
|
8
|
+
* makes a format runnable only when it is the model named.
|
|
9
|
+
*
|
|
10
|
+
* Every placement decision reads this: the orchestrator's provisioning gate
|
|
11
|
+
* (`addonHasCompatibleModel`), the executor's dispatch gate
|
|
12
|
+
* (`collectZeroBuildIssues`) and the runner's device jump
|
|
13
|
+
* (`buildSubtreeCanRun`). Three rules for this question existed before; two of
|
|
14
|
+
* them counted the legacy stub and one did not, so the runner jumped a face
|
|
15
|
+
* step off the Coral while the executor loaded it there, and the root's load
|
|
16
|
+
* failed with it.
|
|
17
|
+
*
|
|
18
|
+
* Lives in the bare `@camstack/types` entry, which every addon bundles
|
|
19
|
+
* (`self-contained` externals), so it travels with a `camstack deploy`.
|
|
20
|
+
*/
|
|
21
|
+
/** The catalog facts one model contributes: its id, its builds, and whether it is a legacy stub. */
|
|
22
|
+
export interface FormatRunnableModel {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly legacy?: boolean;
|
|
25
|
+
readonly formats: Readonly<Record<string, unknown>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Can a pool of `format` run a step whose catalog is `models`, given the model
|
|
29
|
+
* the step names (`null`/absent = unpinned, the format default)?
|
|
30
|
+
*/
|
|
31
|
+
export declare function stepModelRunsOnFormat(models: readonly FormatRunnableModel[], modelId: string | null | undefined, format: string): boolean;
|
|
@@ -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),
|
|
@@ -3629,12 +3674,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3629
3674
|
getRawState: (input) => dispatch("device-ops", "deviceOps", "getRawState", "query", input)
|
|
3630
3675
|
},
|
|
3631
3676
|
deviceStatus: { getStatus: (input) => dispatch("device-status", "deviceStatus", "getStatus", "query", input) },
|
|
3632
|
-
doorbell: {
|
|
3633
|
-
getStatus: (input) => dispatch("doorbell", "doorbell", "getStatus", "query", input),
|
|
3634
|
-
getDeviceSettingsContribution: (input) => dispatch("doorbell", "doorbell", "getDeviceSettingsContribution", "query", input),
|
|
3635
|
-
getDeviceLiveContribution: (input) => dispatch("doorbell", "doorbell", "getDeviceLiveContribution", "query", input),
|
|
3636
|
-
applyDeviceSettingsPatch: (input) => dispatch("doorbell", "doorbell", "applyDeviceSettingsPatch", "mutation", input)
|
|
3637
|
-
},
|
|
3677
|
+
doorbell: { getStatus: (input) => dispatch("doorbell", "doorbell", "getStatus", "query", input) },
|
|
3638
3678
|
enumSensor: { getStatus: (input) => dispatch("enum-sensor", "enumSensor", "getStatus", "query", input) },
|
|
3639
3679
|
eventEmitter: { getStatus: (input) => dispatch("event-emitter", "eventEmitter", "getStatus", "query", input) },
|
|
3640
3680
|
fanControl: {
|
|
@@ -3737,30 +3777,6 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3737
3777
|
getStatus: (input) => dispatch("navigation", "navigation", "getStatus", "query", input)
|
|
3738
3778
|
},
|
|
3739
3779
|
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
3780
|
notifier: {
|
|
3765
3781
|
send: (input) => dispatch("notifier", "notifier", "send", "mutation", input),
|
|
3766
3782
|
cancel: (input) => dispatch("notifier", "notifier", "cancel", "mutation", input),
|
|
@@ -4018,11 +4034,6 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
4018
4034
|
updateDeviceSettings: (input) => dispatchSystem("addonSettings", "updateDeviceSettings", "mutation", input)
|
|
4019
4035
|
},
|
|
4020
4036
|
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
4037
|
deviceAdoption: { getStatus: (input) => dispatchSystem("deviceAdoption", "getStatus", "query", input) },
|
|
4027
4038
|
deviceExport: {
|
|
4028
4039
|
getDeviceSettingsContribution: (input) => dispatchSystem("deviceExport", "getDeviceSettingsContribution", "query", input),
|
|
@@ -4067,12 +4078,8 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
4067
4078
|
updateDeviceField: (input) => dispatchSystem("deviceManager", "updateDeviceField", "mutation", input),
|
|
4068
4079
|
updateDeviceFieldsBatch: (input) => dispatchSystem("deviceManager", "updateDeviceFieldsBatch", "mutation", input),
|
|
4069
4080
|
testField: (input) => dispatchSystem("deviceManager", "testField", "mutation", input),
|
|
4070
|
-
getDeviceStatusAggregate: (input) => dispatchSystem("deviceManager", "getDeviceStatusAggregate", "query", input)
|
|
4071
|
-
getDeviceSettingsContribution: (input) => dispatchSystem("deviceManager", "getDeviceSettingsContribution", "query", input),
|
|
4072
|
-
getDeviceLiveContribution: (input) => dispatchSystem("deviceManager", "getDeviceLiveContribution", "query", input),
|
|
4073
|
-
applyDeviceSettingsPatch: (input) => dispatchSystem("deviceManager", "applyDeviceSettingsPatch", "mutation", input)
|
|
4081
|
+
getDeviceStatusAggregate: (input) => dispatchSystem("deviceManager", "getDeviceStatusAggregate", "query", input)
|
|
4074
4082
|
},
|
|
4075
|
-
deviceProvider: { reloadDevice: (input) => dispatchSystem("deviceProvider", "reloadDevice", "mutation", input) },
|
|
4076
4083
|
deviceState: {
|
|
4077
4084
|
getSnapshot: (input) => dispatchSystem("deviceState", "getSnapshot", "query", input),
|
|
4078
4085
|
getCapSlice: (input) => dispatchSystem("deviceState", "getCapSlice", "query", input),
|
|
@@ -4086,6 +4093,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
4086
4093
|
getDeviceStats: (input) => dispatchSystem("networkQuality", "getDeviceStats", "query", input),
|
|
4087
4094
|
reportClientStats: (input) => dispatchSystem("networkQuality", "reportClientStats", "mutation", input)
|
|
4088
4095
|
},
|
|
4096
|
+
notificationRules: { setDeviceMuted: (input) => dispatchSystem("notificationRules", "setDeviceMuted", "mutation", input) },
|
|
4089
4097
|
osdManager: {
|
|
4090
4098
|
getDeviceOsd: (input) => dispatchSystem("osdManager", "getDeviceOsd", "query", input),
|
|
4091
4099
|
getSourceCatalog: (input) => dispatchSystem("osdManager", "getSourceCatalog", "query", input),
|
|
@@ -4108,7 +4116,6 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
4108
4116
|
assignAudio: (input) => dispatchSystem("pipelineOrchestrator", "assignAudio", "mutation", input),
|
|
4109
4117
|
unassignAudio: (input) => dispatchSystem("pipelineOrchestrator", "unassignAudio", "mutation", input),
|
|
4110
4118
|
getAudioAssignment: (input) => dispatchSystem("pipelineOrchestrator", "getAudioAssignment", "query", input),
|
|
4111
|
-
getAudioAssignments: (input) => dispatchSystem("pipelineOrchestrator", "getAudioAssignments", "query", input),
|
|
4112
4119
|
getCameraSettings: (input) => dispatchSystem("pipelineOrchestrator", "getCameraSettings", "query", input),
|
|
4113
4120
|
setCameraStepToggle: (input) => dispatchSystem("pipelineOrchestrator", "setCameraStepToggle", "mutation", input),
|
|
4114
4121
|
getCameraStepOverrides: (input) => dispatchSystem("pipelineOrchestrator", "getCameraStepOverrides", "query", input),
|
|
@@ -4180,7 +4187,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
4180
4187
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
4181
4188
|
* (~144MB RSS per runner, D28).
|
|
4182
4189
|
*
|
|
4183
|
-
* Coverage:
|
|
4190
|
+
* Coverage: 88 device-scoped capabilities.
|
|
4184
4191
|
*/
|
|
4185
4192
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
4186
4193
|
"accessories",
|
|
@@ -4210,6 +4217,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
4210
4217
|
"detection-pipeline",
|
|
4211
4218
|
"device-admin-link",
|
|
4212
4219
|
"device-discovery",
|
|
4220
|
+
"device-extension",
|
|
4213
4221
|
"device-ops",
|
|
4214
4222
|
"device-status",
|
|
4215
4223
|
"doorbell",
|
|
@@ -4949,6 +4957,12 @@ Object.defineProperty(exports, "normalizeAddonInitResult", {
|
|
|
4949
4957
|
return normalizeAddonInitResult;
|
|
4950
4958
|
}
|
|
4951
4959
|
});
|
|
4960
|
+
Object.defineProperty(exports, "normalizeNodeId", {
|
|
4961
|
+
enumerable: true,
|
|
4962
|
+
get: function() {
|
|
4963
|
+
return normalizeNodeId;
|
|
4964
|
+
}
|
|
4965
|
+
});
|
|
4952
4966
|
Object.defineProperty(exports, "optionalMethod", {
|
|
4953
4967
|
enumerable: true,
|
|
4954
4968
|
get: function() {
|