@camstack/addon-post-analysis 1.2.651 → 1.2.653
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/CHANGELOG.md +12 -0
- package/dist/{clip-model-registry-BWY8P-AK.mjs → clip-model-registry-BrazZJvr.mjs} +2000 -720
- package/dist/{clip-model-registry-Dte3QQEt.js → clip-model-registry-Bz1uQvQ8.js} +2000 -720
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-_vVfXkDx.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D6DnF6Vk.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BKMr4IqL.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CtNyIYsM.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-A7m5uu3n.mjs → hostInit-DtrcY1yz.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +145 -8
- package/dist/pipeline-analytics/index.mjs +145 -8
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-6PIugMbv.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Bk6VlAsZ.mjs +0 -26
|
@@ -5676,7 +5676,7 @@ var ZodIssueCode = {
|
|
|
5676
5676
|
var ZodFirstPartyTypeKind;
|
|
5677
5677
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5678
5678
|
//#endregion
|
|
5679
|
-
//#region ../types/dist/sleep-
|
|
5679
|
+
//#region ../types/dist/sleep-63gM_1JR.mjs
|
|
5680
5680
|
/**
|
|
5681
5681
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5682
5682
|
* window to float samples (D455).
|
|
@@ -8777,65 +8777,6 @@ var MODEL_FORMATS = [
|
|
|
8777
8777
|
"pt",
|
|
8778
8778
|
"gguf"
|
|
8779
8779
|
];
|
|
8780
|
-
/**
|
|
8781
|
-
* Per-call node pinning for `ctx.api` capability calls.
|
|
8782
|
-
*
|
|
8783
|
-
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8784
|
-
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8785
|
-
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8786
|
-
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8787
|
-
* call to that node.
|
|
8788
|
-
*
|
|
8789
|
-
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8790
|
-
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8791
|
-
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8792
|
-
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8793
|
-
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8794
|
-
* which classifies a pinned agent node as `agent-child-forward`
|
|
8795
|
-
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8796
|
-
*
|
|
8797
|
-
* Usage at a call site:
|
|
8798
|
-
*
|
|
8799
|
-
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8800
|
-
*/
|
|
8801
|
-
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8802
|
-
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8803
|
-
/**
|
|
8804
|
-
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8805
|
-
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8806
|
-
*
|
|
8807
|
-
* ## The id is normalised here, and it has to be
|
|
8808
|
-
*
|
|
8809
|
-
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8810
|
-
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8811
|
-
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8812
|
-
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8813
|
-
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8814
|
-
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8815
|
-
* the thing you reach for when you specifically need THIS node — silently
|
|
8816
|
-
* addressed a node that does not exist.
|
|
8817
|
-
*
|
|
8818
|
-
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8819
|
-
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8820
|
-
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8821
|
-
* while D67's whole premise was that the decoder addon is the authority on
|
|
8822
|
-
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8823
|
-
* hardware", which was false.
|
|
8824
|
-
*
|
|
8825
|
-
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8826
|
-
* it is here and not at the call sites.
|
|
8827
|
-
*/
|
|
8828
|
-
function nodePin(nodeId) {
|
|
8829
|
-
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8830
|
-
}
|
|
8831
|
-
/**
|
|
8832
|
-
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8833
|
-
* is idempotent, so passing an already-clean id costs nothing.
|
|
8834
|
-
*/
|
|
8835
|
-
function toNodeId(idOrRunnerId) {
|
|
8836
|
-
const head = idOrRunnerId.split("/")[0];
|
|
8837
|
-
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8838
|
-
}
|
|
8839
8780
|
object({
|
|
8840
8781
|
field: string().min(1),
|
|
8841
8782
|
on: literal("advance")
|
|
@@ -8928,6 +8869,111 @@ function optionalMethod(input, output, options) {
|
|
|
8928
8869
|
function event(data) {
|
|
8929
8870
|
return { data };
|
|
8930
8871
|
}
|
|
8872
|
+
/**
|
|
8873
|
+
* Per-call node pinning for `ctx.api` capability calls.
|
|
8874
|
+
*
|
|
8875
|
+
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8876
|
+
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8877
|
+
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8878
|
+
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8879
|
+
* call to that node.
|
|
8880
|
+
*
|
|
8881
|
+
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8882
|
+
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8883
|
+
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8884
|
+
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8885
|
+
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8886
|
+
* which classifies a pinned agent node as `agent-child-forward`
|
|
8887
|
+
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8888
|
+
*
|
|
8889
|
+
* Usage at a call site:
|
|
8890
|
+
*
|
|
8891
|
+
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8892
|
+
*/
|
|
8893
|
+
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8894
|
+
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8895
|
+
/**
|
|
8896
|
+
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8897
|
+
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8898
|
+
*
|
|
8899
|
+
* ## The id is normalised here, and it has to be
|
|
8900
|
+
*
|
|
8901
|
+
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8902
|
+
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8903
|
+
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8904
|
+
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8905
|
+
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8906
|
+
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8907
|
+
* the thing you reach for when you specifically need THIS node — silently
|
|
8908
|
+
* addressed a node that does not exist.
|
|
8909
|
+
*
|
|
8910
|
+
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8911
|
+
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8912
|
+
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8913
|
+
* while D67's whole premise was that the decoder addon is the authority on
|
|
8914
|
+
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8915
|
+
* hardware", which was false.
|
|
8916
|
+
*
|
|
8917
|
+
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8918
|
+
* it is here and not at the call sites.
|
|
8919
|
+
*/
|
|
8920
|
+
function nodePin(nodeId) {
|
|
8921
|
+
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8922
|
+
}
|
|
8923
|
+
/**
|
|
8924
|
+
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8925
|
+
* is idempotent, so passing an already-clean id costs nothing.
|
|
8926
|
+
*/
|
|
8927
|
+
function toNodeId(idOrRunnerId) {
|
|
8928
|
+
const head = idOrRunnerId.split("/")[0];
|
|
8929
|
+
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8930
|
+
}
|
|
8931
|
+
/**
|
|
8932
|
+
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
8933
|
+
* declarations. Every addon that contributes a UI page registers a
|
|
8934
|
+
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
8935
|
+
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
8936
|
+
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
8937
|
+
* that admin-ui consumes.
|
|
8938
|
+
*
|
|
8939
|
+
* The split exists because the public listing has a different output
|
|
8940
|
+
* shape than the per-provider raw declarations, and we want both ends
|
|
8941
|
+
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
8942
|
+
*/
|
|
8943
|
+
var AddonPageDeclarationSchema = object({
|
|
8944
|
+
id: string(),
|
|
8945
|
+
label: string(),
|
|
8946
|
+
icon: string(),
|
|
8947
|
+
path: string(),
|
|
8948
|
+
/**
|
|
8949
|
+
* Module Federation remote name — must match the `name` field on the
|
|
8950
|
+
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
8951
|
+
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
8952
|
+
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
8953
|
+
* contain hyphens).
|
|
8954
|
+
*/
|
|
8955
|
+
remoteName: string(),
|
|
8956
|
+
/**
|
|
8957
|
+
* Bundle filename inside the addon's `dist/` dir served at
|
|
8958
|
+
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
8959
|
+
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
8960
|
+
* the static-file route can compute an mtime-based cache-buster URL
|
|
8961
|
+
* without a separate filesystem stat.
|
|
8962
|
+
*/
|
|
8963
|
+
bundle: string(),
|
|
8964
|
+
/**
|
|
8965
|
+
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
8966
|
+
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
8967
|
+
* Any OTHER string creates (or joins) a custom section rendered after
|
|
8968
|
+
* the built-in groups; its label comes from `sectionLabel` (first
|
|
8969
|
+
* declaration wins), falling back to the id. Absent → the legacy
|
|
8970
|
+
* "Addon Pages" group.
|
|
8971
|
+
*/
|
|
8972
|
+
section: string().optional(),
|
|
8973
|
+
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
8974
|
+
sectionLabel: string().optional()
|
|
8975
|
+
});
|
|
8976
|
+
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
8931
8977
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
8932
8978
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
8933
8979
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
@@ -8953,6 +8999,27 @@ function customAction(input, output, options) {
|
|
|
8953
8999
|
...options?.caller ? { caller: "required" } : {}
|
|
8954
9000
|
};
|
|
8955
9001
|
}
|
|
9002
|
+
/**
|
|
9003
|
+
* The pipeline step-input shape and the run-result bridge, in a module that
|
|
9004
|
+
* imports nothing but zod and types.
|
|
9005
|
+
*
|
|
9006
|
+
* WHY A SEPARATE FILE (addon footprint, D28): both were declared in
|
|
9007
|
+
* `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
|
|
9008
|
+
* graph. An addon that only needs these two (the benchmark's `runPipelineTest`
|
|
9009
|
+
* action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
|
|
9010
|
+
* them, so every existing import keeps working; addon server code takes them
|
|
9011
|
+
* from `@camstack/types/addon`.
|
|
9012
|
+
*/
|
|
9013
|
+
var PipelineStepInputSchema = lazy(() => object({
|
|
9014
|
+
addonId: string(),
|
|
9015
|
+
modelId: string().optional(),
|
|
9016
|
+
enabled: boolean().default(true),
|
|
9017
|
+
children: array(PipelineStepInputSchema).optional(),
|
|
9018
|
+
settings: record(string(), unknown()).optional(),
|
|
9019
|
+
jumpDeviceKey: string().optional()
|
|
9020
|
+
}));
|
|
9021
|
+
/** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
|
|
9022
|
+
var PipelineRunResultBridge = custom();
|
|
8956
9023
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
8957
9024
|
var VersionOutputSchema = object({ version: string() });
|
|
8958
9025
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -9149,6 +9216,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9149
9216
|
"osd",
|
|
9150
9217
|
"osd-manager",
|
|
9151
9218
|
"pet-feeder",
|
|
9219
|
+
"pet-fountain",
|
|
9152
9220
|
"pipeline-analytics",
|
|
9153
9221
|
"pipeline-executor",
|
|
9154
9222
|
"pipeline-orchestrator",
|
|
@@ -9170,6 +9238,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9170
9238
|
"recording-signal",
|
|
9171
9239
|
"records",
|
|
9172
9240
|
"remote",
|
|
9241
|
+
"remote-layout-components",
|
|
9173
9242
|
"running-state",
|
|
9174
9243
|
"scene-monitor",
|
|
9175
9244
|
"script-runner",
|
|
@@ -10428,6 +10497,56 @@ Object.freeze({
|
|
|
10428
10497
|
"valueType": "number"
|
|
10429
10498
|
}
|
|
10430
10499
|
],
|
|
10500
|
+
"pet-fountain": [
|
|
10501
|
+
{
|
|
10502
|
+
"path": "waterLevel",
|
|
10503
|
+
"valueType": "number"
|
|
10504
|
+
},
|
|
10505
|
+
{
|
|
10506
|
+
"path": "waterVolume",
|
|
10507
|
+
"valueType": "number"
|
|
10508
|
+
},
|
|
10509
|
+
{
|
|
10510
|
+
"path": "empty",
|
|
10511
|
+
"valueType": "boolean"
|
|
10512
|
+
},
|
|
10513
|
+
{
|
|
10514
|
+
"path": "pumpRunning",
|
|
10515
|
+
"valueType": "boolean"
|
|
10516
|
+
},
|
|
10517
|
+
{
|
|
10518
|
+
"path": "pumpIntervalEnabled",
|
|
10519
|
+
"valueType": "boolean"
|
|
10520
|
+
},
|
|
10521
|
+
{
|
|
10522
|
+
"path": "pumpOnTime",
|
|
10523
|
+
"valueType": "number"
|
|
10524
|
+
},
|
|
10525
|
+
{
|
|
10526
|
+
"path": "pumpOffTime",
|
|
10527
|
+
"valueType": "number"
|
|
10528
|
+
},
|
|
10529
|
+
{
|
|
10530
|
+
"path": "lastDrink",
|
|
10531
|
+
"valueType": "number"
|
|
10532
|
+
},
|
|
10533
|
+
{
|
|
10534
|
+
"path": "currentHourDrink",
|
|
10535
|
+
"valueType": "number"
|
|
10536
|
+
},
|
|
10537
|
+
{
|
|
10538
|
+
"path": "last24hDrink",
|
|
10539
|
+
"valueType": "number"
|
|
10540
|
+
},
|
|
10541
|
+
{
|
|
10542
|
+
"path": "todayDrink",
|
|
10543
|
+
"valueType": "number"
|
|
10544
|
+
},
|
|
10545
|
+
{
|
|
10546
|
+
"path": "error",
|
|
10547
|
+
"valueType": "string"
|
|
10548
|
+
}
|
|
10549
|
+
],
|
|
10431
10550
|
"power-meter": [
|
|
10432
10551
|
{
|
|
10433
10552
|
"path": "power",
|
|
@@ -10967,7 +11086,7 @@ Object.freeze({
|
|
|
10967
11086
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
10968
11087
|
* (~144MB RSS per runner, D28).
|
|
10969
11088
|
*
|
|
10970
|
-
* Coverage:
|
|
11089
|
+
* Coverage: 97 device-scoped capabilities.
|
|
10971
11090
|
*/
|
|
10972
11091
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
10973
11092
|
"accessories",
|
|
@@ -11033,6 +11152,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
11033
11152
|
"numeric-sensor",
|
|
11034
11153
|
"osd",
|
|
11035
11154
|
"pet-feeder",
|
|
11155
|
+
"pet-fountain",
|
|
11036
11156
|
"pipeline-analytics",
|
|
11037
11157
|
"power-meter",
|
|
11038
11158
|
"presence",
|
|
@@ -11111,7 +11231,7 @@ function sleep(ms) {
|
|
|
11111
11231
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11112
11232
|
}
|
|
11113
11233
|
//#endregion
|
|
11114
|
-
//#region ../types/dist/err-msg-
|
|
11234
|
+
//#region ../types/dist/err-msg-DWXWc0Di.mjs
|
|
11115
11235
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
11116
11236
|
DeviceType["Camera"] = "camera";
|
|
11117
11237
|
DeviceType["Hub"] = "hub";
|
|
@@ -11203,6 +11323,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
11203
11323
|
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
11204
11324
|
* integrations sharing the same food/desiccant/hopper surface. */
|
|
11205
11325
|
DeviceType["PetFeeder"] = "pet-feeder";
|
|
11326
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
11327
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
11328
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
11329
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
11206
11330
|
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
11207
11331
|
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
11208
11332
|
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
@@ -11473,7 +11597,7 @@ function errMsg(err) {
|
|
|
11473
11597
|
return String(err);
|
|
11474
11598
|
}
|
|
11475
11599
|
//#endregion
|
|
11476
|
-
//#region ../types/dist/composition-
|
|
11600
|
+
//#region ../types/dist/composition-kSuOifuG.mjs
|
|
11477
11601
|
/**
|
|
11478
11602
|
* Deterministic SHA-256 hash of an arbitrary serialisable value. The
|
|
11479
11603
|
* canonical form sorts object keys alphabetically at every depth so two
|
|
@@ -12718,12 +12842,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12718
12842
|
])
|
|
12719
12843
|
})
|
|
12720
12844
|
]);
|
|
12721
|
-
var
|
|
12845
|
+
var forwardArgUnion = union([
|
|
12846
|
+
string(),
|
|
12847
|
+
number(),
|
|
12848
|
+
boolean(),
|
|
12849
|
+
strictObject({ ["$input"]: string().min(1) }),
|
|
12850
|
+
record(string().min(1), lazy(() => forwardArgUnion))
|
|
12851
|
+
]);
|
|
12852
|
+
var CompositionForwardArgSchema = forwardArgUnion;
|
|
12853
|
+
/**
|
|
12854
|
+
* `forward` (D659, amended by D967): one method of a BUILT capability is one
|
|
12855
|
+
* call of `cap.method` on `source`. Without `args` the built method's input
|
|
12856
|
+
* passes through minus `deviceId`; with `args` the template IS the source's
|
|
12857
|
+
* whole input. `deviceId` is always the source's own, never an argument.
|
|
12858
|
+
*/
|
|
12859
|
+
var CompositionForwardCommandSchema = object({
|
|
12722
12860
|
kind: literal("forward"),
|
|
12723
12861
|
source: CompositionSourceRefSchema,
|
|
12724
12862
|
cap: string().min(1),
|
|
12725
|
-
method: string().min(1)
|
|
12726
|
-
|
|
12863
|
+
method: string().min(1),
|
|
12864
|
+
args: record(string().min(1), CompositionForwardArgSchema).optional()
|
|
12865
|
+
});
|
|
12866
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
|
|
12727
12867
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12728
12868
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12729
12869
|
/**
|
|
@@ -12733,7 +12873,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
|
12733
12873
|
*/
|
|
12734
12874
|
var CompositionItemEntrySchema = object({
|
|
12735
12875
|
label: string().min(1).max(80),
|
|
12736
|
-
fields: record(string().min(1), CompositionFieldSourceSchema)
|
|
12876
|
+
fields: record(string().min(1), CompositionFieldSourceSchema),
|
|
12877
|
+
/**
|
|
12878
|
+
* What the cap's `reset` does for THIS item (consumables). Present = the item
|
|
12879
|
+
* is resettable and a `reset` call naming its key forwards here. The item key
|
|
12880
|
+
* selects the command, so it is never an argument: there is no pass-through.
|
|
12881
|
+
*/
|
|
12882
|
+
reset: CompositionForwardCommandSchema.optional()
|
|
12737
12883
|
});
|
|
12738
12884
|
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
12739
12885
|
kind: literal("fields"),
|
|
@@ -12745,7 +12891,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
|
12745
12891
|
* owns the WHOLE array field: the composed value holds exactly these items.
|
|
12746
12892
|
*/
|
|
12747
12893
|
items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
|
|
12748
|
-
/**
|
|
12894
|
+
/** Method name → what serves it. Only a BUILT capability takes commands (D967). */
|
|
12749
12895
|
commands: record(string().min(1), CompositionCommandTargetSchema).optional()
|
|
12750
12896
|
}), object({
|
|
12751
12897
|
kind: literal("passthrough"),
|
|
@@ -16888,6 +17034,538 @@ object({
|
|
|
16888
17034
|
path: ["expr"]
|
|
16889
17035
|
});
|
|
16890
17036
|
});
|
|
17037
|
+
/**
|
|
17038
|
+
* Identity envelope for a device's upstream-system metadata.
|
|
17039
|
+
*
|
|
17040
|
+
* Two jobs:
|
|
17041
|
+
*
|
|
17042
|
+
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
17043
|
+
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
17044
|
+
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
17045
|
+
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
17046
|
+
* without round-tripping through `stableId` / `id` mappings.
|
|
17047
|
+
*
|
|
17048
|
+
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
17049
|
+
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
17050
|
+
* for vendors where MAC is the natural key). For systems where the
|
|
17051
|
+
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
17052
|
+
* providers detect renames by matching `uniqueId` and patch `id`
|
|
17053
|
+
* in-place so dispatch keeps working.
|
|
17054
|
+
*
|
|
17055
|
+
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
17056
|
+
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
17057
|
+
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
17058
|
+
* neither is repeated on each sample. The `device_class` tag is an
|
|
17059
|
+
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
17060
|
+
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
17061
|
+
*
|
|
17062
|
+
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
17063
|
+
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
17064
|
+
* details, …). Providers control what lands here.
|
|
17065
|
+
*/
|
|
17066
|
+
var SourceInfoSchema = object({
|
|
17067
|
+
/** Live dispatch key — mutable when the source system allows rename. */
|
|
17068
|
+
id: string(),
|
|
17069
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
17070
|
+
system: string(),
|
|
17071
|
+
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
17072
|
+
* Used to detect rename when `id` changes. */
|
|
17073
|
+
uniqueId: string().optional(),
|
|
17074
|
+
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
17075
|
+
quantity: ReadingQuantitySchema.optional(),
|
|
17076
|
+
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
17077
|
+
dateTimeShape: _enum([
|
|
17078
|
+
"date",
|
|
17079
|
+
"time",
|
|
17080
|
+
"datetime"
|
|
17081
|
+
]).optional(),
|
|
17082
|
+
/** Provider-specific extras that don't fit the structured slots.
|
|
17083
|
+
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
17084
|
+
* attribute blobs (would explode the meta JSON column). */
|
|
17085
|
+
raw: record(string(), unknown()).optional()
|
|
17086
|
+
});
|
|
17087
|
+
/**
|
|
17088
|
+
* Build the synthetic SourceInfo every existing provider falls back to
|
|
17089
|
+
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
17090
|
+
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
17091
|
+
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
17092
|
+
* id. Real upstream identity replaces this once a provider migrates and
|
|
17093
|
+
* calls `updateSourceInfo()` with concrete values.
|
|
17094
|
+
*/
|
|
17095
|
+
function synthesizeSourceInfo(input) {
|
|
17096
|
+
return {
|
|
17097
|
+
id: input.stableId,
|
|
17098
|
+
system: input.addonId
|
|
17099
|
+
};
|
|
17100
|
+
}
|
|
17101
|
+
/**
|
|
17102
|
+
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
17103
|
+
* patch values are ignored (no field clobber); to clear a field
|
|
17104
|
+
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
17105
|
+
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
17106
|
+
* bother with a `null`-means-delete sentinel.
|
|
17107
|
+
*
|
|
17108
|
+
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
17109
|
+
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
17110
|
+
*/
|
|
17111
|
+
function mergeSourceInfo(prev, patch) {
|
|
17112
|
+
const next = { ...prev };
|
|
17113
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
17114
|
+
if (v === void 0) continue;
|
|
17115
|
+
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
17116
|
+
next.raw = {
|
|
17117
|
+
...prev.raw,
|
|
17118
|
+
...v
|
|
17119
|
+
};
|
|
17120
|
+
continue;
|
|
17121
|
+
}
|
|
17122
|
+
next[k] = v;
|
|
17123
|
+
}
|
|
17124
|
+
return next;
|
|
17125
|
+
}
|
|
17126
|
+
/**
|
|
17127
|
+
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
17128
|
+
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
17129
|
+
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
17130
|
+
* lives under a single nested key to keep it composable with those.
|
|
17131
|
+
*/
|
|
17132
|
+
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
17133
|
+
/**
|
|
17134
|
+
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
17135
|
+
* with Zod. Returns `null` when the blob is missing or the nested
|
|
17136
|
+
* `sourceInfo` key fails validation — callers fall back to the
|
|
17137
|
+
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
17138
|
+
*/
|
|
17139
|
+
function extractSourceInfoFromMetadata(metadata) {
|
|
17140
|
+
if (!metadata) return null;
|
|
17141
|
+
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
17142
|
+
if (!raw) return null;
|
|
17143
|
+
const parsed = SourceInfoSchema.safeParse(raw);
|
|
17144
|
+
if (!parsed.success) return null;
|
|
17145
|
+
return parsed.data;
|
|
17146
|
+
}
|
|
17147
|
+
/**
|
|
17148
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
17149
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
17150
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
17151
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
17152
|
+
*/
|
|
17153
|
+
var UNIT_TABLE = {
|
|
17154
|
+
"°C": {
|
|
17155
|
+
dimension: "temperature",
|
|
17156
|
+
factor: 1,
|
|
17157
|
+
offset: 0
|
|
17158
|
+
},
|
|
17159
|
+
"°F": {
|
|
17160
|
+
dimension: "temperature",
|
|
17161
|
+
factor: 5 / 9,
|
|
17162
|
+
offset: -160 / 9
|
|
17163
|
+
},
|
|
17164
|
+
K: {
|
|
17165
|
+
dimension: "temperature",
|
|
17166
|
+
factor: 1,
|
|
17167
|
+
offset: -273.15
|
|
17168
|
+
},
|
|
17169
|
+
hPa: {
|
|
17170
|
+
dimension: "pressure",
|
|
17171
|
+
factor: 1,
|
|
17172
|
+
offset: 0
|
|
17173
|
+
},
|
|
17174
|
+
kPa: {
|
|
17175
|
+
dimension: "pressure",
|
|
17176
|
+
factor: 10,
|
|
17177
|
+
offset: 0
|
|
17178
|
+
},
|
|
17179
|
+
Pa: {
|
|
17180
|
+
dimension: "pressure",
|
|
17181
|
+
factor: .01,
|
|
17182
|
+
offset: 0
|
|
17183
|
+
},
|
|
17184
|
+
mbar: {
|
|
17185
|
+
dimension: "pressure",
|
|
17186
|
+
factor: 1,
|
|
17187
|
+
offset: 0
|
|
17188
|
+
},
|
|
17189
|
+
bar: {
|
|
17190
|
+
dimension: "pressure",
|
|
17191
|
+
factor: 1e3,
|
|
17192
|
+
offset: 0
|
|
17193
|
+
},
|
|
17194
|
+
inHg: {
|
|
17195
|
+
dimension: "pressure",
|
|
17196
|
+
factor: 33.8639,
|
|
17197
|
+
offset: 0
|
|
17198
|
+
},
|
|
17199
|
+
mmHg: {
|
|
17200
|
+
dimension: "pressure",
|
|
17201
|
+
factor: 1.33322,
|
|
17202
|
+
offset: 0
|
|
17203
|
+
},
|
|
17204
|
+
psi: {
|
|
17205
|
+
dimension: "pressure",
|
|
17206
|
+
factor: 68.9476,
|
|
17207
|
+
offset: 0
|
|
17208
|
+
},
|
|
17209
|
+
"m/s": {
|
|
17210
|
+
dimension: "speed",
|
|
17211
|
+
factor: 1,
|
|
17212
|
+
offset: 0
|
|
17213
|
+
},
|
|
17214
|
+
"km/h": {
|
|
17215
|
+
dimension: "speed",
|
|
17216
|
+
factor: 1 / 3.6,
|
|
17217
|
+
offset: 0
|
|
17218
|
+
},
|
|
17219
|
+
mph: {
|
|
17220
|
+
dimension: "speed",
|
|
17221
|
+
factor: .44704,
|
|
17222
|
+
offset: 0
|
|
17223
|
+
},
|
|
17224
|
+
kn: {
|
|
17225
|
+
dimension: "speed",
|
|
17226
|
+
factor: .514444,
|
|
17227
|
+
offset: 0
|
|
17228
|
+
},
|
|
17229
|
+
"mm/h": {
|
|
17230
|
+
dimension: "precipitation-rate",
|
|
17231
|
+
factor: 1,
|
|
17232
|
+
offset: 0
|
|
17233
|
+
},
|
|
17234
|
+
"in/h": {
|
|
17235
|
+
dimension: "precipitation-rate",
|
|
17236
|
+
factor: 25.4,
|
|
17237
|
+
offset: 0
|
|
17238
|
+
},
|
|
17239
|
+
m: {
|
|
17240
|
+
dimension: "length",
|
|
17241
|
+
factor: 1,
|
|
17242
|
+
offset: 0
|
|
17243
|
+
},
|
|
17244
|
+
mm: {
|
|
17245
|
+
dimension: "length",
|
|
17246
|
+
factor: .001,
|
|
17247
|
+
offset: 0
|
|
17248
|
+
},
|
|
17249
|
+
cm: {
|
|
17250
|
+
dimension: "length",
|
|
17251
|
+
factor: .01,
|
|
17252
|
+
offset: 0
|
|
17253
|
+
},
|
|
17254
|
+
km: {
|
|
17255
|
+
dimension: "length",
|
|
17256
|
+
factor: 1e3,
|
|
17257
|
+
offset: 0
|
|
17258
|
+
},
|
|
17259
|
+
in: {
|
|
17260
|
+
dimension: "length",
|
|
17261
|
+
factor: .0254,
|
|
17262
|
+
offset: 0
|
|
17263
|
+
},
|
|
17264
|
+
ft: {
|
|
17265
|
+
dimension: "length",
|
|
17266
|
+
factor: .3048,
|
|
17267
|
+
offset: 0
|
|
17268
|
+
},
|
|
17269
|
+
mi: {
|
|
17270
|
+
dimension: "length",
|
|
17271
|
+
factor: 1609.344,
|
|
17272
|
+
offset: 0
|
|
17273
|
+
},
|
|
17274
|
+
"m²": {
|
|
17275
|
+
dimension: "area",
|
|
17276
|
+
factor: 1,
|
|
17277
|
+
offset: 0
|
|
17278
|
+
},
|
|
17279
|
+
lx: {
|
|
17280
|
+
dimension: "illuminance",
|
|
17281
|
+
factor: 1,
|
|
17282
|
+
offset: 0
|
|
17283
|
+
},
|
|
17284
|
+
"W/m²": {
|
|
17285
|
+
dimension: "irradiance",
|
|
17286
|
+
factor: 1,
|
|
17287
|
+
offset: 0
|
|
17288
|
+
},
|
|
17289
|
+
W: {
|
|
17290
|
+
dimension: "power",
|
|
17291
|
+
factor: 1,
|
|
17292
|
+
offset: 0
|
|
17293
|
+
},
|
|
17294
|
+
kW: {
|
|
17295
|
+
dimension: "power",
|
|
17296
|
+
factor: 1e3,
|
|
17297
|
+
offset: 0
|
|
17298
|
+
},
|
|
17299
|
+
VA: {
|
|
17300
|
+
dimension: "apparent-power",
|
|
17301
|
+
factor: 1,
|
|
17302
|
+
offset: 0
|
|
17303
|
+
},
|
|
17304
|
+
Wh: {
|
|
17305
|
+
dimension: "energy",
|
|
17306
|
+
factor: 1,
|
|
17307
|
+
offset: 0
|
|
17308
|
+
},
|
|
17309
|
+
kWh: {
|
|
17310
|
+
dimension: "energy",
|
|
17311
|
+
factor: 1e3,
|
|
17312
|
+
offset: 0
|
|
17313
|
+
},
|
|
17314
|
+
MWh: {
|
|
17315
|
+
dimension: "energy",
|
|
17316
|
+
factor: 1e6,
|
|
17317
|
+
offset: 0
|
|
17318
|
+
},
|
|
17319
|
+
V: {
|
|
17320
|
+
dimension: "voltage",
|
|
17321
|
+
factor: 1,
|
|
17322
|
+
offset: 0
|
|
17323
|
+
},
|
|
17324
|
+
mV: {
|
|
17325
|
+
dimension: "voltage",
|
|
17326
|
+
factor: .001,
|
|
17327
|
+
offset: 0
|
|
17328
|
+
},
|
|
17329
|
+
A: {
|
|
17330
|
+
dimension: "current",
|
|
17331
|
+
factor: 1,
|
|
17332
|
+
offset: 0
|
|
17333
|
+
},
|
|
17334
|
+
mA: {
|
|
17335
|
+
dimension: "current",
|
|
17336
|
+
factor: .001,
|
|
17337
|
+
offset: 0
|
|
17338
|
+
},
|
|
17339
|
+
ppm: {
|
|
17340
|
+
dimension: "concentration-volume",
|
|
17341
|
+
factor: 1,
|
|
17342
|
+
offset: 0
|
|
17343
|
+
},
|
|
17344
|
+
ppb: {
|
|
17345
|
+
dimension: "concentration-volume",
|
|
17346
|
+
factor: .001,
|
|
17347
|
+
offset: 0
|
|
17348
|
+
},
|
|
17349
|
+
"µg/m³": {
|
|
17350
|
+
dimension: "concentration-mass",
|
|
17351
|
+
factor: 1,
|
|
17352
|
+
offset: 0
|
|
17353
|
+
},
|
|
17354
|
+
"mg/m³": {
|
|
17355
|
+
dimension: "concentration-mass",
|
|
17356
|
+
factor: 1e3,
|
|
17357
|
+
offset: 0
|
|
17358
|
+
},
|
|
17359
|
+
kg: {
|
|
17360
|
+
dimension: "mass",
|
|
17361
|
+
factor: 1,
|
|
17362
|
+
offset: 0
|
|
17363
|
+
},
|
|
17364
|
+
g: {
|
|
17365
|
+
dimension: "mass",
|
|
17366
|
+
factor: .001,
|
|
17367
|
+
offset: 0
|
|
17368
|
+
},
|
|
17369
|
+
lb: {
|
|
17370
|
+
dimension: "mass",
|
|
17371
|
+
factor: .453592,
|
|
17372
|
+
offset: 0
|
|
17373
|
+
},
|
|
17374
|
+
oz: {
|
|
17375
|
+
dimension: "mass",
|
|
17376
|
+
factor: .0283495,
|
|
17377
|
+
offset: 0
|
|
17378
|
+
},
|
|
17379
|
+
"%": {
|
|
17380
|
+
dimension: "percentage",
|
|
17381
|
+
factor: 1,
|
|
17382
|
+
offset: 0
|
|
17383
|
+
},
|
|
17384
|
+
L: {
|
|
17385
|
+
dimension: "volume",
|
|
17386
|
+
factor: 1,
|
|
17387
|
+
offset: 0
|
|
17388
|
+
},
|
|
17389
|
+
mL: {
|
|
17390
|
+
dimension: "volume",
|
|
17391
|
+
factor: .001,
|
|
17392
|
+
offset: 0
|
|
17393
|
+
},
|
|
17394
|
+
gal: {
|
|
17395
|
+
dimension: "volume",
|
|
17396
|
+
factor: 3.785411784,
|
|
17397
|
+
offset: 0
|
|
17398
|
+
},
|
|
17399
|
+
s: {
|
|
17400
|
+
dimension: "duration",
|
|
17401
|
+
factor: 1,
|
|
17402
|
+
offset: 0
|
|
17403
|
+
},
|
|
17404
|
+
ms: {
|
|
17405
|
+
dimension: "duration",
|
|
17406
|
+
factor: .001,
|
|
17407
|
+
offset: 0
|
|
17408
|
+
},
|
|
17409
|
+
min: {
|
|
17410
|
+
dimension: "duration",
|
|
17411
|
+
factor: 60,
|
|
17412
|
+
offset: 0
|
|
17413
|
+
},
|
|
17414
|
+
h: {
|
|
17415
|
+
dimension: "duration",
|
|
17416
|
+
factor: 3600,
|
|
17417
|
+
offset: 0
|
|
17418
|
+
},
|
|
17419
|
+
d: {
|
|
17420
|
+
dimension: "duration",
|
|
17421
|
+
factor: 86400,
|
|
17422
|
+
offset: 0
|
|
17423
|
+
},
|
|
17424
|
+
B: {
|
|
17425
|
+
dimension: "information",
|
|
17426
|
+
factor: 1,
|
|
17427
|
+
offset: 0
|
|
17428
|
+
},
|
|
17429
|
+
KiB: {
|
|
17430
|
+
dimension: "information",
|
|
17431
|
+
factor: 1024,
|
|
17432
|
+
offset: 0
|
|
17433
|
+
},
|
|
17434
|
+
MiB: {
|
|
17435
|
+
dimension: "information",
|
|
17436
|
+
factor: 1024 ** 2,
|
|
17437
|
+
offset: 0
|
|
17438
|
+
},
|
|
17439
|
+
GiB: {
|
|
17440
|
+
dimension: "information",
|
|
17441
|
+
factor: 1024 ** 3,
|
|
17442
|
+
offset: 0
|
|
17443
|
+
},
|
|
17444
|
+
TiB: {
|
|
17445
|
+
dimension: "information",
|
|
17446
|
+
factor: 1024 ** 4,
|
|
17447
|
+
offset: 0
|
|
17448
|
+
},
|
|
17449
|
+
kB: {
|
|
17450
|
+
dimension: "information",
|
|
17451
|
+
factor: 1e3,
|
|
17452
|
+
offset: 0
|
|
17453
|
+
},
|
|
17454
|
+
MB: {
|
|
17455
|
+
dimension: "information",
|
|
17456
|
+
factor: 1e6,
|
|
17457
|
+
offset: 0
|
|
17458
|
+
},
|
|
17459
|
+
GB: {
|
|
17460
|
+
dimension: "information",
|
|
17461
|
+
factor: 1e9,
|
|
17462
|
+
offset: 0
|
|
17463
|
+
},
|
|
17464
|
+
"°": {
|
|
17465
|
+
dimension: "angle",
|
|
17466
|
+
factor: 1,
|
|
17467
|
+
offset: 0
|
|
17468
|
+
},
|
|
17469
|
+
dBFS: {
|
|
17470
|
+
dimension: "sound-level",
|
|
17471
|
+
factor: 1,
|
|
17472
|
+
offset: 0
|
|
17473
|
+
},
|
|
17474
|
+
dBm: {
|
|
17475
|
+
dimension: "signal-level",
|
|
17476
|
+
factor: 1,
|
|
17477
|
+
offset: 0
|
|
17478
|
+
},
|
|
17479
|
+
mired: {
|
|
17480
|
+
dimension: "colour-temperature",
|
|
17481
|
+
factor: 1,
|
|
17482
|
+
offset: 0
|
|
17483
|
+
},
|
|
17484
|
+
kbps: {
|
|
17485
|
+
dimension: "bitrate",
|
|
17486
|
+
factor: 1,
|
|
17487
|
+
offset: 0
|
|
17488
|
+
},
|
|
17489
|
+
Mbps: {
|
|
17490
|
+
dimension: "bitrate",
|
|
17491
|
+
factor: 1e3,
|
|
17492
|
+
offset: 0
|
|
17493
|
+
},
|
|
17494
|
+
fps: {
|
|
17495
|
+
dimension: "rate",
|
|
17496
|
+
factor: 1,
|
|
17497
|
+
offset: 0
|
|
17498
|
+
}
|
|
17499
|
+
};
|
|
17500
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
17501
|
+
function specFor(unit) {
|
|
17502
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
17503
|
+
}
|
|
17504
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
17505
|
+
function toCanonical(value, spec) {
|
|
17506
|
+
return value * spec.factor + spec.offset;
|
|
17507
|
+
}
|
|
17508
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
17509
|
+
function fromCanonical(canonical, spec) {
|
|
17510
|
+
return (canonical - spec.offset) / spec.factor;
|
|
17511
|
+
}
|
|
17512
|
+
/**
|
|
17513
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
17514
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
17515
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
17516
|
+
*/
|
|
17517
|
+
function tryConvertUnit(value, from, to) {
|
|
17518
|
+
if (from === to) return value;
|
|
17519
|
+
const fromSpec = specFor(from);
|
|
17520
|
+
if (!fromSpec) return void 0;
|
|
17521
|
+
const toSpec = specFor(to);
|
|
17522
|
+
if (!toSpec) return void 0;
|
|
17523
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
17524
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
17525
|
+
}
|
|
17526
|
+
/**
|
|
17527
|
+
* A number a person types for a physical quantity — the input side of the
|
|
17528
|
+
* value + unit contract (D972).
|
|
17529
|
+
*
|
|
17530
|
+
* Readings are stored in the base unit of their quantity (D944); a cap method
|
|
17531
|
+
* param is stored in the unit its input schema DECLARES (`paramMeta`). The
|
|
17532
|
+
* wire pair {@link QuantityValue} says "this number is expressed in this
|
|
17533
|
+
* unit"; {@link convertQuantityInput} brings it to the stored unit and refuses,
|
|
17534
|
+
* with a typed {@link QuantityUnitError}, a unit that cannot be that quantity.
|
|
17535
|
+
*/
|
|
17536
|
+
/** A unit that cannot be applied to the number it was given with. */
|
|
17537
|
+
var QuantityUnitError = class extends Error {
|
|
17538
|
+
code;
|
|
17539
|
+
constructor(code, message) {
|
|
17540
|
+
super(`[${code}] ${message}`);
|
|
17541
|
+
this.name = "QuantityUnitError";
|
|
17542
|
+
this.code = code;
|
|
17543
|
+
}
|
|
17544
|
+
};
|
|
17545
|
+
/** The wire pair: `value` is expressed in `unit`. */
|
|
17546
|
+
var QuantityValueSchema = object({
|
|
17547
|
+
value: number(),
|
|
17548
|
+
unit: string().min(1)
|
|
17549
|
+
});
|
|
17550
|
+
/**
|
|
17551
|
+
* Zod `.meta()` bag for a cap METHOD param that is a physical quantity — the
|
|
17552
|
+
* input-side twin of `readingMeta`. The value is carried in `unit` (a gram
|
|
17553
|
+
* portion stays in grams); the unit must belong to the quantity, checked once
|
|
17554
|
+
* when the cap is declared.
|
|
17555
|
+
*/
|
|
17556
|
+
function paramMeta(declared) {
|
|
17557
|
+
const base = baseUnitOf(declared.quantity);
|
|
17558
|
+
if (base === void 0) throw new QuantityUnitError("unknown-unit", `quantity "${declared.quantity}" has no unit`);
|
|
17559
|
+
if (tryConvertUnit(1, declared.unit, base) === void 0) throw new QuantityUnitError("cross-dimension", `"${declared.unit}" is not a unit of ${declared.quantity}`);
|
|
17560
|
+
return {
|
|
17561
|
+
quantity: declared.quantity,
|
|
17562
|
+
unit: declared.unit
|
|
17563
|
+
};
|
|
17564
|
+
}
|
|
17565
|
+
object({
|
|
17566
|
+
quantity: ReadingQuantitySchema,
|
|
17567
|
+
unit: string().min(1)
|
|
17568
|
+
});
|
|
16891
17569
|
/** How a leaf compares a device field to a value. Derived from the field's
|
|
16892
17570
|
* `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
16893
17571
|
var AutomationConditionOperatorSchema = _enum([
|
|
@@ -16910,8 +17588,10 @@ var AutomationConditionLeafSchema = object({
|
|
|
16910
17588
|
string(),
|
|
16911
17589
|
number(),
|
|
16912
17590
|
boolean(),
|
|
16913
|
-
array(union([string(), number()]))
|
|
16914
|
-
|
|
17591
|
+
array(union([string(), number()])),
|
|
17592
|
+
QuantityValueSchema
|
|
17593
|
+
]),
|
|
17594
|
+
unit: string().min(1).optional()
|
|
16915
17595
|
});
|
|
16916
17596
|
var AutomationConditionStationaryPresentSchema = object({
|
|
16917
17597
|
kind: literal("stationary-present"),
|
|
@@ -17064,7 +17744,13 @@ var AutomationCapStepSchema = object({
|
|
|
17064
17744
|
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
17065
17745
|
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
17066
17746
|
* the expression leaf. */
|
|
17067
|
-
args: record(string(), unknown()).optional()
|
|
17747
|
+
args: record(string(), unknown()).optional(),
|
|
17748
|
+
/**
|
|
17749
|
+
* The unit the person picked per arg (display only). `args` always hold the
|
|
17750
|
+
* value in the unit the method's input DECLARES (`paramMeta`); on a write an
|
|
17751
|
+
* arg may be a {@link QuantityValue}, which the save boundary converts.
|
|
17752
|
+
*/
|
|
17753
|
+
argUnits: record(string(), string().min(1)).optional()
|
|
17068
17754
|
});
|
|
17069
17755
|
/**
|
|
17070
17756
|
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
@@ -17466,6 +18152,437 @@ object({
|
|
|
17466
18152
|
lastRunAt: number().nullable(),
|
|
17467
18153
|
lastError: string().nullable()
|
|
17468
18154
|
});
|
|
18155
|
+
/** A bare number accepted as a base unit — the save succeeded, say so. */
|
|
18156
|
+
var UnitWarningSchema = object({
|
|
18157
|
+
/** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
|
|
18158
|
+
path: string(),
|
|
18159
|
+
deviceId: number().int().optional(),
|
|
18160
|
+
field: string(),
|
|
18161
|
+
value: number(),
|
|
18162
|
+
assumedUnit: string(),
|
|
18163
|
+
message: string()
|
|
18164
|
+
});
|
|
18165
|
+
/**
|
|
18166
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
18167
|
+
*
|
|
18168
|
+
* Apprise-derived model (see
|
|
18169
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
18170
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
18171
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
18172
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
18173
|
+
* message to what the kind supports — callers never special-case a service.
|
|
18174
|
+
*
|
|
18175
|
+
* DESIGN DECISIONS (locked):
|
|
18176
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
18177
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
18178
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
18179
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
18180
|
+
* alternative would fork the UI per addon and cannot host the
|
|
18181
|
+
* discovery→adopt flow.
|
|
18182
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
18183
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
18184
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
18185
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
18186
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
18187
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
18188
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
18189
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
18190
|
+
* base64 fallback needed.
|
|
18191
|
+
*
|
|
18192
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
18193
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
18194
|
+
* admin "Integrations" page.
|
|
18195
|
+
*/
|
|
18196
|
+
/**
|
|
18197
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
18198
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
18199
|
+
*/
|
|
18200
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
18201
|
+
"image",
|
|
18202
|
+
"video",
|
|
18203
|
+
"gif",
|
|
18204
|
+
"audio",
|
|
18205
|
+
"icon"
|
|
18206
|
+
]);
|
|
18207
|
+
/**
|
|
18208
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
18209
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
18210
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
18211
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
18212
|
+
*/
|
|
18213
|
+
var AttachmentSchema = object({
|
|
18214
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
18215
|
+
url: string().optional(),
|
|
18216
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
18217
|
+
mime: string().optional(),
|
|
18218
|
+
name: string().optional()
|
|
18219
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
18220
|
+
var NotificationFormatSchema = _enum([
|
|
18221
|
+
"text",
|
|
18222
|
+
"markdown",
|
|
18223
|
+
"html"
|
|
18224
|
+
]);
|
|
18225
|
+
/**
|
|
18226
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
18227
|
+
*
|
|
18228
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
18229
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
18230
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
18231
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
18232
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
18233
|
+
*
|
|
18234
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
18235
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
18236
|
+
* differently.
|
|
18237
|
+
*
|
|
18238
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
18239
|
+
*
|
|
18240
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
18241
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
18242
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
18243
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
18244
|
+
* decides its glyph, which is the only place that decision can be made
|
|
18245
|
+
* honestly.
|
|
18246
|
+
*
|
|
18247
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
18248
|
+
* `disarm` straight through; iOS feeds that string to
|
|
18249
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
18250
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
18251
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
18252
|
+
*
|
|
18253
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
18254
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
18255
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
18256
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
18257
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
18258
|
+
*/
|
|
18259
|
+
var NotificationActionIconSchema = _enum([
|
|
18260
|
+
"acknowledge",
|
|
18261
|
+
"dismiss",
|
|
18262
|
+
"silence",
|
|
18263
|
+
"snooze",
|
|
18264
|
+
"view",
|
|
18265
|
+
"play",
|
|
18266
|
+
"open",
|
|
18267
|
+
"close",
|
|
18268
|
+
"lock",
|
|
18269
|
+
"unlock",
|
|
18270
|
+
"arm",
|
|
18271
|
+
"arm-home",
|
|
18272
|
+
"arm-away",
|
|
18273
|
+
"arm-night",
|
|
18274
|
+
"disarm",
|
|
18275
|
+
"light",
|
|
18276
|
+
"alert",
|
|
18277
|
+
"camera"
|
|
18278
|
+
]);
|
|
18279
|
+
/** A single tap-through action button. */
|
|
18280
|
+
var NotificationActionSchema = object({
|
|
18281
|
+
id: string(),
|
|
18282
|
+
label: string(),
|
|
18283
|
+
url: string().optional(),
|
|
18284
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
18285
|
+
icon: NotificationActionIconSchema.optional(),
|
|
18286
|
+
/**
|
|
18287
|
+
* Renders in a warning style where the notifier supports it.
|
|
18288
|
+
*
|
|
18289
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
18290
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
18291
|
+
* does not buy.
|
|
18292
|
+
*/
|
|
18293
|
+
destructive: boolean().optional(),
|
|
18294
|
+
/**
|
|
18295
|
+
* How the tap should REACH the url.
|
|
18296
|
+
*
|
|
18297
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
18298
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
18299
|
+
* That is right for a button whose answer the operator wants to read.
|
|
18300
|
+
*
|
|
18301
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
18302
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
18303
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
18304
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
18305
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
18306
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
18307
|
+
* a requirement.
|
|
18308
|
+
*/
|
|
18309
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
18310
|
+
});
|
|
18311
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
18312
|
+
var DeliveryParamValueSchema = union([
|
|
18313
|
+
string(),
|
|
18314
|
+
number(),
|
|
18315
|
+
boolean()
|
|
18316
|
+
]);
|
|
18317
|
+
/**
|
|
18318
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
18319
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
18320
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
18321
|
+
* no editor knows a service by name.
|
|
18322
|
+
*
|
|
18323
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
18324
|
+
* run on the addon cap path).
|
|
18325
|
+
*/
|
|
18326
|
+
var DeliveryParamSchema = object({
|
|
18327
|
+
key: string().min(1),
|
|
18328
|
+
label: string(),
|
|
18329
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
18330
|
+
labelKey: string().optional(),
|
|
18331
|
+
type: _enum([
|
|
18332
|
+
"enum",
|
|
18333
|
+
"number",
|
|
18334
|
+
"boolean",
|
|
18335
|
+
"string"
|
|
18336
|
+
]),
|
|
18337
|
+
/** `enum` only: the closed set of accepted values. */
|
|
18338
|
+
options: array(object({
|
|
18339
|
+
value: string(),
|
|
18340
|
+
label: string()
|
|
18341
|
+
})).optional(),
|
|
18342
|
+
/** `number` only. */
|
|
18343
|
+
min: number().optional(),
|
|
18344
|
+
max: number().optional(),
|
|
18345
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
18346
|
+
default: DeliveryParamValueSchema.optional()
|
|
18347
|
+
});
|
|
18348
|
+
/**
|
|
18349
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
18350
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
18351
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
18352
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
18353
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
18354
|
+
* `priority` for that one target.
|
|
18355
|
+
*/
|
|
18356
|
+
var NotificationSchema = object({
|
|
18357
|
+
body: string(),
|
|
18358
|
+
title: string().optional(),
|
|
18359
|
+
format: NotificationFormatSchema.default("text"),
|
|
18360
|
+
priority: number().int().min(1).max(5).default(3),
|
|
18361
|
+
level: string().optional(),
|
|
18362
|
+
attachments: array(AttachmentSchema).optional(),
|
|
18363
|
+
clickUrl: string().optional(),
|
|
18364
|
+
actions: array(NotificationActionSchema).optional(),
|
|
18365
|
+
sound: string().optional(),
|
|
18366
|
+
ttl: number().optional(),
|
|
18367
|
+
tag: string().optional(),
|
|
18368
|
+
deviceId: number().optional(),
|
|
18369
|
+
eventId: string().optional(),
|
|
18370
|
+
metadata: record(string(), unknown()).optional(),
|
|
18371
|
+
/**
|
|
18372
|
+
* Values for the parameters the target's kind DECLARES in
|
|
18373
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
18374
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
18375
|
+
*/
|
|
18376
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
18377
|
+
});
|
|
18378
|
+
/** One declared native severity/priority level for a kind. */
|
|
18379
|
+
var TargetKindLevelSchema = object({
|
|
18380
|
+
id: string(),
|
|
18381
|
+
label: string(),
|
|
18382
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
18383
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
18384
|
+
flags: object({
|
|
18385
|
+
critical: boolean().optional(),
|
|
18386
|
+
silent: boolean().optional(),
|
|
18387
|
+
noPush: boolean().optional()
|
|
18388
|
+
}).optional(),
|
|
18389
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
18390
|
+
requires: array(string()).optional(),
|
|
18391
|
+
description: string().optional()
|
|
18392
|
+
});
|
|
18393
|
+
/** The full capability block consulted before dispatch. */
|
|
18394
|
+
var TargetKindCapsSchema = object({
|
|
18395
|
+
attachments: object({
|
|
18396
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
18397
|
+
mode: _enum([
|
|
18398
|
+
"url",
|
|
18399
|
+
"bytes",
|
|
18400
|
+
"both"
|
|
18401
|
+
]),
|
|
18402
|
+
max: number().int().nonnegative(),
|
|
18403
|
+
maxBytes: number().int().positive().optional()
|
|
18404
|
+
}),
|
|
18405
|
+
/** Max action buttons (0 = none). */
|
|
18406
|
+
actions: number().int().nonnegative(),
|
|
18407
|
+
/**
|
|
18408
|
+
* Whether this kind renders a per-action ICON.
|
|
18409
|
+
*
|
|
18410
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
18411
|
+
* run on the addon cap path — three production failures in one day taught
|
|
18412
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
18413
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
18414
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
18415
|
+
*/
|
|
18416
|
+
actionIcons: boolean().optional(),
|
|
18417
|
+
levels: array(TargetKindLevelSchema),
|
|
18418
|
+
format: array(NotificationFormatSchema),
|
|
18419
|
+
clickUrl: boolean(),
|
|
18420
|
+
sound: boolean(),
|
|
18421
|
+
ttl: boolean(),
|
|
18422
|
+
bodyMaxLen: number().int().positive()
|
|
18423
|
+
});
|
|
18424
|
+
/**
|
|
18425
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
18426
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
18427
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
18428
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
18429
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
18430
|
+
*/
|
|
18431
|
+
var ConfigSchemaPassthrough$3 = unknown();
|
|
18432
|
+
var TargetKindSchema = object({
|
|
18433
|
+
kind: string(),
|
|
18434
|
+
label: string(),
|
|
18435
|
+
icon: string(),
|
|
18436
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
18437
|
+
addonId: string(),
|
|
18438
|
+
/**
|
|
18439
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
18440
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
18441
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
18442
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
18443
|
+
*
|
|
18444
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
18445
|
+
* client, and a native client joins it onto its own hub base.
|
|
18446
|
+
*
|
|
18447
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
18448
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
18449
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
18450
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
18451
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
18452
|
+
*/
|
|
18453
|
+
iconUrl: string().optional(),
|
|
18454
|
+
/**
|
|
18455
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
18456
|
+
*
|
|
18457
|
+
* The server knows this and therefore says it, because the client cannot
|
|
18458
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
18459
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
18460
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
18461
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
18462
|
+
*
|
|
18463
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
18464
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
18465
|
+
* its raster path, which is the safe default for an unknown image.
|
|
18466
|
+
*/
|
|
18467
|
+
iconMediaType: string().optional(),
|
|
18468
|
+
configSchema: ConfigSchemaPassthrough$3,
|
|
18469
|
+
/**
|
|
18470
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
18471
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
18472
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
18473
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
18474
|
+
* on every edit.
|
|
18475
|
+
*
|
|
18476
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
18477
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
18478
|
+
* it absent must assume ANY edit may need the secret.
|
|
18479
|
+
*/
|
|
18480
|
+
destinationKeys: array(string()).optional(),
|
|
18481
|
+
supportsDiscovery: boolean(),
|
|
18482
|
+
/**
|
|
18483
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
18484
|
+
* Absent = none; the rule editor then offers nothing.
|
|
18485
|
+
*/
|
|
18486
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
18487
|
+
caps: TargetKindCapsSchema
|
|
18488
|
+
});
|
|
18489
|
+
/**
|
|
18490
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
18491
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
18492
|
+
* round-trip a stored secret to the UI.
|
|
18493
|
+
*/
|
|
18494
|
+
var TargetSchema = object({
|
|
18495
|
+
id: string(),
|
|
18496
|
+
name: string(),
|
|
18497
|
+
kind: string(),
|
|
18498
|
+
addonId: string(),
|
|
18499
|
+
enabled: boolean(),
|
|
18500
|
+
config: record(string(), unknown())
|
|
18501
|
+
});
|
|
18502
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
18503
|
+
var DiscoveredTargetSchema = object({
|
|
18504
|
+
kind: string(),
|
|
18505
|
+
suggestedName: string(),
|
|
18506
|
+
config: record(string(), unknown())
|
|
18507
|
+
});
|
|
18508
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
18509
|
+
var RenderedAsSchema = object({
|
|
18510
|
+
level: string(),
|
|
18511
|
+
format: NotificationFormatSchema,
|
|
18512
|
+
attachmentsSent: number().int().nonnegative(),
|
|
18513
|
+
actionsSent: number().int().nonnegative(),
|
|
18514
|
+
truncated: boolean(),
|
|
18515
|
+
dropped: array(string())
|
|
18516
|
+
});
|
|
18517
|
+
var SendResultSchema = object({
|
|
18518
|
+
success: boolean(),
|
|
18519
|
+
error: string().optional(),
|
|
18520
|
+
renderedAs: RenderedAsSchema.optional()
|
|
18521
|
+
});
|
|
18522
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
18523
|
+
var TestResultSchema = SendResultSchema;
|
|
18524
|
+
/**
|
|
18525
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
18526
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
18527
|
+
*/
|
|
18528
|
+
var ProbeTargetConfigResultSchema = object({
|
|
18529
|
+
ok: boolean(),
|
|
18530
|
+
resolvedUrl: string(),
|
|
18531
|
+
httpStatus: number().int().optional(),
|
|
18532
|
+
stateInstance: string().optional(),
|
|
18533
|
+
error: string().optional()
|
|
18534
|
+
});
|
|
18535
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
18536
|
+
kind: string(),
|
|
18537
|
+
config: record(string(), unknown()).optional(),
|
|
18538
|
+
/**
|
|
18539
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
18540
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
18541
|
+
* secrets server-side before dialling (same merge as upsert).
|
|
18542
|
+
*/
|
|
18543
|
+
targetId: string().optional()
|
|
18544
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
18545
|
+
kind: string(),
|
|
18546
|
+
config: record(string(), unknown()),
|
|
18547
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
18548
|
+
targetId: string().optional()
|
|
18549
|
+
}), ProbeTargetConfigResultSchema, {
|
|
18550
|
+
kind: "mutation",
|
|
18551
|
+
auth: "admin"
|
|
18552
|
+
}), method(object({
|
|
18553
|
+
targetId: string(),
|
|
18554
|
+
notification: NotificationSchema
|
|
18555
|
+
}), SendResultSchema, {
|
|
18556
|
+
kind: "mutation",
|
|
18557
|
+
auth: "admin"
|
|
18558
|
+
}), method(object({
|
|
18559
|
+
targetId: string(),
|
|
18560
|
+
sample: NotificationSchema.optional()
|
|
18561
|
+
}), TestResultSchema, {
|
|
18562
|
+
kind: "mutation",
|
|
18563
|
+
auth: "admin"
|
|
18564
|
+
}), method(object({
|
|
18565
|
+
target: TargetSchema,
|
|
18566
|
+
/**
|
|
18567
|
+
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
18568
|
+
* existing row. The UI never holds the stored token; blank / redacted
|
|
18569
|
+
* password fields are restored from this source (D634 still applies if
|
|
18570
|
+
* destinations diverge without a re-typed secret).
|
|
18571
|
+
*/
|
|
18572
|
+
cloneSecretsFrom: string().optional()
|
|
18573
|
+
}), TargetSchema, {
|
|
18574
|
+
kind: "mutation",
|
|
18575
|
+
auth: "admin"
|
|
18576
|
+
}), method(object({ targetId: string() }), _void(), {
|
|
18577
|
+
kind: "mutation",
|
|
18578
|
+
auth: "admin"
|
|
18579
|
+
}), method(object({
|
|
18580
|
+
targetId: string(),
|
|
18581
|
+
enabled: boolean()
|
|
18582
|
+
}), _void(), {
|
|
18583
|
+
kind: "mutation",
|
|
18584
|
+
auth: "admin"
|
|
18585
|
+
});
|
|
17469
18586
|
/**
|
|
17470
18587
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
17471
18588
|
* surface that admin-ui consumes through `useAddonPagesListPages()`.
|
|
@@ -17498,52 +18615,6 @@ var AddonPageInfoSchema = object({
|
|
|
17498
18615
|
bundleUrl: string()
|
|
17499
18616
|
});
|
|
17500
18617
|
method(_void(), array(AddonPageInfoSchema).readonly());
|
|
17501
|
-
/**
|
|
17502
|
-
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
17503
|
-
* declarations. Every addon that contributes a UI page registers a
|
|
17504
|
-
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
17505
|
-
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
17506
|
-
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
17507
|
-
* that admin-ui consumes.
|
|
17508
|
-
*
|
|
17509
|
-
* The split exists because the public listing has a different output
|
|
17510
|
-
* shape than the per-provider raw declarations, and we want both ends
|
|
17511
|
-
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
17512
|
-
*/
|
|
17513
|
-
var AddonPageDeclarationSchema = object({
|
|
17514
|
-
id: string(),
|
|
17515
|
-
label: string(),
|
|
17516
|
-
icon: string(),
|
|
17517
|
-
path: string(),
|
|
17518
|
-
/**
|
|
17519
|
-
* Module Federation remote name — must match the `name` field on the
|
|
17520
|
-
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
17521
|
-
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
17522
|
-
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
17523
|
-
* contain hyphens).
|
|
17524
|
-
*/
|
|
17525
|
-
remoteName: string(),
|
|
17526
|
-
/**
|
|
17527
|
-
* Bundle filename inside the addon's `dist/` dir served at
|
|
17528
|
-
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
17529
|
-
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
17530
|
-
* the static-file route can compute an mtime-based cache-buster URL
|
|
17531
|
-
* without a separate filesystem stat.
|
|
17532
|
-
*/
|
|
17533
|
-
bundle: string(),
|
|
17534
|
-
/**
|
|
17535
|
-
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
17536
|
-
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
17537
|
-
* Any OTHER string creates (or joins) a custom section rendered after
|
|
17538
|
-
* the built-in groups; its label comes from `sectionLabel` (first
|
|
17539
|
-
* declaration wins), falling back to the id. Absent → the legacy
|
|
17540
|
-
* "Addon Pages" group.
|
|
17541
|
-
*/
|
|
17542
|
-
section: string().optional(),
|
|
17543
|
-
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
17544
|
-
sectionLabel: string().optional()
|
|
17545
|
-
});
|
|
17546
|
-
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
17547
18618
|
var AddonHttpRouteSchema = object({
|
|
17548
18619
|
method: _enum([
|
|
17549
18620
|
"GET",
|
|
@@ -20215,7 +21286,11 @@ var CompositionProblemSchema = object({
|
|
|
20215
21286
|
"mixed-quantity",
|
|
20216
21287
|
"helper-mismatch",
|
|
20217
21288
|
"member-constraint",
|
|
20218
|
-
"retention"
|
|
21289
|
+
"retention",
|
|
21290
|
+
"command-method-unknown",
|
|
21291
|
+
"command-source-unknown",
|
|
21292
|
+
"command-input-unknown",
|
|
21293
|
+
"command-input-missing"
|
|
20219
21294
|
]),
|
|
20220
21295
|
path: string(),
|
|
20221
21296
|
message: string()
|
|
@@ -20254,7 +21329,9 @@ var CompositionFieldRoleSchema = _enum([
|
|
|
20254
21329
|
var CompositionFieldItemSchema = object({
|
|
20255
21330
|
arrayPath: string(),
|
|
20256
21331
|
key: string(),
|
|
20257
|
-
label: string()
|
|
21332
|
+
label: string(),
|
|
21333
|
+
/** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
|
|
21334
|
+
resettable: boolean().optional()
|
|
20258
21335
|
});
|
|
20259
21336
|
var CompositionFieldPlanSchema = object({
|
|
20260
21337
|
cap: string(),
|
|
@@ -20459,7 +21536,7 @@ Object.freeze({
|
|
|
20459
21536
|
* One catalog row per base reading a group helper can build (D944 §3).
|
|
20460
21537
|
* Data, not a form default written into the block.
|
|
20461
21538
|
*/
|
|
20462
|
-
var NOW$
|
|
21539
|
+
var NOW$3 = {
|
|
20463
21540
|
fieldPath: "lastFetchedAt",
|
|
20464
21541
|
expr: "now"
|
|
20465
21542
|
};
|
|
@@ -20511,7 +21588,7 @@ var GROUP_TEMPLATES = [
|
|
|
20511
21588
|
quantity: "temperature",
|
|
20512
21589
|
defaultAggregate: "mean",
|
|
20513
21590
|
empty: "null",
|
|
20514
|
-
companions: [NOW$
|
|
21591
|
+
companions: [NOW$3],
|
|
20515
21592
|
alsoFrom: {
|
|
20516
21593
|
deviceTypes: CLIMATE_DEVICES,
|
|
20517
21594
|
accept: {
|
|
@@ -20529,7 +21606,7 @@ var GROUP_TEMPLATES = [
|
|
|
20529
21606
|
quantity: "humidity",
|
|
20530
21607
|
defaultAggregate: "mean",
|
|
20531
21608
|
empty: "null",
|
|
20532
|
-
companions: [NOW$
|
|
21609
|
+
companions: [NOW$3],
|
|
20533
21610
|
alsoFrom: {
|
|
20534
21611
|
deviceTypes: CLIMATE_DEVICES,
|
|
20535
21612
|
accept: {
|
|
@@ -20547,7 +21624,7 @@ var GROUP_TEMPLATES = [
|
|
|
20547
21624
|
quantity: "pressure",
|
|
20548
21625
|
defaultAggregate: "mean",
|
|
20549
21626
|
empty: "null",
|
|
20550
|
-
companions: [NOW$
|
|
21627
|
+
companions: [NOW$3]
|
|
20551
21628
|
}),
|
|
20552
21629
|
numeric({
|
|
20553
21630
|
id: "illuminance",
|
|
@@ -20558,7 +21635,7 @@ var GROUP_TEMPLATES = [
|
|
|
20558
21635
|
quantity: "illuminance",
|
|
20559
21636
|
defaultAggregate: "mean",
|
|
20560
21637
|
empty: "null",
|
|
20561
|
-
companions: [NOW$
|
|
21638
|
+
companions: [NOW$3]
|
|
20562
21639
|
}),
|
|
20563
21640
|
numeric({
|
|
20564
21641
|
id: "power",
|
|
@@ -20569,7 +21646,7 @@ var GROUP_TEMPLATES = [
|
|
|
20569
21646
|
quantity: "power",
|
|
20570
21647
|
defaultAggregate: "sum",
|
|
20571
21648
|
empty: "omit",
|
|
20572
|
-
companions: [NOW$
|
|
21649
|
+
companions: [NOW$3]
|
|
20573
21650
|
}),
|
|
20574
21651
|
numeric({
|
|
20575
21652
|
id: "energy",
|
|
@@ -20580,7 +21657,7 @@ var GROUP_TEMPLATES = [
|
|
|
20580
21657
|
quantity: "energy",
|
|
20581
21658
|
defaultAggregate: "sum",
|
|
20582
21659
|
empty: "omit",
|
|
20583
|
-
companions: [NOW$
|
|
21660
|
+
companions: [NOW$3]
|
|
20584
21661
|
}),
|
|
20585
21662
|
numeric({
|
|
20586
21663
|
id: "battery",
|
|
@@ -20615,7 +21692,7 @@ var GROUP_TEMPLATES = [
|
|
|
20615
21692
|
quantity: "device",
|
|
20616
21693
|
defaultAggregate: "mean",
|
|
20617
21694
|
empty: "null",
|
|
20618
|
-
companions: [NOW$
|
|
21695
|
+
companions: [NOW$3]
|
|
20619
21696
|
}),
|
|
20620
21697
|
binary({
|
|
20621
21698
|
id: "motion",
|
|
@@ -22565,116 +23642,6 @@ var DeviceAdminLinkSchema = object({
|
|
|
22565
23642
|
});
|
|
22566
23643
|
method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
|
|
22567
23644
|
/**
|
|
22568
|
-
* Identity envelope for a device's upstream-system metadata.
|
|
22569
|
-
*
|
|
22570
|
-
* Two jobs:
|
|
22571
|
-
*
|
|
22572
|
-
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
22573
|
-
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
22574
|
-
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
22575
|
-
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
22576
|
-
* without round-tripping through `stableId` / `id` mappings.
|
|
22577
|
-
*
|
|
22578
|
-
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
22579
|
-
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
22580
|
-
* for vendors where MAC is the natural key). For systems where the
|
|
22581
|
-
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
22582
|
-
* providers detect renames by matching `uniqueId` and patch `id`
|
|
22583
|
-
* in-place so dispatch keeps working.
|
|
22584
|
-
*
|
|
22585
|
-
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
22586
|
-
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
22587
|
-
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
22588
|
-
* neither is repeated on each sample. The `device_class` tag is an
|
|
22589
|
-
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
22590
|
-
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
22591
|
-
*
|
|
22592
|
-
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
22593
|
-
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
22594
|
-
* details, …). Providers control what lands here.
|
|
22595
|
-
*/
|
|
22596
|
-
var SourceInfoSchema = object({
|
|
22597
|
-
/** Live dispatch key — mutable when the source system allows rename. */
|
|
22598
|
-
id: string(),
|
|
22599
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
22600
|
-
system: string(),
|
|
22601
|
-
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
22602
|
-
* Used to detect rename when `id` changes. */
|
|
22603
|
-
uniqueId: string().optional(),
|
|
22604
|
-
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
22605
|
-
quantity: ReadingQuantitySchema.optional(),
|
|
22606
|
-
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
22607
|
-
dateTimeShape: _enum([
|
|
22608
|
-
"date",
|
|
22609
|
-
"time",
|
|
22610
|
-
"datetime"
|
|
22611
|
-
]).optional(),
|
|
22612
|
-
/** Provider-specific extras that don't fit the structured slots.
|
|
22613
|
-
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
22614
|
-
* attribute blobs (would explode the meta JSON column). */
|
|
22615
|
-
raw: record(string(), unknown()).optional()
|
|
22616
|
-
});
|
|
22617
|
-
/**
|
|
22618
|
-
* Build the synthetic SourceInfo every existing provider falls back to
|
|
22619
|
-
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
22620
|
-
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
22621
|
-
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
22622
|
-
* id. Real upstream identity replaces this once a provider migrates and
|
|
22623
|
-
* calls `updateSourceInfo()` with concrete values.
|
|
22624
|
-
*/
|
|
22625
|
-
function synthesizeSourceInfo(input) {
|
|
22626
|
-
return {
|
|
22627
|
-
id: input.stableId,
|
|
22628
|
-
system: input.addonId
|
|
22629
|
-
};
|
|
22630
|
-
}
|
|
22631
|
-
/**
|
|
22632
|
-
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
22633
|
-
* patch values are ignored (no field clobber); to clear a field
|
|
22634
|
-
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
22635
|
-
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
22636
|
-
* bother with a `null`-means-delete sentinel.
|
|
22637
|
-
*
|
|
22638
|
-
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
22639
|
-
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
22640
|
-
*/
|
|
22641
|
-
function mergeSourceInfo(prev, patch) {
|
|
22642
|
-
const next = { ...prev };
|
|
22643
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
22644
|
-
if (v === void 0) continue;
|
|
22645
|
-
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
22646
|
-
next.raw = {
|
|
22647
|
-
...prev.raw,
|
|
22648
|
-
...v
|
|
22649
|
-
};
|
|
22650
|
-
continue;
|
|
22651
|
-
}
|
|
22652
|
-
next[k] = v;
|
|
22653
|
-
}
|
|
22654
|
-
return next;
|
|
22655
|
-
}
|
|
22656
|
-
/**
|
|
22657
|
-
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
22658
|
-
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
22659
|
-
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
22660
|
-
* lives under a single nested key to keep it composable with those.
|
|
22661
|
-
*/
|
|
22662
|
-
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
22663
|
-
/**
|
|
22664
|
-
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
22665
|
-
* with Zod. Returns `null` when the blob is missing or the nested
|
|
22666
|
-
* `sourceInfo` key fails validation — callers fall back to the
|
|
22667
|
-
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
22668
|
-
*/
|
|
22669
|
-
function extractSourceInfoFromMetadata(metadata) {
|
|
22670
|
-
if (!metadata) return null;
|
|
22671
|
-
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
22672
|
-
if (!raw) return null;
|
|
22673
|
-
const parsed = SourceInfoSchema.safeParse(raw);
|
|
22674
|
-
if (!parsed.success) return null;
|
|
22675
|
-
return parsed.data;
|
|
22676
|
-
}
|
|
22677
|
-
/**
|
|
22678
23645
|
* device-discovery — device-scoped capability for parents that host /
|
|
22679
23646
|
* enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
|
|
22680
23647
|
* integrations). The parent advertises a list of discoverable children,
|
|
@@ -23079,12 +24046,48 @@ var ExposeInputSchema = object({
|
|
|
23079
24046
|
capabilities: array(string()).optional()
|
|
23080
24047
|
});
|
|
23081
24048
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
24049
|
+
/**
|
|
24050
|
+
* What an exporter may expose besides devices (D968). An action and a
|
|
24051
|
+
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
24052
|
+
* exporter's own selection, not the device registry.
|
|
24053
|
+
*/
|
|
24054
|
+
var ExportEntityKindSchema = _enum([
|
|
24055
|
+
"device",
|
|
24056
|
+
"action",
|
|
24057
|
+
"notifier"
|
|
24058
|
+
]);
|
|
24059
|
+
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
24060
|
+
object({
|
|
24061
|
+
kind: literal("device"),
|
|
24062
|
+
deviceId: string()
|
|
24063
|
+
}),
|
|
24064
|
+
object({
|
|
24065
|
+
kind: literal("action"),
|
|
24066
|
+
actionId: string().min(1)
|
|
24067
|
+
}),
|
|
24068
|
+
object({
|
|
24069
|
+
kind: literal("notifier"),
|
|
24070
|
+
targetId: string().min(1)
|
|
24071
|
+
})
|
|
24072
|
+
]);
|
|
24073
|
+
var ExposedEntitySchema = object({
|
|
24074
|
+
entity: ExportEntityRefSchema,
|
|
24075
|
+
exposedAs: string().optional()
|
|
24076
|
+
});
|
|
24077
|
+
var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
24078
|
+
var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
23082
24079
|
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
23083
24080
|
kind: "mutation",
|
|
23084
24081
|
auth: "admin"
|
|
23085
24082
|
}), method(UnexposeInputSchema, _void(), {
|
|
23086
24083
|
kind: "mutation",
|
|
23087
24084
|
auth: "admin"
|
|
24085
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
24086
|
+
kind: "mutation",
|
|
24087
|
+
auth: "admin"
|
|
24088
|
+
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
24089
|
+
kind: "mutation",
|
|
24090
|
+
auth: "admin"
|
|
23088
24091
|
});
|
|
23089
24092
|
/** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
|
|
23090
24093
|
var ICON_CATEGORY_IDS = [
|
|
@@ -45150,7 +46153,7 @@ var LlmProviderWidgetSchema = object({
|
|
|
45150
46153
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
45151
46154
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
45152
46155
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
45153
|
-
var ConfigSchemaPassthrough$
|
|
46156
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
45154
46157
|
var LlmProfileKindDescriptorSchema = object({
|
|
45155
46158
|
kind: LlmProfileKindSchema,
|
|
45156
46159
|
label: string(),
|
|
@@ -45159,9 +46162,9 @@ var LlmProfileKindDescriptorSchema = object({
|
|
|
45159
46162
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
45160
46163
|
addonId: string(),
|
|
45161
46164
|
/** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
|
|
45162
|
-
connectionSchema: ConfigSchemaPassthrough$
|
|
46165
|
+
connectionSchema: ConfigSchemaPassthrough$2,
|
|
45163
46166
|
/** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
|
|
45164
|
-
advancedSchema: ConfigSchemaPassthrough$
|
|
46167
|
+
advancedSchema: ConfigSchemaPassthrough$2.optional(),
|
|
45165
46168
|
/** The kind's defaults; a model from `listModels` may override any of them. */
|
|
45166
46169
|
features: LlmModelFeaturesSchema,
|
|
45167
46170
|
/** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
|
|
@@ -45687,10 +46690,10 @@ var CpuBreakdownSchema = object({
|
|
|
45687
46690
|
cores: number()
|
|
45688
46691
|
});
|
|
45689
46692
|
var MemoryInfoSchema = object({
|
|
45690
|
-
percent: number(),
|
|
46693
|
+
percent: number().nullable(),
|
|
45691
46694
|
totalBytes: number(),
|
|
45692
|
-
usedBytes: number(),
|
|
45693
|
-
availableBytes: number(),
|
|
46695
|
+
usedBytes: number().nullable(),
|
|
46696
|
+
availableBytes: number().nullable(),
|
|
45694
46697
|
swapUsedBytes: number(),
|
|
45695
46698
|
swapTotalBytes: number()
|
|
45696
46699
|
});
|
|
@@ -45994,8 +46997,9 @@ var GetLoadSeriesInputSchema = object({
|
|
|
45994
46997
|
});
|
|
45995
46998
|
var SystemMetricsSchema = object({
|
|
45996
46999
|
cpuPercent: number(),
|
|
45997
|
-
|
|
45998
|
-
|
|
47000
|
+
/** `null` when the memory sample failed or was not taken yet (D393). */
|
|
47001
|
+
memoryPercent: number().nullable(),
|
|
47002
|
+
memoryUsedMB: number().nullable(),
|
|
45999
47003
|
memoryTotalMB: number(),
|
|
46000
47004
|
diskPercent: number().optional(),
|
|
46001
47005
|
temperature: number().optional(),
|
|
@@ -46231,427 +47235,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
46231
47235
|
sourcePort: number().optional()
|
|
46232
47236
|
});
|
|
46233
47237
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
46234
|
-
/**
|
|
46235
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
46236
|
-
*
|
|
46237
|
-
* Apprise-derived model (see
|
|
46238
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
46239
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
46240
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
46241
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
46242
|
-
* message to what the kind supports — callers never special-case a service.
|
|
46243
|
-
*
|
|
46244
|
-
* DESIGN DECISIONS (locked):
|
|
46245
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
46246
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
46247
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
46248
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
46249
|
-
* alternative would fork the UI per addon and cannot host the
|
|
46250
|
-
* discovery→adopt flow.
|
|
46251
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
46252
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
46253
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
46254
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
46255
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
46256
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
46257
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
46258
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
46259
|
-
* base64 fallback needed.
|
|
46260
|
-
*
|
|
46261
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
46262
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
46263
|
-
* admin "Integrations" page.
|
|
46264
|
-
*/
|
|
46265
|
-
/**
|
|
46266
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
46267
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
46268
|
-
*/
|
|
46269
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
46270
|
-
"image",
|
|
46271
|
-
"video",
|
|
46272
|
-
"gif",
|
|
46273
|
-
"audio",
|
|
46274
|
-
"icon"
|
|
46275
|
-
]);
|
|
46276
|
-
/**
|
|
46277
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
46278
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
46279
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
46280
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
46281
|
-
*/
|
|
46282
|
-
var AttachmentSchema = object({
|
|
46283
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
46284
|
-
url: string().optional(),
|
|
46285
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
46286
|
-
mime: string().optional(),
|
|
46287
|
-
name: string().optional()
|
|
46288
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
46289
|
-
var NotificationFormatSchema = _enum([
|
|
46290
|
-
"text",
|
|
46291
|
-
"markdown",
|
|
46292
|
-
"html"
|
|
46293
|
-
]);
|
|
46294
|
-
/**
|
|
46295
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
46296
|
-
*
|
|
46297
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
46298
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
46299
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
46300
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
46301
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
46302
|
-
*
|
|
46303
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
46304
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
46305
|
-
* differently.
|
|
46306
|
-
*
|
|
46307
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
46308
|
-
*
|
|
46309
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
46310
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
46311
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
46312
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
46313
|
-
* decides its glyph, which is the only place that decision can be made
|
|
46314
|
-
* honestly.
|
|
46315
|
-
*
|
|
46316
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
46317
|
-
* `disarm` straight through; iOS feeds that string to
|
|
46318
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
46319
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
46320
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
46321
|
-
*
|
|
46322
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
46323
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
46324
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
46325
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
46326
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
46327
|
-
*/
|
|
46328
|
-
var NotificationActionIconSchema = _enum([
|
|
46329
|
-
"acknowledge",
|
|
46330
|
-
"dismiss",
|
|
46331
|
-
"silence",
|
|
46332
|
-
"snooze",
|
|
46333
|
-
"view",
|
|
46334
|
-
"play",
|
|
46335
|
-
"open",
|
|
46336
|
-
"close",
|
|
46337
|
-
"lock",
|
|
46338
|
-
"unlock",
|
|
46339
|
-
"arm",
|
|
46340
|
-
"arm-home",
|
|
46341
|
-
"arm-away",
|
|
46342
|
-
"arm-night",
|
|
46343
|
-
"disarm",
|
|
46344
|
-
"light",
|
|
46345
|
-
"alert",
|
|
46346
|
-
"camera"
|
|
46347
|
-
]);
|
|
46348
|
-
/** A single tap-through action button. */
|
|
46349
|
-
var NotificationActionSchema = object({
|
|
46350
|
-
id: string(),
|
|
46351
|
-
label: string(),
|
|
46352
|
-
url: string().optional(),
|
|
46353
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
46354
|
-
icon: NotificationActionIconSchema.optional(),
|
|
46355
|
-
/**
|
|
46356
|
-
* Renders in a warning style where the notifier supports it.
|
|
46357
|
-
*
|
|
46358
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
46359
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
46360
|
-
* does not buy.
|
|
46361
|
-
*/
|
|
46362
|
-
destructive: boolean().optional(),
|
|
46363
|
-
/**
|
|
46364
|
-
* How the tap should REACH the url.
|
|
46365
|
-
*
|
|
46366
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
46367
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
46368
|
-
* That is right for a button whose answer the operator wants to read.
|
|
46369
|
-
*
|
|
46370
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
46371
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
46372
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
46373
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
46374
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
46375
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
46376
|
-
* a requirement.
|
|
46377
|
-
*/
|
|
46378
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
46379
|
-
});
|
|
46380
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
46381
|
-
var DeliveryParamValueSchema = union([
|
|
46382
|
-
string(),
|
|
46383
|
-
number(),
|
|
46384
|
-
boolean()
|
|
46385
|
-
]);
|
|
46386
|
-
/**
|
|
46387
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
46388
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
46389
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
46390
|
-
* no editor knows a service by name.
|
|
46391
|
-
*
|
|
46392
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
46393
|
-
* run on the addon cap path).
|
|
46394
|
-
*/
|
|
46395
|
-
var DeliveryParamSchema = object({
|
|
46396
|
-
key: string().min(1),
|
|
46397
|
-
label: string(),
|
|
46398
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
46399
|
-
labelKey: string().optional(),
|
|
46400
|
-
type: _enum([
|
|
46401
|
-
"enum",
|
|
46402
|
-
"number",
|
|
46403
|
-
"boolean",
|
|
46404
|
-
"string"
|
|
46405
|
-
]),
|
|
46406
|
-
/** `enum` only: the closed set of accepted values. */
|
|
46407
|
-
options: array(object({
|
|
46408
|
-
value: string(),
|
|
46409
|
-
label: string()
|
|
46410
|
-
})).optional(),
|
|
46411
|
-
/** `number` only. */
|
|
46412
|
-
min: number().optional(),
|
|
46413
|
-
max: number().optional(),
|
|
46414
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
46415
|
-
default: DeliveryParamValueSchema.optional()
|
|
46416
|
-
});
|
|
46417
|
-
/**
|
|
46418
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
46419
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
46420
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
46421
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
46422
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
46423
|
-
* `priority` for that one target.
|
|
46424
|
-
*/
|
|
46425
|
-
var NotificationSchema = object({
|
|
46426
|
-
body: string(),
|
|
46427
|
-
title: string().optional(),
|
|
46428
|
-
format: NotificationFormatSchema.default("text"),
|
|
46429
|
-
priority: number().int().min(1).max(5).default(3),
|
|
46430
|
-
level: string().optional(),
|
|
46431
|
-
attachments: array(AttachmentSchema).optional(),
|
|
46432
|
-
clickUrl: string().optional(),
|
|
46433
|
-
actions: array(NotificationActionSchema).optional(),
|
|
46434
|
-
sound: string().optional(),
|
|
46435
|
-
ttl: number().optional(),
|
|
46436
|
-
tag: string().optional(),
|
|
46437
|
-
deviceId: number().optional(),
|
|
46438
|
-
eventId: string().optional(),
|
|
46439
|
-
metadata: record(string(), unknown()).optional(),
|
|
46440
|
-
/**
|
|
46441
|
-
* Values for the parameters the target's kind DECLARES in
|
|
46442
|
-
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
46443
|
-
* declaration by the sender; a notifier reads only the keys it declared.
|
|
46444
|
-
*/
|
|
46445
|
-
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
46446
|
-
});
|
|
46447
|
-
/** One declared native severity/priority level for a kind. */
|
|
46448
|
-
var TargetKindLevelSchema = object({
|
|
46449
|
-
id: string(),
|
|
46450
|
-
label: string(),
|
|
46451
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
46452
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
46453
|
-
flags: object({
|
|
46454
|
-
critical: boolean().optional(),
|
|
46455
|
-
silent: boolean().optional(),
|
|
46456
|
-
noPush: boolean().optional()
|
|
46457
|
-
}).optional(),
|
|
46458
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
46459
|
-
requires: array(string()).optional(),
|
|
46460
|
-
description: string().optional()
|
|
46461
|
-
});
|
|
46462
|
-
/** The full capability block consulted before dispatch. */
|
|
46463
|
-
var TargetKindCapsSchema = object({
|
|
46464
|
-
attachments: object({
|
|
46465
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
46466
|
-
mode: _enum([
|
|
46467
|
-
"url",
|
|
46468
|
-
"bytes",
|
|
46469
|
-
"both"
|
|
46470
|
-
]),
|
|
46471
|
-
max: number().int().nonnegative(),
|
|
46472
|
-
maxBytes: number().int().positive().optional()
|
|
46473
|
-
}),
|
|
46474
|
-
/** Max action buttons (0 = none). */
|
|
46475
|
-
actions: number().int().nonnegative(),
|
|
46476
|
-
/**
|
|
46477
|
-
* Whether this kind renders a per-action ICON.
|
|
46478
|
-
*
|
|
46479
|
-
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
46480
|
-
* run on the addon cap path — three production failures in one day taught
|
|
46481
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
46482
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
46483
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
46484
|
-
*/
|
|
46485
|
-
actionIcons: boolean().optional(),
|
|
46486
|
-
levels: array(TargetKindLevelSchema),
|
|
46487
|
-
format: array(NotificationFormatSchema),
|
|
46488
|
-
clickUrl: boolean(),
|
|
46489
|
-
sound: boolean(),
|
|
46490
|
-
ttl: boolean(),
|
|
46491
|
-
bodyMaxLen: number().int().positive()
|
|
46492
|
-
});
|
|
46493
|
-
/**
|
|
46494
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
46495
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
46496
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
46497
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
46498
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
46499
|
-
*/
|
|
46500
|
-
var ConfigSchemaPassthrough$2 = unknown();
|
|
46501
|
-
var TargetKindSchema = object({
|
|
46502
|
-
kind: string(),
|
|
46503
|
-
label: string(),
|
|
46504
|
-
icon: string(),
|
|
46505
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
46506
|
-
addonId: string(),
|
|
46507
|
-
/**
|
|
46508
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
46509
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
46510
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
46511
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
46512
|
-
*
|
|
46513
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
46514
|
-
* client, and a native client joins it onto its own hub base.
|
|
46515
|
-
*
|
|
46516
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
46517
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
46518
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
46519
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
46520
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
46521
|
-
*/
|
|
46522
|
-
iconUrl: string().optional(),
|
|
46523
|
-
/**
|
|
46524
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
46525
|
-
*
|
|
46526
|
-
* The server knows this and therefore says it, because the client cannot
|
|
46527
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
46528
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
46529
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
46530
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
46531
|
-
*
|
|
46532
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
46533
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
46534
|
-
* its raster path, which is the safe default for an unknown image.
|
|
46535
|
-
*/
|
|
46536
|
-
iconMediaType: string().optional(),
|
|
46537
|
-
configSchema: ConfigSchemaPassthrough$2,
|
|
46538
|
-
/**
|
|
46539
|
-
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
46540
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
46541
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
46542
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
46543
|
-
* on every edit.
|
|
46544
|
-
*
|
|
46545
|
-
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
46546
|
-
* kinds) and from a provider older than this field — an editor that finds
|
|
46547
|
-
* it absent must assume ANY edit may need the secret.
|
|
46548
|
-
*/
|
|
46549
|
-
destinationKeys: array(string()).optional(),
|
|
46550
|
-
supportsDiscovery: boolean(),
|
|
46551
|
-
/**
|
|
46552
|
-
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
46553
|
-
* Absent = none; the rule editor then offers nothing.
|
|
46554
|
-
*/
|
|
46555
|
-
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
46556
|
-
caps: TargetKindCapsSchema
|
|
46557
|
-
});
|
|
46558
|
-
/**
|
|
46559
|
-
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
46560
|
-
* (return a presence marker only) when serving `listTargets` — never
|
|
46561
|
-
* round-trip a stored secret to the UI.
|
|
46562
|
-
*/
|
|
46563
|
-
var TargetSchema = object({
|
|
46564
|
-
id: string(),
|
|
46565
|
-
name: string(),
|
|
46566
|
-
kind: string(),
|
|
46567
|
-
addonId: string(),
|
|
46568
|
-
enabled: boolean(),
|
|
46569
|
-
config: record(string(), unknown())
|
|
46570
|
-
});
|
|
46571
|
-
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
46572
|
-
var DiscoveredTargetSchema = object({
|
|
46573
|
-
kind: string(),
|
|
46574
|
-
suggestedName: string(),
|
|
46575
|
-
config: record(string(), unknown())
|
|
46576
|
-
});
|
|
46577
|
-
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
46578
|
-
var RenderedAsSchema = object({
|
|
46579
|
-
level: string(),
|
|
46580
|
-
format: NotificationFormatSchema,
|
|
46581
|
-
attachmentsSent: number().int().nonnegative(),
|
|
46582
|
-
actionsSent: number().int().nonnegative(),
|
|
46583
|
-
truncated: boolean(),
|
|
46584
|
-
dropped: array(string())
|
|
46585
|
-
});
|
|
46586
|
-
var SendResultSchema = object({
|
|
46587
|
-
success: boolean(),
|
|
46588
|
-
error: string().optional(),
|
|
46589
|
-
renderedAs: RenderedAsSchema.optional()
|
|
46590
|
-
});
|
|
46591
|
-
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
46592
|
-
var TestResultSchema = SendResultSchema;
|
|
46593
|
-
/**
|
|
46594
|
-
* Pre-save credential probe against a partial (unsaved) target config.
|
|
46595
|
-
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
46596
|
-
*/
|
|
46597
|
-
var ProbeTargetConfigResultSchema = object({
|
|
46598
|
-
ok: boolean(),
|
|
46599
|
-
resolvedUrl: string(),
|
|
46600
|
-
httpStatus: number().int().optional(),
|
|
46601
|
-
stateInstance: string().optional(),
|
|
46602
|
-
error: string().optional()
|
|
46603
|
-
});
|
|
46604
|
-
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
46605
|
-
kind: string(),
|
|
46606
|
-
config: record(string(), unknown()).optional(),
|
|
46607
|
-
/**
|
|
46608
|
-
* When editing / duplicating, the blank password field never round-trips
|
|
46609
|
-
* the stored token. Pass the source target id so the provider can restore
|
|
46610
|
-
* secrets server-side before dialling (same merge as upsert).
|
|
46611
|
-
*/
|
|
46612
|
-
targetId: string().optional()
|
|
46613
|
-
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
46614
|
-
kind: string(),
|
|
46615
|
-
config: record(string(), unknown()),
|
|
46616
|
-
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
46617
|
-
targetId: string().optional()
|
|
46618
|
-
}), ProbeTargetConfigResultSchema, {
|
|
46619
|
-
kind: "mutation",
|
|
46620
|
-
auth: "admin"
|
|
46621
|
-
}), method(object({
|
|
46622
|
-
targetId: string(),
|
|
46623
|
-
notification: NotificationSchema
|
|
46624
|
-
}), SendResultSchema, {
|
|
46625
|
-
kind: "mutation",
|
|
46626
|
-
auth: "admin"
|
|
46627
|
-
}), method(object({
|
|
46628
|
-
targetId: string(),
|
|
46629
|
-
sample: NotificationSchema.optional()
|
|
46630
|
-
}), TestResultSchema, {
|
|
46631
|
-
kind: "mutation",
|
|
46632
|
-
auth: "admin"
|
|
46633
|
-
}), method(object({
|
|
46634
|
-
target: TargetSchema,
|
|
46635
|
-
/**
|
|
46636
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
46637
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
46638
|
-
* password fields are restored from this source (D634 still applies if
|
|
46639
|
-
* destinations diverge without a re-typed secret).
|
|
46640
|
-
*/
|
|
46641
|
-
cloneSecretsFrom: string().optional()
|
|
46642
|
-
}), TargetSchema, {
|
|
46643
|
-
kind: "mutation",
|
|
46644
|
-
auth: "admin"
|
|
46645
|
-
}), method(object({ targetId: string() }), _void(), {
|
|
46646
|
-
kind: "mutation",
|
|
46647
|
-
auth: "admin"
|
|
46648
|
-
}), method(object({
|
|
46649
|
-
targetId: string(),
|
|
46650
|
-
enabled: boolean()
|
|
46651
|
-
}), _void(), {
|
|
46652
|
-
kind: "mutation",
|
|
46653
|
-
auth: "admin"
|
|
46654
|
-
});
|
|
46655
47238
|
var AUDIO_MACRO_LABELS = [
|
|
46656
47239
|
{
|
|
46657
47240
|
id: "speech",
|
|
@@ -54896,14 +55479,6 @@ var EngineProvisioningSchema = object({
|
|
|
54896
55479
|
*/
|
|
54897
55480
|
configIssues: array(string()).optional()
|
|
54898
55481
|
});
|
|
54899
|
-
var PipelineStepInputSchema = lazy(() => object({
|
|
54900
|
-
addonId: string(),
|
|
54901
|
-
modelId: string().optional(),
|
|
54902
|
-
enabled: boolean().default(true),
|
|
54903
|
-
children: array(PipelineStepInputSchema).optional(),
|
|
54904
|
-
settings: record(string(), unknown()).optional(),
|
|
54905
|
-
jumpDeviceKey: string().optional()
|
|
54906
|
-
}));
|
|
54907
55482
|
var ModelSubstitutionSchema = object({
|
|
54908
55483
|
addonId: string(),
|
|
54909
55484
|
chosen: string(),
|
|
@@ -54982,7 +55557,6 @@ var ConfigUISchemaBridge = custom();
|
|
|
54982
55557
|
var ConfigUISchemaNullableBridge = custom();
|
|
54983
55558
|
var InferenceCapabilitiesBridge = custom();
|
|
54984
55559
|
var ModelAvailabilityListBridge = custom();
|
|
54985
|
-
var PipelineRunResultBridge = custom();
|
|
54986
55560
|
method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
|
|
54987
55561
|
modelId: string(),
|
|
54988
55562
|
settings: record(string(), unknown()).readonly()
|
|
@@ -57978,11 +58552,13 @@ var ImageContractSchema = object({
|
|
|
57978
58552
|
* The shell verdict (see {@link WrapperKindSchema}) with the proofs that
|
|
57979
58553
|
* produced it.
|
|
57980
58554
|
*
|
|
57981
|
-
* There is deliberately NO `canSelfUpdate`
|
|
57982
|
-
*
|
|
57983
|
-
*
|
|
57984
|
-
*
|
|
57985
|
-
*
|
|
58555
|
+
* There is deliberately NO `canSelfUpdate` boolean: a wrapper update is an
|
|
58556
|
+
* ANNOUNCEMENT. The notification body carries the exact command for a `docker`
|
|
58557
|
+
* shell and says the app restarts into the new build for `electron`, and that
|
|
58558
|
+
* sentence is chosen from `kind` alone — a boolean nothing reads is the defect
|
|
58559
|
+
* this repo keeps paying for. The one shell that CAN be acted on is the
|
|
58560
|
+
* Electron app, and only through `installDesktopUpdate` (see
|
|
58561
|
+
* {@link DesktopAppStatusSchema} for what the node reads from the app first).
|
|
57986
58562
|
*/
|
|
57987
58563
|
var WrapperIdentitySchema = object({
|
|
57988
58564
|
kind: _enum([
|
|
@@ -58030,6 +58606,49 @@ var ServerRollbackInfoSchema = object({
|
|
|
58030
58606
|
reason: string()
|
|
58031
58607
|
});
|
|
58032
58608
|
/**
|
|
58609
|
+
* What the native desktop app (Electron) that hosts this node says about
|
|
58610
|
+
* itself, read from `<dataDir>/server-root/desktop-app-state.json` (ADR-0971).
|
|
58611
|
+
* Absent on the status = no desktop app, or it never wrote the file.
|
|
58612
|
+
*
|
|
58613
|
+
* `stale` is the honest answer to an app that stopped writing (closed, hung,
|
|
58614
|
+
* crashed): the update facts are then the LAST ones known, not current ones,
|
|
58615
|
+
* and a consumer must not read `update.available: false` from a stale record
|
|
58616
|
+
* as "no update". `stateAtMs` is when the app last wrote.
|
|
58617
|
+
*/
|
|
58618
|
+
var DesktopAppStatusSchema = object({
|
|
58619
|
+
/** The Electron app's own version (not the node closure's). */
|
|
58620
|
+
version: string(),
|
|
58621
|
+
update: object({
|
|
58622
|
+
available: boolean(),
|
|
58623
|
+
/** The new build is downloaded and `installDesktopUpdate` can apply it. */
|
|
58624
|
+
downloaded: boolean(),
|
|
58625
|
+
version: string().nullable(),
|
|
58626
|
+
checking: boolean(),
|
|
58627
|
+
/** Why the app's last update check produced no answer; `null` when it did. */
|
|
58628
|
+
error: string().nullable()
|
|
58629
|
+
}),
|
|
58630
|
+
stateAtMs: number(),
|
|
58631
|
+
stale: boolean()
|
|
58632
|
+
});
|
|
58633
|
+
/**
|
|
58634
|
+
* Why `installDesktopUpdate` did nothing. Stable names a UI maps to a sentence:
|
|
58635
|
+
* - `not-electron` — this node does not run inside the desktop app.
|
|
58636
|
+
* - `no-app-state` — the app never published its state (or the file is unreadable).
|
|
58637
|
+
* - `app-state-stale` — the app stopped writing; it is probably not running.
|
|
58638
|
+
* - `not-downloaded` — no update is downloaded yet; nothing to install.
|
|
58639
|
+
*/
|
|
58640
|
+
var InstallDesktopUpdateReasonSchema = _enum([
|
|
58641
|
+
"not-electron",
|
|
58642
|
+
"no-app-state",
|
|
58643
|
+
"app-state-stale",
|
|
58644
|
+
"not-downloaded"
|
|
58645
|
+
]);
|
|
58646
|
+
var InstallDesktopUpdateResultSchema = object({
|
|
58647
|
+
accepted: boolean(),
|
|
58648
|
+
/** Non-null exactly when `accepted` is false. */
|
|
58649
|
+
reason: InstallDesktopUpdateReasonSchema.nullable()
|
|
58650
|
+
});
|
|
58651
|
+
/**
|
|
58033
58652
|
* The runner boot check (D877): the runners this node PLANNED (hub: one per
|
|
58034
58653
|
* `resolveRunnerId` over its forked addons; agent: the runners its process
|
|
58035
58654
|
* service holds, because only the hub plans) against the states the process
|
|
@@ -58133,7 +58752,12 @@ var ServerPackageStatusSchema = object({
|
|
|
58133
58752
|
* on a provider that predates it. Absent is "not checked yet", never "all
|
|
58134
58753
|
* well": a consumer that needs the answer treats it as pending.
|
|
58135
58754
|
*/
|
|
58136
|
-
runners: RunnerBootCheckSchema.optional()
|
|
58755
|
+
runners: RunnerBootCheckSchema.optional(),
|
|
58756
|
+
/**
|
|
58757
|
+
* The desktop app hosting this node (see {@link DesktopAppStatusSchema}).
|
|
58758
|
+
* Optional for version skew AND because most nodes have no desktop app.
|
|
58759
|
+
*/
|
|
58760
|
+
desktopApp: DesktopAppStatusSchema.optional()
|
|
58137
58761
|
});
|
|
58138
58762
|
var ServerUpdateCheckResultSchema = object({
|
|
58139
58763
|
packageName: string(),
|
|
@@ -58216,6 +58840,12 @@ var ServerUpdateActionResultSchema = object({
|
|
|
58216
58840
|
*/
|
|
58217
58841
|
blockers: array(UpdateBlockerSchema).optional()
|
|
58218
58842
|
});
|
|
58843
|
+
var LaunchOverridesSchema = object({
|
|
58844
|
+
/** The persisted heap ceiling in MB, or `null` when none is set. */
|
|
58845
|
+
heapMb: number().int().nullable() });
|
|
58846
|
+
var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
|
|
58847
|
+
/** Always true: the override is read at launch, not by the running process. */
|
|
58848
|
+
restartRequired: boolean() });
|
|
58219
58849
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
58220
58850
|
kind: "mutation",
|
|
58221
58851
|
auth: "admin"
|
|
@@ -58232,12 +58862,18 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
|
|
|
58232
58862
|
kind: "mutation",
|
|
58233
58863
|
auth: "admin",
|
|
58234
58864
|
timeoutMs: 16 * 6e4
|
|
58865
|
+
}), method(_void(), InstallDesktopUpdateResultSchema, {
|
|
58866
|
+
kind: "mutation",
|
|
58867
|
+
auth: "admin"
|
|
58235
58868
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58236
58869
|
kind: "mutation",
|
|
58237
58870
|
auth: "admin"
|
|
58238
58871
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58239
58872
|
kind: "mutation",
|
|
58240
58873
|
auth: "admin"
|
|
58874
|
+
}), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
|
|
58875
|
+
kind: "mutation",
|
|
58876
|
+
auth: "admin"
|
|
58241
58877
|
});
|
|
58242
58878
|
/**
|
|
58243
58879
|
* `smtp-provider` — pluggable email delivery surface.
|
|
@@ -61379,13 +62015,19 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
|
61379
62015
|
}), method(ActionIdInput, _void(), {
|
|
61380
62016
|
kind: "mutation",
|
|
61381
62017
|
auth: "admin"
|
|
61382
|
-
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
62018
|
+
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
62019
|
+
action: ActionEntrySchema,
|
|
62020
|
+
warnings: array(UnitWarningSchema).optional()
|
|
62021
|
+
}), {
|
|
61383
62022
|
kind: "mutation",
|
|
61384
62023
|
auth: "admin"
|
|
61385
62024
|
}), method(object({
|
|
61386
62025
|
actionId: string().min(1),
|
|
61387
62026
|
action: ActionInputSchema.partial()
|
|
61388
|
-
}), object({
|
|
62027
|
+
}), object({
|
|
62028
|
+
action: ActionEntrySchema,
|
|
62029
|
+
warnings: array(UnitWarningSchema).optional()
|
|
62030
|
+
}), {
|
|
61389
62031
|
kind: "mutation",
|
|
61390
62032
|
auth: "admin"
|
|
61391
62033
|
}), method(ActionIdInput, _void(), {
|
|
@@ -61967,6 +62609,30 @@ object({
|
|
|
61967
62609
|
at: number()
|
|
61968
62610
|
}).nullable()
|
|
61969
62611
|
});
|
|
62612
|
+
var RemoteButtonShapeSchema = _enum([
|
|
62613
|
+
"rect",
|
|
62614
|
+
"rounded",
|
|
62615
|
+
"round",
|
|
62616
|
+
"pill"
|
|
62617
|
+
]);
|
|
62618
|
+
var RemoteButtonToneSchema = _enum([
|
|
62619
|
+
"neutral",
|
|
62620
|
+
"accent",
|
|
62621
|
+
"danger",
|
|
62622
|
+
"red",
|
|
62623
|
+
"green",
|
|
62624
|
+
"yellow",
|
|
62625
|
+
"blue"
|
|
62626
|
+
]);
|
|
62627
|
+
var RemoteGroupFrameSchema = _enum([
|
|
62628
|
+
"none",
|
|
62629
|
+
"panel",
|
|
62630
|
+
"divider",
|
|
62631
|
+
"dpad",
|
|
62632
|
+
"rocker-v",
|
|
62633
|
+
"rocker-h",
|
|
62634
|
+
"keypad"
|
|
62635
|
+
]);
|
|
61970
62636
|
var RemoteLayoutButtonSchema = object({
|
|
61971
62637
|
id: string().min(1).max(40),
|
|
61972
62638
|
/** 0-based grid position. */
|
|
@@ -61976,12 +62642,94 @@ var RemoteLayoutButtonSchema = object({
|
|
|
61976
62642
|
colSpan: number().int().min(1).max(24),
|
|
61977
62643
|
icon: string().min(1).max(60).optional(),
|
|
61978
62644
|
label: string().min(1).max(40).optional(),
|
|
61979
|
-
role: RemoteRoleSchema
|
|
62645
|
+
role: RemoteRoleSchema,
|
|
62646
|
+
/** Absent = the plain rendering every layout had before shapes existed. */
|
|
62647
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62648
|
+
tone: RemoteButtonToneSchema.optional(),
|
|
62649
|
+
/** The group this button belongs to; it must lie inside that group's rectangle. */
|
|
62650
|
+
groupId: string().min(1).max(40).optional()
|
|
61980
62651
|
});
|
|
62652
|
+
/**
|
|
62653
|
+
* A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
|
|
62654
|
+
* titled panel, a section divider, a D-pad ring. `componentId` names the palette
|
|
62655
|
+
* entry it came from (informational: the layout is self-contained).
|
|
62656
|
+
*/
|
|
62657
|
+
var RemoteLayoutGroupSchema = object({
|
|
62658
|
+
id: string().min(1).max(40),
|
|
62659
|
+
componentId: string().min(1).max(80),
|
|
62660
|
+
frame: RemoteGroupFrameSchema,
|
|
62661
|
+
row: number().int().min(0).max(24),
|
|
62662
|
+
col: number().int().min(0).max(24),
|
|
62663
|
+
rowSpan: number().int().min(1).max(24),
|
|
62664
|
+
colSpan: number().int().min(1).max(24),
|
|
62665
|
+
title: string().min(1).max(40).optional()
|
|
62666
|
+
});
|
|
62667
|
+
function rectsOverlap(a, b) {
|
|
62668
|
+
return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
|
|
62669
|
+
}
|
|
62670
|
+
function rectContains(outer, inner) {
|
|
62671
|
+
return inner.row >= outer.row && inner.col >= outer.col && inner.row + inner.rowSpan <= outer.row + outer.rowSpan && inner.col + inner.colSpan <= outer.col + outer.colSpan;
|
|
62672
|
+
}
|
|
62673
|
+
/**
|
|
62674
|
+
* The group rules of a layout: inside the grid, no two groups overlap, a
|
|
62675
|
+
* member lies inside its group, a divider holds nothing, and a button that is
|
|
62676
|
+
* not a member never sits inside someone else's rectangle (a group moves as
|
|
62677
|
+
* one thing, so a stray key under it would be left behind).
|
|
62678
|
+
*/
|
|
62679
|
+
function refineGroups(grid, ctx) {
|
|
62680
|
+
const groups = grid.groups ?? [];
|
|
62681
|
+
const byId = /* @__PURE__ */ new Map();
|
|
62682
|
+
const fail = (message, path) => {
|
|
62683
|
+
ctx.addIssue({
|
|
62684
|
+
code: "custom",
|
|
62685
|
+
message,
|
|
62686
|
+
path
|
|
62687
|
+
});
|
|
62688
|
+
};
|
|
62689
|
+
groups.forEach((group, index) => {
|
|
62690
|
+
if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
|
|
62691
|
+
"groups",
|
|
62692
|
+
index,
|
|
62693
|
+
"id"
|
|
62694
|
+
]);
|
|
62695
|
+
byId.set(group.id, group);
|
|
62696
|
+
if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
|
|
62697
|
+
for (let other = 0; other < index; other += 1) {
|
|
62698
|
+
const earlier = groups[other];
|
|
62699
|
+
if (earlier !== void 0 && rectsOverlap(earlier, group)) {
|
|
62700
|
+
fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
|
|
62701
|
+
return;
|
|
62702
|
+
}
|
|
62703
|
+
}
|
|
62704
|
+
});
|
|
62705
|
+
grid.buttons.forEach((button, index) => {
|
|
62706
|
+
if (button.groupId !== void 0) {
|
|
62707
|
+
const group = byId.get(button.groupId);
|
|
62708
|
+
if (group === void 0) {
|
|
62709
|
+
fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
|
|
62710
|
+
"buttons",
|
|
62711
|
+
index,
|
|
62712
|
+
"groupId"
|
|
62713
|
+
]);
|
|
62714
|
+
return;
|
|
62715
|
+
}
|
|
62716
|
+
if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
|
|
62717
|
+
"buttons",
|
|
62718
|
+
index,
|
|
62719
|
+
"groupId"
|
|
62720
|
+
]);
|
|
62721
|
+
if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
|
|
62722
|
+
return;
|
|
62723
|
+
}
|
|
62724
|
+
const foreign = groups.find((group) => rectsOverlap(group, button));
|
|
62725
|
+
if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
|
|
62726
|
+
});
|
|
62727
|
+
}
|
|
61981
62728
|
object({
|
|
61982
62729
|
columns: number().int().min(1).max(24),
|
|
61983
62730
|
rows: number().int().min(1).max(24),
|
|
61984
|
-
buttons: array(RemoteLayoutButtonSchema).max(200)
|
|
62731
|
+
buttons: array(RemoteLayoutButtonSchema).max(200),
|
|
62732
|
+
groups: array(RemoteLayoutGroupSchema).max(50).optional()
|
|
61985
62733
|
}).superRefine((grid, ctx) => {
|
|
61986
62734
|
const ids = /* @__PURE__ */ new Set();
|
|
61987
62735
|
const taken = /* @__PURE__ */ new Map();
|
|
@@ -62018,6 +62766,7 @@ object({
|
|
|
62018
62766
|
taken.set(cell, button.id);
|
|
62019
62767
|
}
|
|
62020
62768
|
});
|
|
62769
|
+
refineGroups(grid, ctx);
|
|
62021
62770
|
});
|
|
62022
62771
|
var RemoteBoundCommandSchema = object({
|
|
62023
62772
|
role: RemoteRoleSchema,
|
|
@@ -62149,6 +62898,91 @@ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSche
|
|
|
62149
62898
|
kind: "mutation",
|
|
62150
62899
|
auth: "admin"
|
|
62151
62900
|
});
|
|
62901
|
+
var RemoteComponentCategorySchema = _enum([
|
|
62902
|
+
"power",
|
|
62903
|
+
"navigation",
|
|
62904
|
+
"volume",
|
|
62905
|
+
"numbers",
|
|
62906
|
+
"media",
|
|
62907
|
+
"colour",
|
|
62908
|
+
"apps",
|
|
62909
|
+
"layout"
|
|
62910
|
+
]);
|
|
62911
|
+
/** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
|
|
62912
|
+
var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
|
|
62913
|
+
var RemoteComponentButtonSchema = object({
|
|
62914
|
+
/** Joined to the instance id when the component is inserted. */
|
|
62915
|
+
idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
|
|
62916
|
+
row: number().int().min(0).max(24),
|
|
62917
|
+
col: number().int().min(0).max(24),
|
|
62918
|
+
rowSpan: number().int().min(1).max(24),
|
|
62919
|
+
colSpan: number().int().min(1).max(24),
|
|
62920
|
+
role: RemoteRoleSchema,
|
|
62921
|
+
icon: string().min(1).max(60).optional(),
|
|
62922
|
+
label: string().min(1).max(40).optional(),
|
|
62923
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62924
|
+
tone: RemoteButtonToneSchema.optional()
|
|
62925
|
+
});
|
|
62926
|
+
var RemoteLayoutComponentSchema = object({
|
|
62927
|
+
id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
|
|
62928
|
+
name: string().min(1).max(60),
|
|
62929
|
+
nameI18n: object({
|
|
62930
|
+
en: string().min(1).max(60),
|
|
62931
|
+
it: string().min(1).max(60)
|
|
62932
|
+
}).optional(),
|
|
62933
|
+
/** A display icon key, resolved by the UI like a button icon. */
|
|
62934
|
+
icon: string().min(1).max(60),
|
|
62935
|
+
category: RemoteComponentCategorySchema,
|
|
62936
|
+
size: object({
|
|
62937
|
+
rows: number().int().min(1).max(24),
|
|
62938
|
+
cols: number().int().min(1).max(24)
|
|
62939
|
+
}),
|
|
62940
|
+
/** The frame drawn around the whole component, or null for loose buttons. */
|
|
62941
|
+
group: object({
|
|
62942
|
+
frame: RemoteGroupFrameSchema,
|
|
62943
|
+
title: string().min(1).max(40).optional()
|
|
62944
|
+
}).nullable(),
|
|
62945
|
+
buttons: array(RemoteComponentButtonSchema).max(40)
|
|
62946
|
+
}).superRefine((component, ctx) => {
|
|
62947
|
+
const suffixes = /* @__PURE__ */ new Set();
|
|
62948
|
+
component.buttons.forEach((button, index) => {
|
|
62949
|
+
if (suffixes.has(button.idSuffix)) ctx.addIssue({
|
|
62950
|
+
code: "custom",
|
|
62951
|
+
message: `button "${button.idSuffix}" is listed twice`,
|
|
62952
|
+
path: [
|
|
62953
|
+
"buttons",
|
|
62954
|
+
index,
|
|
62955
|
+
"idSuffix"
|
|
62956
|
+
]
|
|
62957
|
+
});
|
|
62958
|
+
suffixes.add(button.idSuffix);
|
|
62959
|
+
if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
|
|
62960
|
+
ctx.addIssue({
|
|
62961
|
+
code: "custom",
|
|
62962
|
+
message: `button "${button.idSuffix}" leaves the component`,
|
|
62963
|
+
path: ["buttons", index]
|
|
62964
|
+
});
|
|
62965
|
+
return;
|
|
62966
|
+
}
|
|
62967
|
+
for (let other = 0; other < index; other += 1) {
|
|
62968
|
+
const earlier = component.buttons[other];
|
|
62969
|
+
if (earlier !== void 0 && rectsOverlap(earlier, button)) {
|
|
62970
|
+
ctx.addIssue({
|
|
62971
|
+
code: "custom",
|
|
62972
|
+
message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
|
|
62973
|
+
path: ["buttons", index]
|
|
62974
|
+
});
|
|
62975
|
+
return;
|
|
62976
|
+
}
|
|
62977
|
+
}
|
|
62978
|
+
});
|
|
62979
|
+
if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
|
|
62980
|
+
code: "custom",
|
|
62981
|
+
message: "a divider component holds no buttons",
|
|
62982
|
+
path: ["buttons"]
|
|
62983
|
+
});
|
|
62984
|
+
});
|
|
62985
|
+
method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
|
|
62152
62986
|
DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
62153
62987
|
kind: "mutation",
|
|
62154
62988
|
auth: "protected"
|
|
@@ -63204,14 +64038,13 @@ reset: method(object({
|
|
|
63204
64038
|
auth: "admin"
|
|
63205
64039
|
}) },
|
|
63206
64040
|
/**
|
|
63207
|
-
* A
|
|
63208
|
-
*
|
|
63209
|
-
*
|
|
63210
|
-
* commands (`forward`) arrive in slice 3 (D663).
|
|
64041
|
+
* A consumable ADDED to an existing device (a graft) refuses `reset` by
|
|
64042
|
+
* name: only a composed device of a NEW type forwards an item's reset to a
|
|
64043
|
+
* source button, and marks that item `resettable` (D967).
|
|
63211
64044
|
*/
|
|
63212
64045
|
composedMethods: { reset: {
|
|
63213
64046
|
kind: "refuse",
|
|
63214
|
-
reason: "a
|
|
64047
|
+
reason: "a consumable added to an existing device is not resettable — only a new composed device forwards a reset"
|
|
63215
64048
|
} },
|
|
63216
64049
|
status: {
|
|
63217
64050
|
schema: ConsumablesStatusSchema,
|
|
@@ -63827,7 +64660,7 @@ var DishwasherStatusSchema = object({
|
|
|
63827
64660
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
63828
64661
|
lastFetchedAt: number()
|
|
63829
64662
|
});
|
|
63830
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
64663
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
63831
64664
|
/** A value a program option or setting can take. */
|
|
63832
64665
|
var DishwasherValueSchema = union([
|
|
63833
64666
|
boolean(),
|
|
@@ -63902,77 +64735,77 @@ var dishwasherCapability = {
|
|
|
63902
64735
|
* option feature keys; each is checked against the program's constraints.
|
|
63903
64736
|
*/
|
|
63904
64737
|
start: method(object({
|
|
63905
|
-
deviceId: DeviceIdSchema,
|
|
64738
|
+
deviceId: DeviceIdSchema$1,
|
|
63906
64739
|
program: string().min(1).optional(),
|
|
63907
64740
|
options: record(string(), DishwasherValueSchema).optional()
|
|
63908
64741
|
}), _void(), {
|
|
63909
64742
|
kind: "mutation",
|
|
63910
64743
|
auth: "protected"
|
|
63911
64744
|
}),
|
|
63912
|
-
pause: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64745
|
+
pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63913
64746
|
kind: "mutation",
|
|
63914
64747
|
auth: "protected"
|
|
63915
64748
|
}),
|
|
63916
|
-
resume: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64749
|
+
resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63917
64750
|
kind: "mutation",
|
|
63918
64751
|
auth: "protected"
|
|
63919
64752
|
}),
|
|
63920
|
-
stop: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64753
|
+
stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63921
64754
|
kind: "mutation",
|
|
63922
64755
|
auth: "protected"
|
|
63923
64756
|
}),
|
|
63924
64757
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
63925
64758
|
setProgram: method(object({
|
|
63926
|
-
deviceId: DeviceIdSchema,
|
|
64759
|
+
deviceId: DeviceIdSchema$1,
|
|
63927
64760
|
program: string().min(1)
|
|
63928
64761
|
}), _void(), {
|
|
63929
64762
|
kind: "mutation",
|
|
63930
64763
|
auth: "protected"
|
|
63931
64764
|
}),
|
|
63932
64765
|
setPower: method(object({
|
|
63933
|
-
deviceId: DeviceIdSchema,
|
|
64766
|
+
deviceId: DeviceIdSchema$1,
|
|
63934
64767
|
on: boolean()
|
|
63935
64768
|
}), _void(), {
|
|
63936
64769
|
kind: "mutation",
|
|
63937
64770
|
auth: "protected"
|
|
63938
64771
|
}),
|
|
63939
64772
|
setHalfLoad: method(object({
|
|
63940
|
-
deviceId: DeviceIdSchema,
|
|
64773
|
+
deviceId: DeviceIdSchema$1,
|
|
63941
64774
|
on: boolean()
|
|
63942
64775
|
}), _void(), {
|
|
63943
64776
|
kind: "mutation",
|
|
63944
64777
|
auth: "protected"
|
|
63945
64778
|
}),
|
|
63946
64779
|
setHygienePlus: method(object({
|
|
63947
|
-
deviceId: DeviceIdSchema,
|
|
64780
|
+
deviceId: DeviceIdSchema$1,
|
|
63948
64781
|
on: boolean()
|
|
63949
64782
|
}), _void(), {
|
|
63950
64783
|
kind: "mutation",
|
|
63951
64784
|
auth: "protected"
|
|
63952
64785
|
}),
|
|
63953
64786
|
setExtraDry: method(object({
|
|
63954
|
-
deviceId: DeviceIdSchema,
|
|
64787
|
+
deviceId: DeviceIdSchema$1,
|
|
63955
64788
|
on: boolean()
|
|
63956
64789
|
}), _void(), {
|
|
63957
64790
|
kind: "mutation",
|
|
63958
64791
|
auth: "protected"
|
|
63959
64792
|
}),
|
|
63960
64793
|
setIntensiveZone: method(object({
|
|
63961
|
-
deviceId: DeviceIdSchema,
|
|
64794
|
+
deviceId: DeviceIdSchema$1,
|
|
63962
64795
|
on: boolean()
|
|
63963
64796
|
}), _void(), {
|
|
63964
64797
|
kind: "mutation",
|
|
63965
64798
|
auth: "protected"
|
|
63966
64799
|
}),
|
|
63967
64800
|
setVarioSpeedPlus: method(object({
|
|
63968
|
-
deviceId: DeviceIdSchema,
|
|
64801
|
+
deviceId: DeviceIdSchema$1,
|
|
63969
64802
|
on: boolean()
|
|
63970
64803
|
}), _void(), {
|
|
63971
64804
|
kind: "mutation",
|
|
63972
64805
|
auth: "protected"
|
|
63973
64806
|
}),
|
|
63974
64807
|
setChildLock: method(object({
|
|
63975
|
-
deviceId: DeviceIdSchema,
|
|
64808
|
+
deviceId: DeviceIdSchema$1,
|
|
63976
64809
|
on: boolean()
|
|
63977
64810
|
}), _void(), {
|
|
63978
64811
|
kind: "mutation",
|
|
@@ -63984,7 +64817,7 @@ var dishwasherCapability = {
|
|
|
63984
64817
|
* its declared range / members.
|
|
63985
64818
|
*/
|
|
63986
64819
|
setOption: method(object({
|
|
63987
|
-
deviceId: DeviceIdSchema,
|
|
64820
|
+
deviceId: DeviceIdSchema$1,
|
|
63988
64821
|
key: string().min(1),
|
|
63989
64822
|
value: DishwasherValueSchema
|
|
63990
64823
|
}), _void(), {
|
|
@@ -63992,7 +64825,7 @@ var dishwasherCapability = {
|
|
|
63992
64825
|
auth: "protected"
|
|
63993
64826
|
}),
|
|
63994
64827
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
63995
|
-
getOptions: method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" })
|
|
64828
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
63996
64829
|
},
|
|
63997
64830
|
status: {
|
|
63998
64831
|
schema: DishwasherStatusSchema,
|
|
@@ -67769,7 +68602,8 @@ var TopologyNodeSchema = object({
|
|
|
67769
68602
|
isHub: boolean(),
|
|
67770
68603
|
isOnline: boolean(),
|
|
67771
68604
|
cpuPercent: number(),
|
|
67772
|
-
|
|
68605
|
+
/** `null` when the node's memory read failed or it has not reported one (D393). */
|
|
68606
|
+
memoryPercent: number().nullable(),
|
|
67773
68607
|
uptime: number(),
|
|
67774
68608
|
lastSeen: string(),
|
|
67775
68609
|
localIps: array(string()).readonly(),
|
|
@@ -68380,6 +69214,14 @@ var PetFeederDeviceStatusSchema = _enum([
|
|
|
68380
69214
|
"on_batteries"
|
|
68381
69215
|
]);
|
|
68382
69216
|
var gramsPortion = number().int().min(4).max(200);
|
|
69217
|
+
/** A portion REQUESTED of the feeder is a mass the method takes in GRAMS (the
|
|
69218
|
+
* hardware's own unit). The bounds are grams; a caller entering another unit
|
|
69219
|
+
* converts to grams first (`paramMeta`, D972). The STATUS fields above stay
|
|
69220
|
+
* plain grams — they are not readings in the kilogram base. */
|
|
69221
|
+
var gramsParam = gramsPortion.meta(paramMeta({
|
|
69222
|
+
quantity: "mass",
|
|
69223
|
+
unit: "g"
|
|
69224
|
+
}));
|
|
68383
69225
|
var PetFeederStatusSchema = object({
|
|
68384
69226
|
/** Food in the TANK (hopper), as the feeder's own sensor reports it
|
|
68385
69227
|
* (`state.food`). A coarse level code, not grams — `0` is empty. The
|
|
@@ -68387,9 +69229,10 @@ var PetFeederStatusSchema = object({
|
|
|
68387
69229
|
* combined reading; per-hopper levels live in `food1`/`food2`. Null when
|
|
68388
69230
|
* the device has not reported yet. */
|
|
68389
69231
|
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
68390
|
-
/** Food in the BOWL
|
|
68391
|
-
* feeder
|
|
68392
|
-
|
|
69232
|
+
/** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
|
|
69233
|
+
* feeder with a bowl scale; null when the model has none or its reading is
|
|
69234
|
+
* of an unproven unit. */
|
|
69235
|
+
bowlFood: number().nullable().meta(readingMeta("mass")),
|
|
68393
69236
|
/** True while the pet is eating (`state.eating`). */
|
|
68394
69237
|
eating: boolean(),
|
|
68395
69238
|
/** Whether `markFoodReplenished` does anything on this model. When false
|
|
@@ -68467,9 +69310,9 @@ var petFeederCapability = {
|
|
|
68467
69310
|
*/
|
|
68468
69311
|
feed: method(object({
|
|
68469
69312
|
deviceId: number().int().nonnegative(),
|
|
68470
|
-
grams:
|
|
68471
|
-
hopper1:
|
|
68472
|
-
hopper2:
|
|
69313
|
+
grams: gramsParam.optional(),
|
|
69314
|
+
hopper1: gramsParam.optional(),
|
|
69315
|
+
hopper2: gramsParam.optional()
|
|
68473
69316
|
}), _void(), {
|
|
68474
69317
|
kind: "mutation",
|
|
68475
69318
|
auth: "protected"
|
|
@@ -68573,6 +69416,137 @@ var petFeederCapability = {
|
|
|
68573
69416
|
}
|
|
68574
69417
|
} }
|
|
68575
69418
|
};
|
|
69419
|
+
/**
|
|
69420
|
+
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
69421
|
+
* circulating drinking fountain: how much water is in the tank, how much the
|
|
69422
|
+
* pet drank, whether the pump runs and on what duty cycle.
|
|
69423
|
+
*
|
|
69424
|
+
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
69425
|
+
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
69426
|
+
* changed" button), and an operator BUILDS the device from them — a composed
|
|
69427
|
+
* device of this type (D659, D967). Every method is therefore a forward to ONE
|
|
69428
|
+
* method of ONE source entity, and every reading is a field derived from a
|
|
69429
|
+
* source entity; the provider-shaped interface below exists so the composed
|
|
69430
|
+
* device and any future native provider answer the same contract.
|
|
69431
|
+
*
|
|
69432
|
+
* Quantities are stored in the base unit of their kind (D944): volumes in
|
|
69433
|
+
* LITRES, durations in SECONDS, the tank level in percent. A source that
|
|
69434
|
+
* reports millilitres or minutes is converted in the composition's expression,
|
|
69435
|
+
* never here. Every reading may be `null`: a source that is offline or does
|
|
69436
|
+
* not report is unknown, never zero.
|
|
69437
|
+
*
|
|
69438
|
+
* The filter is not a field of this cap: it is an item of the `consumables`
|
|
69439
|
+
* cap on the same device (kind `wear`), reset by the same forward mechanism.
|
|
69440
|
+
*/
|
|
69441
|
+
var PetFountainStatusSchema = object({
|
|
69442
|
+
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
69443
|
+
waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
69444
|
+
/** Water in the tank, in litres. Null while the source has not reported. */
|
|
69445
|
+
waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69446
|
+
/** True when the tank is empty. Null while unknown. */
|
|
69447
|
+
empty: boolean().nullable(),
|
|
69448
|
+
/** True while the pump is running. Null while unknown. */
|
|
69449
|
+
pumpRunning: boolean().nullable(),
|
|
69450
|
+
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
69451
|
+
pumpIntervalEnabled: boolean().nullable(),
|
|
69452
|
+
/** How long the pump runs per cycle, in seconds. */
|
|
69453
|
+
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69454
|
+
/** How long the pump rests per cycle, in seconds. */
|
|
69455
|
+
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69456
|
+
/** Water drunk in the last drink, in litres. */
|
|
69457
|
+
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69458
|
+
/** Water drunk in the current hour, in litres. */
|
|
69459
|
+
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69460
|
+
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
69461
|
+
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69462
|
+
/** Water drunk since local midnight, in litres. */
|
|
69463
|
+
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69464
|
+
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
69465
|
+
error: string().nullable(),
|
|
69466
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
69467
|
+
lastFetchedAt: number()
|
|
69468
|
+
});
|
|
69469
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
69470
|
+
var MinutesSchema = number().int().min(0);
|
|
69471
|
+
var petFountainCapability = {
|
|
69472
|
+
name: "pet-fountain",
|
|
69473
|
+
scope: "device",
|
|
69474
|
+
deviceNative: true,
|
|
69475
|
+
mode: "singleton",
|
|
69476
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
69477
|
+
methods: {
|
|
69478
|
+
/** Start or stop the pump. */
|
|
69479
|
+
setPump: method(object({
|
|
69480
|
+
deviceId: DeviceIdSchema,
|
|
69481
|
+
on: boolean()
|
|
69482
|
+
}), _void(), {
|
|
69483
|
+
kind: "mutation",
|
|
69484
|
+
auth: "protected"
|
|
69485
|
+
}),
|
|
69486
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
69487
|
+
setPumpInterval: method(object({
|
|
69488
|
+
deviceId: DeviceIdSchema,
|
|
69489
|
+
on: boolean()
|
|
69490
|
+
}), _void(), {
|
|
69491
|
+
kind: "mutation",
|
|
69492
|
+
auth: "protected"
|
|
69493
|
+
}),
|
|
69494
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
69495
|
+
setPumpOnMinutes: method(object({
|
|
69496
|
+
deviceId: DeviceIdSchema,
|
|
69497
|
+
minutes: MinutesSchema
|
|
69498
|
+
}), _void(), {
|
|
69499
|
+
kind: "mutation",
|
|
69500
|
+
auth: "protected"
|
|
69501
|
+
}),
|
|
69502
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
69503
|
+
setPumpOffMinutes: method(object({
|
|
69504
|
+
deviceId: DeviceIdSchema,
|
|
69505
|
+
minutes: MinutesSchema
|
|
69506
|
+
}), _void(), {
|
|
69507
|
+
kind: "mutation",
|
|
69508
|
+
auth: "protected"
|
|
69509
|
+
}),
|
|
69510
|
+
/** Clear the rolling daily consumption counters on the device. */
|
|
69511
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
69512
|
+
kind: "mutation",
|
|
69513
|
+
auth: "protected"
|
|
69514
|
+
})
|
|
69515
|
+
},
|
|
69516
|
+
status: {
|
|
69517
|
+
schema: PetFountainStatusSchema,
|
|
69518
|
+
kind: "push"
|
|
69519
|
+
},
|
|
69520
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
69521
|
+
runtimeState: PetFountainStatusSchema,
|
|
69522
|
+
/**
|
|
69523
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
69524
|
+
*
|
|
69525
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
69526
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
69527
|
+
*/
|
|
69528
|
+
durability: "session",
|
|
69529
|
+
/** History (D850): recorded series. */
|
|
69530
|
+
history: { fields: {
|
|
69531
|
+
waterLevel: {
|
|
69532
|
+
kind: "gauge",
|
|
69533
|
+
unit: "%",
|
|
69534
|
+
deadband: 1
|
|
69535
|
+
},
|
|
69536
|
+
waterVolume: {
|
|
69537
|
+
kind: "gauge",
|
|
69538
|
+
unit: "L"
|
|
69539
|
+
},
|
|
69540
|
+
todayDrink: {
|
|
69541
|
+
kind: "gauge",
|
|
69542
|
+
unit: "L"
|
|
69543
|
+
},
|
|
69544
|
+
last24hDrink: {
|
|
69545
|
+
kind: "gauge",
|
|
69546
|
+
unit: "L"
|
|
69547
|
+
}
|
|
69548
|
+
} }
|
|
69549
|
+
};
|
|
68576
69550
|
var VehicleSchema = object({
|
|
68577
69551
|
id: string(),
|
|
68578
69552
|
name: string(),
|
|
@@ -74570,6 +75544,87 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
74570
75544
|
kind: "mutation",
|
|
74571
75545
|
auth: "admin"
|
|
74572
75546
|
});
|
|
75547
|
+
var btn = (idSuffix, row, col, init) => ({
|
|
75548
|
+
idSuffix,
|
|
75549
|
+
row,
|
|
75550
|
+
col,
|
|
75551
|
+
rowSpan: 1,
|
|
75552
|
+
colSpan: 1,
|
|
75553
|
+
...init
|
|
75554
|
+
});
|
|
75555
|
+
function rocker(id, name, it, frame, title, buttons, size) {
|
|
75556
|
+
return {
|
|
75557
|
+
id,
|
|
75558
|
+
name,
|
|
75559
|
+
nameI18n: {
|
|
75560
|
+
en: name,
|
|
75561
|
+
it
|
|
75562
|
+
},
|
|
75563
|
+
icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
|
|
75564
|
+
category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
|
|
75565
|
+
size,
|
|
75566
|
+
group: title === null ? { frame } : {
|
|
75567
|
+
frame,
|
|
75568
|
+
title
|
|
75569
|
+
},
|
|
75570
|
+
buttons
|
|
75571
|
+
};
|
|
75572
|
+
}
|
|
75573
|
+
btn("power", 0, 0, {
|
|
75574
|
+
role: "power",
|
|
75575
|
+
shape: "round",
|
|
75576
|
+
tone: "danger"
|
|
75577
|
+
}), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
|
|
75578
|
+
role: "ok",
|
|
75579
|
+
shape: "round",
|
|
75580
|
+
tone: "accent"
|
|
75581
|
+
}), btn("right", 1, 2, { role: "right" }), btn("down", 2, 1, { role: "down" }), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
|
|
75582
|
+
role: "volume_up",
|
|
75583
|
+
shape: "pill"
|
|
75584
|
+
}), btn("down", 1, 0, {
|
|
75585
|
+
role: "volume_down",
|
|
75586
|
+
shape: "pill"
|
|
75587
|
+
})], {
|
|
75588
|
+
rows: 2,
|
|
75589
|
+
cols: 1
|
|
75590
|
+
}), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
|
|
75591
|
+
role: "channel_up",
|
|
75592
|
+
shape: "pill"
|
|
75593
|
+
}), btn("down", 1, 0, {
|
|
75594
|
+
role: "channel_down",
|
|
75595
|
+
shape: "pill"
|
|
75596
|
+
})], {
|
|
75597
|
+
rows: 2,
|
|
75598
|
+
cols: 1
|
|
75599
|
+
}), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
|
|
75600
|
+
role: "custom:minus",
|
|
75601
|
+
label: "−",
|
|
75602
|
+
shape: "pill"
|
|
75603
|
+
}), btn("plus", 0, 1, {
|
|
75604
|
+
role: "custom:plus",
|
|
75605
|
+
label: "+",
|
|
75606
|
+
shape: "pill"
|
|
75607
|
+
})], {
|
|
75608
|
+
rows: 1,
|
|
75609
|
+
cols: 2
|
|
75610
|
+
}), [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })], btn("red", 0, 0, {
|
|
75611
|
+
role: "custom:color_red",
|
|
75612
|
+
tone: "red"
|
|
75613
|
+
}), btn("green", 0, 1, {
|
|
75614
|
+
role: "custom:color_green",
|
|
75615
|
+
tone: "green"
|
|
75616
|
+
}), btn("yellow", 0, 2, {
|
|
75617
|
+
role: "custom:color_yellow",
|
|
75618
|
+
tone: "yellow"
|
|
75619
|
+
}), btn("blue", 0, 3, {
|
|
75620
|
+
role: "custom:color_blue",
|
|
75621
|
+
tone: "blue"
|
|
75622
|
+
}), btn("rewind", 0, 0, { role: "rewind" }), btn("play", 0, 1, { role: "play" }), btn("pause", 0, 2, { role: "pause" }), btn("forward", 0, 3, { role: "fast_forward" }), btn("stop", 0, 4, { role: "stop" }), [
|
|
75623
|
+
1,
|
|
75624
|
+
2,
|
|
75625
|
+
3,
|
|
75626
|
+
4
|
|
75627
|
+
].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` })), btn("back", 0, 0, { role: "back" }), btn("home", 0, 1, { role: "home" }), btn("menu", 0, 2, { role: "menu" });
|
|
74573
75628
|
discriminatedUnion("kind", [object({
|
|
74574
75629
|
kind: literal("raw"),
|
|
74575
75630
|
timings: array(number().int()),
|
|
@@ -74580,7 +75635,7 @@ discriminatedUnion("kind", [object({
|
|
|
74580
75635
|
})]);
|
|
74581
75636
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
74582
75637
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
74583
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
75638
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
74584
75639
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
74585
75640
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
74586
75641
|
new Set(CAP_NAMES);
|
|
@@ -74984,6 +76039,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
74984
76039
|
notifier: notifierCapability,
|
|
74985
76040
|
numericSensor: numericSensorCapability,
|
|
74986
76041
|
petFeeder: petFeederCapability,
|
|
76042
|
+
petFountain: petFountainCapability,
|
|
74987
76043
|
powerMeter: powerMeterCapability,
|
|
74988
76044
|
presence: presenceCapability,
|
|
74989
76045
|
pressureSensor: pressureSensorCapability,
|
|
@@ -75751,7 +76807,7 @@ var DeclaredDevices = class {
|
|
|
75751
76807
|
return removed;
|
|
75752
76808
|
}
|
|
75753
76809
|
};
|
|
75754
|
-
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
76810
|
+
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
75755
76811
|
new Set(Object.values(DeviceType));
|
|
75756
76812
|
Object.freeze({
|
|
75757
76813
|
"accessories.getStatus": {
|
|
@@ -77686,6 +78742,13 @@ Object.freeze({
|
|
|
77686
78742
|
access: "create",
|
|
77687
78743
|
role: "admin"
|
|
77688
78744
|
},
|
|
78745
|
+
"deviceExport.exposeEntity": {
|
|
78746
|
+
capName: "device-export",
|
|
78747
|
+
capScope: "system",
|
|
78748
|
+
addonId: null,
|
|
78749
|
+
access: "create",
|
|
78750
|
+
role: "admin"
|
|
78751
|
+
},
|
|
77689
78752
|
"deviceExport.getDeviceLiveContribution": {
|
|
77690
78753
|
capName: "device-export",
|
|
77691
78754
|
capScope: "system",
|
|
@@ -77714,6 +78777,13 @@ Object.freeze({
|
|
|
77714
78777
|
access: "view",
|
|
77715
78778
|
role: "admin"
|
|
77716
78779
|
},
|
|
78780
|
+
"deviceExport.listExposedEntities": {
|
|
78781
|
+
capName: "device-export",
|
|
78782
|
+
capScope: "system",
|
|
78783
|
+
addonId: null,
|
|
78784
|
+
access: "view",
|
|
78785
|
+
role: "admin"
|
|
78786
|
+
},
|
|
77717
78787
|
"deviceExport.listSupportedDeviceKinds": {
|
|
77718
78788
|
capName: "device-export",
|
|
77719
78789
|
capScope: "system",
|
|
@@ -77721,6 +78791,13 @@ Object.freeze({
|
|
|
77721
78791
|
access: "view",
|
|
77722
78792
|
role: "admin"
|
|
77723
78793
|
},
|
|
78794
|
+
"deviceExport.listSupportedEntityKinds": {
|
|
78795
|
+
capName: "device-export",
|
|
78796
|
+
capScope: "system",
|
|
78797
|
+
addonId: null,
|
|
78798
|
+
access: "view",
|
|
78799
|
+
role: "admin"
|
|
78800
|
+
},
|
|
77724
78801
|
"deviceExport.unexposeDevice": {
|
|
77725
78802
|
capName: "device-export",
|
|
77726
78803
|
capScope: "system",
|
|
@@ -77728,6 +78805,13 @@ Object.freeze({
|
|
|
77728
78805
|
access: "create",
|
|
77729
78806
|
role: "admin"
|
|
77730
78807
|
},
|
|
78808
|
+
"deviceExport.unexposeEntity": {
|
|
78809
|
+
capName: "device-export",
|
|
78810
|
+
capScope: "system",
|
|
78811
|
+
addonId: null,
|
|
78812
|
+
access: "create",
|
|
78813
|
+
role: "admin"
|
|
78814
|
+
},
|
|
77731
78815
|
"deviceExtension.listCandidates": {
|
|
77732
78816
|
capName: "device-extension",
|
|
77733
78817
|
capScope: "device",
|
|
@@ -81193,6 +82277,48 @@ Object.freeze({
|
|
|
81193
82277
|
access: "create",
|
|
81194
82278
|
role: "viewer"
|
|
81195
82279
|
},
|
|
82280
|
+
"petFountain.getStatus": {
|
|
82281
|
+
capName: "pet-fountain",
|
|
82282
|
+
capScope: "device",
|
|
82283
|
+
addonId: null,
|
|
82284
|
+
access: "view",
|
|
82285
|
+
role: "viewer"
|
|
82286
|
+
},
|
|
82287
|
+
"petFountain.resetDailyConsumption": {
|
|
82288
|
+
capName: "pet-fountain",
|
|
82289
|
+
capScope: "device",
|
|
82290
|
+
addonId: null,
|
|
82291
|
+
access: "delete",
|
|
82292
|
+
role: "viewer"
|
|
82293
|
+
},
|
|
82294
|
+
"petFountain.setPump": {
|
|
82295
|
+
capName: "pet-fountain",
|
|
82296
|
+
capScope: "device",
|
|
82297
|
+
addonId: null,
|
|
82298
|
+
access: "create",
|
|
82299
|
+
role: "viewer"
|
|
82300
|
+
},
|
|
82301
|
+
"petFountain.setPumpInterval": {
|
|
82302
|
+
capName: "pet-fountain",
|
|
82303
|
+
capScope: "device",
|
|
82304
|
+
addonId: null,
|
|
82305
|
+
access: "create",
|
|
82306
|
+
role: "viewer"
|
|
82307
|
+
},
|
|
82308
|
+
"petFountain.setPumpOffMinutes": {
|
|
82309
|
+
capName: "pet-fountain",
|
|
82310
|
+
capScope: "device",
|
|
82311
|
+
addonId: null,
|
|
82312
|
+
access: "create",
|
|
82313
|
+
role: "viewer"
|
|
82314
|
+
},
|
|
82315
|
+
"petFountain.setPumpOnMinutes": {
|
|
82316
|
+
capName: "pet-fountain",
|
|
82317
|
+
capScope: "device",
|
|
82318
|
+
addonId: null,
|
|
82319
|
+
access: "create",
|
|
82320
|
+
role: "viewer"
|
|
82321
|
+
},
|
|
81196
82322
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
81197
82323
|
capName: "pipeline-analytics",
|
|
81198
82324
|
capScope: "device",
|
|
@@ -83482,6 +84608,13 @@ Object.freeze({
|
|
|
83482
84608
|
access: "create",
|
|
83483
84609
|
role: "admin"
|
|
83484
84610
|
},
|
|
84611
|
+
"remoteLayoutComponents.listComponents": {
|
|
84612
|
+
capName: "remote-layout-components",
|
|
84613
|
+
capScope: "system",
|
|
84614
|
+
addonId: null,
|
|
84615
|
+
access: "view",
|
|
84616
|
+
role: "admin"
|
|
84617
|
+
},
|
|
83485
84618
|
"runningState.getStatus": {
|
|
83486
84619
|
capName: "running-state",
|
|
83487
84620
|
capScope: "device",
|
|
@@ -83594,6 +84727,13 @@ Object.freeze({
|
|
|
83594
84727
|
access: "create",
|
|
83595
84728
|
role: "admin"
|
|
83596
84729
|
},
|
|
84730
|
+
"serverManagement.getLaunchOverrides": {
|
|
84731
|
+
capName: "server-management",
|
|
84732
|
+
capScope: "system",
|
|
84733
|
+
addonId: null,
|
|
84734
|
+
access: "view",
|
|
84735
|
+
role: "admin"
|
|
84736
|
+
},
|
|
83597
84737
|
"serverManagement.getServerPackageStatus": {
|
|
83598
84738
|
capName: "server-management",
|
|
83599
84739
|
capScope: "system",
|
|
@@ -83601,6 +84741,13 @@ Object.freeze({
|
|
|
83601
84741
|
access: "view",
|
|
83602
84742
|
role: "admin"
|
|
83603
84743
|
},
|
|
84744
|
+
"serverManagement.installDesktopUpdate": {
|
|
84745
|
+
capName: "server-management",
|
|
84746
|
+
capScope: "system",
|
|
84747
|
+
addonId: null,
|
|
84748
|
+
access: "create",
|
|
84749
|
+
role: "admin"
|
|
84750
|
+
},
|
|
83604
84751
|
"serverManagement.restartServer": {
|
|
83605
84752
|
capName: "server-management",
|
|
83606
84753
|
capScope: "system",
|
|
@@ -83615,6 +84762,13 @@ Object.freeze({
|
|
|
83615
84762
|
access: "create",
|
|
83616
84763
|
role: "admin"
|
|
83617
84764
|
},
|
|
84765
|
+
"serverManagement.setLaunchOverrides": {
|
|
84766
|
+
capName: "server-management",
|
|
84767
|
+
capScope: "system",
|
|
84768
|
+
addonId: null,
|
|
84769
|
+
access: "create",
|
|
84770
|
+
role: "admin"
|
|
84771
|
+
},
|
|
83618
84772
|
"settingsStore.aggregate": {
|
|
83619
84773
|
capName: "settings-store",
|
|
83620
84774
|
capScope: "system",
|
|
@@ -87504,7 +88658,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87504
88658
|
min: 4,
|
|
87505
88659
|
max: 200,
|
|
87506
88660
|
step: 1,
|
|
87507
|
-
unit: "g"
|
|
88661
|
+
unit: "g",
|
|
88662
|
+
quantity: "mass"
|
|
87508
88663
|
},
|
|
87509
88664
|
optional: true
|
|
87510
88665
|
}]
|
|
@@ -87523,7 +88678,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87523
88678
|
min: 4,
|
|
87524
88679
|
max: 200,
|
|
87525
88680
|
step: 1,
|
|
87526
|
-
unit: "g"
|
|
88681
|
+
unit: "g",
|
|
88682
|
+
quantity: "mass"
|
|
87527
88683
|
},
|
|
87528
88684
|
optional: true
|
|
87529
88685
|
}, {
|
|
@@ -87534,7 +88690,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87534
88690
|
min: 4,
|
|
87535
88691
|
max: 200,
|
|
87536
88692
|
step: 1,
|
|
87537
|
-
unit: "g"
|
|
88693
|
+
unit: "g",
|
|
88694
|
+
quantity: "mass"
|
|
87538
88695
|
},
|
|
87539
88696
|
optional: true
|
|
87540
88697
|
}]
|
|
@@ -87675,6 +88832,100 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87675
88832
|
}
|
|
87676
88833
|
]
|
|
87677
88834
|
},
|
|
88835
|
+
"pet-fountain": {
|
|
88836
|
+
scope: "device",
|
|
88837
|
+
noun: t("Pet fountain", "Fontana per animali"),
|
|
88838
|
+
actions: [
|
|
88839
|
+
{
|
|
88840
|
+
id: "petFountain.pumpOn",
|
|
88841
|
+
method: "setPump",
|
|
88842
|
+
label: t("Pump on", "Pompa accesa"),
|
|
88843
|
+
phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
|
|
88844
|
+
icon: "play",
|
|
88845
|
+
args: { on: true }
|
|
88846
|
+
},
|
|
88847
|
+
{
|
|
88848
|
+
id: "petFountain.pumpOff",
|
|
88849
|
+
method: "setPump",
|
|
88850
|
+
label: t("Pump off", "Pompa spenta"),
|
|
88851
|
+
phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
|
|
88852
|
+
icon: "square",
|
|
88853
|
+
args: { on: false }
|
|
88854
|
+
},
|
|
88855
|
+
{
|
|
88856
|
+
id: "petFountain.intervalOn",
|
|
88857
|
+
method: "setPumpInterval",
|
|
88858
|
+
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
88859
|
+
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
88860
|
+
icon: "timer",
|
|
88861
|
+
args: { on: true }
|
|
88862
|
+
},
|
|
88863
|
+
{
|
|
88864
|
+
id: "petFountain.intervalOff",
|
|
88865
|
+
method: "setPumpInterval",
|
|
88866
|
+
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
88867
|
+
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
88868
|
+
icon: "timer-off",
|
|
88869
|
+
args: { on: false }
|
|
88870
|
+
},
|
|
88871
|
+
{
|
|
88872
|
+
id: "petFountain.pumpOnMinutes",
|
|
88873
|
+
method: "setPumpOnMinutes",
|
|
88874
|
+
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
88875
|
+
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
88876
|
+
icon: "timer",
|
|
88877
|
+
openArgs: [{
|
|
88878
|
+
key: "minutes",
|
|
88879
|
+
label: t("Minutes", "Minuti"),
|
|
88880
|
+
offer: {
|
|
88881
|
+
kind: "number",
|
|
88882
|
+
min: 0,
|
|
88883
|
+
step: 1,
|
|
88884
|
+
unit: "min"
|
|
88885
|
+
}
|
|
88886
|
+
}]
|
|
88887
|
+
},
|
|
88888
|
+
{
|
|
88889
|
+
id: "petFountain.pumpOffMinutes",
|
|
88890
|
+
method: "setPumpOffMinutes",
|
|
88891
|
+
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
88892
|
+
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
88893
|
+
icon: "timer-off",
|
|
88894
|
+
openArgs: [{
|
|
88895
|
+
key: "minutes",
|
|
88896
|
+
label: t("Minutes", "Minuti"),
|
|
88897
|
+
offer: {
|
|
88898
|
+
kind: "number",
|
|
88899
|
+
min: 0,
|
|
88900
|
+
step: 1,
|
|
88901
|
+
unit: "min"
|
|
88902
|
+
}
|
|
88903
|
+
}]
|
|
88904
|
+
},
|
|
88905
|
+
{
|
|
88906
|
+
id: "petFountain.resetDailyConsumption",
|
|
88907
|
+
method: "resetDailyConsumption",
|
|
88908
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
88909
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
88910
|
+
icon: "rotate-ccw"
|
|
88911
|
+
}
|
|
88912
|
+
],
|
|
88913
|
+
state: [{
|
|
88914
|
+
kind: "boolean",
|
|
88915
|
+
field: "empty",
|
|
88916
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
88917
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
88918
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
88919
|
+
icon: "droplet"
|
|
88920
|
+
}, {
|
|
88921
|
+
kind: "numeric",
|
|
88922
|
+
field: "waterLevel",
|
|
88923
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
88924
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
88925
|
+
unit: "%",
|
|
88926
|
+
icon: "droplets"
|
|
88927
|
+
}]
|
|
88928
|
+
},
|
|
87678
88929
|
"media-player": {
|
|
87679
88930
|
scope: "device",
|
|
87680
88931
|
noun: t("Media player", "Lettore multimediale"),
|
|
@@ -89540,6 +90791,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
89540
90791
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
89541
90792
|
records: system(INTERNAL),
|
|
89542
90793
|
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
90794
|
+
"remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
|
|
89543
90795
|
"server-management": system(CLUSTER),
|
|
89544
90796
|
"settings-store": system(INTERNAL),
|
|
89545
90797
|
"smtp-provider": system(INTERNAL),
|
|
@@ -89612,7 +90864,7 @@ function projectAction(cap, action) {
|
|
|
89612
90864
|
};
|
|
89613
90865
|
}
|
|
89614
90866
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
89615
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
90867
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
89616
90868
|
DeviceFeature.BatteryOperated;
|
|
89617
90869
|
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
89618
90870
|
var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
|
|
@@ -91163,6 +92415,36 @@ Object.freeze({
|
|
|
91163
92415
|
form: "single",
|
|
91164
92416
|
optional: false
|
|
91165
92417
|
}],
|
|
92418
|
+
"petFountain.getStatus": [{
|
|
92419
|
+
name: "deviceId",
|
|
92420
|
+
form: "single",
|
|
92421
|
+
optional: false
|
|
92422
|
+
}],
|
|
92423
|
+
"petFountain.resetDailyConsumption": [{
|
|
92424
|
+
name: "deviceId",
|
|
92425
|
+
form: "single",
|
|
92426
|
+
optional: false
|
|
92427
|
+
}],
|
|
92428
|
+
"petFountain.setPump": [{
|
|
92429
|
+
name: "deviceId",
|
|
92430
|
+
form: "single",
|
|
92431
|
+
optional: false
|
|
92432
|
+
}],
|
|
92433
|
+
"petFountain.setPumpInterval": [{
|
|
92434
|
+
name: "deviceId",
|
|
92435
|
+
form: "single",
|
|
92436
|
+
optional: false
|
|
92437
|
+
}],
|
|
92438
|
+
"petFountain.setPumpOffMinutes": [{
|
|
92439
|
+
name: "deviceId",
|
|
92440
|
+
form: "single",
|
|
92441
|
+
optional: false
|
|
92442
|
+
}],
|
|
92443
|
+
"petFountain.setPumpOnMinutes": [{
|
|
92444
|
+
name: "deviceId",
|
|
92445
|
+
form: "single",
|
|
92446
|
+
optional: false
|
|
92447
|
+
}],
|
|
91166
92448
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
91167
92449
|
name: "deviceId",
|
|
91168
92450
|
form: "single",
|
|
@@ -95559,8 +96841,6 @@ var NC_TEMPLATE_VARS = [
|
|
|
95559
96841
|
* delete it from here then, and the served catalog names it again.
|
|
95560
96842
|
*/
|
|
95561
96843
|
var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
|
|
95562
|
-
"node-restarting",
|
|
95563
|
-
"node-updating",
|
|
95564
96844
|
"alert-critical",
|
|
95565
96845
|
"alert-error",
|
|
95566
96846
|
"alert-critical-resolved",
|