@camstack/addon-post-analysis 1.2.651 → 1.2.652
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 +6 -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 +16 -3
- package/dist/pipeline-analytics/index.mjs +16 -3
- 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
|
@@ -5645,7 +5645,7 @@ var ZodIssueCode = {
|
|
|
5645
5645
|
var ZodFirstPartyTypeKind;
|
|
5646
5646
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5647
5647
|
//#endregion
|
|
5648
|
-
//#region ../types/dist/sleep-
|
|
5648
|
+
//#region ../types/dist/sleep-63gM_1JR.mjs
|
|
5649
5649
|
/**
|
|
5650
5650
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5651
5651
|
* window to float samples (D455).
|
|
@@ -8746,65 +8746,6 @@ var MODEL_FORMATS = [
|
|
|
8746
8746
|
"pt",
|
|
8747
8747
|
"gguf"
|
|
8748
8748
|
];
|
|
8749
|
-
/**
|
|
8750
|
-
* Per-call node pinning for `ctx.api` capability calls.
|
|
8751
|
-
*
|
|
8752
|
-
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8753
|
-
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8754
|
-
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8755
|
-
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8756
|
-
* call to that node.
|
|
8757
|
-
*
|
|
8758
|
-
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8759
|
-
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8760
|
-
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8761
|
-
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8762
|
-
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8763
|
-
* which classifies a pinned agent node as `agent-child-forward`
|
|
8764
|
-
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8765
|
-
*
|
|
8766
|
-
* Usage at a call site:
|
|
8767
|
-
*
|
|
8768
|
-
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8769
|
-
*/
|
|
8770
|
-
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8771
|
-
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8772
|
-
/**
|
|
8773
|
-
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8774
|
-
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8775
|
-
*
|
|
8776
|
-
* ## The id is normalised here, and it has to be
|
|
8777
|
-
*
|
|
8778
|
-
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8779
|
-
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8780
|
-
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8781
|
-
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8782
|
-
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8783
|
-
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8784
|
-
* the thing you reach for when you specifically need THIS node — silently
|
|
8785
|
-
* addressed a node that does not exist.
|
|
8786
|
-
*
|
|
8787
|
-
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8788
|
-
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8789
|
-
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8790
|
-
* while D67's whole premise was that the decoder addon is the authority on
|
|
8791
|
-
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8792
|
-
* hardware", which was false.
|
|
8793
|
-
*
|
|
8794
|
-
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8795
|
-
* it is here and not at the call sites.
|
|
8796
|
-
*/
|
|
8797
|
-
function nodePin(nodeId) {
|
|
8798
|
-
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8799
|
-
}
|
|
8800
|
-
/**
|
|
8801
|
-
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8802
|
-
* is idempotent, so passing an already-clean id costs nothing.
|
|
8803
|
-
*/
|
|
8804
|
-
function toNodeId(idOrRunnerId) {
|
|
8805
|
-
const head = idOrRunnerId.split("/")[0];
|
|
8806
|
-
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8807
|
-
}
|
|
8808
8749
|
object({
|
|
8809
8750
|
field: string().min(1),
|
|
8810
8751
|
on: literal("advance")
|
|
@@ -8897,6 +8838,111 @@ function optionalMethod(input, output, options) {
|
|
|
8897
8838
|
function event(data) {
|
|
8898
8839
|
return { data };
|
|
8899
8840
|
}
|
|
8841
|
+
/**
|
|
8842
|
+
* Per-call node pinning for `ctx.api` capability calls.
|
|
8843
|
+
*
|
|
8844
|
+
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8845
|
+
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8846
|
+
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8847
|
+
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8848
|
+
* call to that node.
|
|
8849
|
+
*
|
|
8850
|
+
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8851
|
+
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8852
|
+
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8853
|
+
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8854
|
+
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8855
|
+
* which classifies a pinned agent node as `agent-child-forward`
|
|
8856
|
+
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8857
|
+
*
|
|
8858
|
+
* Usage at a call site:
|
|
8859
|
+
*
|
|
8860
|
+
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8861
|
+
*/
|
|
8862
|
+
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8863
|
+
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8864
|
+
/**
|
|
8865
|
+
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8866
|
+
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8867
|
+
*
|
|
8868
|
+
* ## The id is normalised here, and it has to be
|
|
8869
|
+
*
|
|
8870
|
+
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8871
|
+
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8872
|
+
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8873
|
+
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8874
|
+
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8875
|
+
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8876
|
+
* the thing you reach for when you specifically need THIS node — silently
|
|
8877
|
+
* addressed a node that does not exist.
|
|
8878
|
+
*
|
|
8879
|
+
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8880
|
+
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8881
|
+
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8882
|
+
* while D67's whole premise was that the decoder addon is the authority on
|
|
8883
|
+
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8884
|
+
* hardware", which was false.
|
|
8885
|
+
*
|
|
8886
|
+
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8887
|
+
* it is here and not at the call sites.
|
|
8888
|
+
*/
|
|
8889
|
+
function nodePin(nodeId) {
|
|
8890
|
+
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8891
|
+
}
|
|
8892
|
+
/**
|
|
8893
|
+
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8894
|
+
* is idempotent, so passing an already-clean id costs nothing.
|
|
8895
|
+
*/
|
|
8896
|
+
function toNodeId(idOrRunnerId) {
|
|
8897
|
+
const head = idOrRunnerId.split("/")[0];
|
|
8898
|
+
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8899
|
+
}
|
|
8900
|
+
/**
|
|
8901
|
+
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
8902
|
+
* declarations. Every addon that contributes a UI page registers a
|
|
8903
|
+
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
8904
|
+
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
8905
|
+
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
8906
|
+
* that admin-ui consumes.
|
|
8907
|
+
*
|
|
8908
|
+
* The split exists because the public listing has a different output
|
|
8909
|
+
* shape than the per-provider raw declarations, and we want both ends
|
|
8910
|
+
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
8911
|
+
*/
|
|
8912
|
+
var AddonPageDeclarationSchema = object({
|
|
8913
|
+
id: string(),
|
|
8914
|
+
label: string(),
|
|
8915
|
+
icon: string(),
|
|
8916
|
+
path: string(),
|
|
8917
|
+
/**
|
|
8918
|
+
* Module Federation remote name — must match the `name` field on the
|
|
8919
|
+
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
8920
|
+
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
8921
|
+
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
8922
|
+
* contain hyphens).
|
|
8923
|
+
*/
|
|
8924
|
+
remoteName: string(),
|
|
8925
|
+
/**
|
|
8926
|
+
* Bundle filename inside the addon's `dist/` dir served at
|
|
8927
|
+
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
8928
|
+
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
8929
|
+
* the static-file route can compute an mtime-based cache-buster URL
|
|
8930
|
+
* without a separate filesystem stat.
|
|
8931
|
+
*/
|
|
8932
|
+
bundle: string(),
|
|
8933
|
+
/**
|
|
8934
|
+
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
8935
|
+
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
8936
|
+
* Any OTHER string creates (or joins) a custom section rendered after
|
|
8937
|
+
* the built-in groups; its label comes from `sectionLabel` (first
|
|
8938
|
+
* declaration wins), falling back to the id. Absent → the legacy
|
|
8939
|
+
* "Addon Pages" group.
|
|
8940
|
+
*/
|
|
8941
|
+
section: string().optional(),
|
|
8942
|
+
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
8943
|
+
sectionLabel: string().optional()
|
|
8944
|
+
});
|
|
8945
|
+
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
8900
8946
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
8901
8947
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
8902
8948
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
@@ -8922,6 +8968,27 @@ function customAction(input, output, options) {
|
|
|
8922
8968
|
...options?.caller ? { caller: "required" } : {}
|
|
8923
8969
|
};
|
|
8924
8970
|
}
|
|
8971
|
+
/**
|
|
8972
|
+
* The pipeline step-input shape and the run-result bridge, in a module that
|
|
8973
|
+
* imports nothing but zod and types.
|
|
8974
|
+
*
|
|
8975
|
+
* WHY A SEPARATE FILE (addon footprint, D28): both were declared in
|
|
8976
|
+
* `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
|
|
8977
|
+
* graph. An addon that only needs these two (the benchmark's `runPipelineTest`
|
|
8978
|
+
* action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
|
|
8979
|
+
* them, so every existing import keeps working; addon server code takes them
|
|
8980
|
+
* from `@camstack/types/addon`.
|
|
8981
|
+
*/
|
|
8982
|
+
var PipelineStepInputSchema = lazy(() => object({
|
|
8983
|
+
addonId: string(),
|
|
8984
|
+
modelId: string().optional(),
|
|
8985
|
+
enabled: boolean().default(true),
|
|
8986
|
+
children: array(PipelineStepInputSchema).optional(),
|
|
8987
|
+
settings: record(string(), unknown()).optional(),
|
|
8988
|
+
jumpDeviceKey: string().optional()
|
|
8989
|
+
}));
|
|
8990
|
+
/** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
|
|
8991
|
+
var PipelineRunResultBridge = custom();
|
|
8925
8992
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
8926
8993
|
var VersionOutputSchema = object({ version: string() });
|
|
8927
8994
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -9118,6 +9185,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9118
9185
|
"osd",
|
|
9119
9186
|
"osd-manager",
|
|
9120
9187
|
"pet-feeder",
|
|
9188
|
+
"pet-fountain",
|
|
9121
9189
|
"pipeline-analytics",
|
|
9122
9190
|
"pipeline-executor",
|
|
9123
9191
|
"pipeline-orchestrator",
|
|
@@ -9139,6 +9207,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9139
9207
|
"recording-signal",
|
|
9140
9208
|
"records",
|
|
9141
9209
|
"remote",
|
|
9210
|
+
"remote-layout-components",
|
|
9142
9211
|
"running-state",
|
|
9143
9212
|
"scene-monitor",
|
|
9144
9213
|
"script-runner",
|
|
@@ -10397,6 +10466,56 @@ Object.freeze({
|
|
|
10397
10466
|
"valueType": "number"
|
|
10398
10467
|
}
|
|
10399
10468
|
],
|
|
10469
|
+
"pet-fountain": [
|
|
10470
|
+
{
|
|
10471
|
+
"path": "waterLevel",
|
|
10472
|
+
"valueType": "number"
|
|
10473
|
+
},
|
|
10474
|
+
{
|
|
10475
|
+
"path": "waterVolume",
|
|
10476
|
+
"valueType": "number"
|
|
10477
|
+
},
|
|
10478
|
+
{
|
|
10479
|
+
"path": "empty",
|
|
10480
|
+
"valueType": "boolean"
|
|
10481
|
+
},
|
|
10482
|
+
{
|
|
10483
|
+
"path": "pumpRunning",
|
|
10484
|
+
"valueType": "boolean"
|
|
10485
|
+
},
|
|
10486
|
+
{
|
|
10487
|
+
"path": "pumpIntervalEnabled",
|
|
10488
|
+
"valueType": "boolean"
|
|
10489
|
+
},
|
|
10490
|
+
{
|
|
10491
|
+
"path": "pumpOnTime",
|
|
10492
|
+
"valueType": "number"
|
|
10493
|
+
},
|
|
10494
|
+
{
|
|
10495
|
+
"path": "pumpOffTime",
|
|
10496
|
+
"valueType": "number"
|
|
10497
|
+
},
|
|
10498
|
+
{
|
|
10499
|
+
"path": "lastDrink",
|
|
10500
|
+
"valueType": "number"
|
|
10501
|
+
},
|
|
10502
|
+
{
|
|
10503
|
+
"path": "currentHourDrink",
|
|
10504
|
+
"valueType": "number"
|
|
10505
|
+
},
|
|
10506
|
+
{
|
|
10507
|
+
"path": "last24hDrink",
|
|
10508
|
+
"valueType": "number"
|
|
10509
|
+
},
|
|
10510
|
+
{
|
|
10511
|
+
"path": "todayDrink",
|
|
10512
|
+
"valueType": "number"
|
|
10513
|
+
},
|
|
10514
|
+
{
|
|
10515
|
+
"path": "error",
|
|
10516
|
+
"valueType": "string"
|
|
10517
|
+
}
|
|
10518
|
+
],
|
|
10400
10519
|
"power-meter": [
|
|
10401
10520
|
{
|
|
10402
10521
|
"path": "power",
|
|
@@ -10936,7 +11055,7 @@ Object.freeze({
|
|
|
10936
11055
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
10937
11056
|
* (~144MB RSS per runner, D28).
|
|
10938
11057
|
*
|
|
10939
|
-
* Coverage:
|
|
11058
|
+
* Coverage: 97 device-scoped capabilities.
|
|
10940
11059
|
*/
|
|
10941
11060
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
10942
11061
|
"accessories",
|
|
@@ -11002,6 +11121,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
11002
11121
|
"numeric-sensor",
|
|
11003
11122
|
"osd",
|
|
11004
11123
|
"pet-feeder",
|
|
11124
|
+
"pet-fountain",
|
|
11005
11125
|
"pipeline-analytics",
|
|
11006
11126
|
"power-meter",
|
|
11007
11127
|
"presence",
|
|
@@ -11080,7 +11200,7 @@ function sleep(ms) {
|
|
|
11080
11200
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11081
11201
|
}
|
|
11082
11202
|
//#endregion
|
|
11083
|
-
//#region ../types/dist/err-msg-
|
|
11203
|
+
//#region ../types/dist/err-msg-DWXWc0Di.mjs
|
|
11084
11204
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
11085
11205
|
DeviceType["Camera"] = "camera";
|
|
11086
11206
|
DeviceType["Hub"] = "hub";
|
|
@@ -11172,6 +11292,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
11172
11292
|
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
11173
11293
|
* integrations sharing the same food/desiccant/hopper surface. */
|
|
11174
11294
|
DeviceType["PetFeeder"] = "pet-feeder";
|
|
11295
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
11296
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
11297
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
11298
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
11175
11299
|
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
11176
11300
|
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
11177
11301
|
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
@@ -11442,7 +11566,7 @@ function errMsg(err) {
|
|
|
11442
11566
|
return String(err);
|
|
11443
11567
|
}
|
|
11444
11568
|
//#endregion
|
|
11445
|
-
//#region ../types/dist/composition-
|
|
11569
|
+
//#region ../types/dist/composition-kSuOifuG.mjs
|
|
11446
11570
|
/**
|
|
11447
11571
|
* Deterministic SHA-256 hash of an arbitrary serialisable value. The
|
|
11448
11572
|
* canonical form sorts object keys alphabetically at every depth so two
|
|
@@ -12687,12 +12811,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12687
12811
|
])
|
|
12688
12812
|
})
|
|
12689
12813
|
]);
|
|
12690
|
-
var
|
|
12814
|
+
var forwardArgUnion = union([
|
|
12815
|
+
string(),
|
|
12816
|
+
number(),
|
|
12817
|
+
boolean(),
|
|
12818
|
+
strictObject({ ["$input"]: string().min(1) }),
|
|
12819
|
+
record(string().min(1), lazy(() => forwardArgUnion))
|
|
12820
|
+
]);
|
|
12821
|
+
var CompositionForwardArgSchema = forwardArgUnion;
|
|
12822
|
+
/**
|
|
12823
|
+
* `forward` (D659, amended by D967): one method of a BUILT capability is one
|
|
12824
|
+
* call of `cap.method` on `source`. Without `args` the built method's input
|
|
12825
|
+
* passes through minus `deviceId`; with `args` the template IS the source's
|
|
12826
|
+
* whole input. `deviceId` is always the source's own, never an argument.
|
|
12827
|
+
*/
|
|
12828
|
+
var CompositionForwardCommandSchema = object({
|
|
12691
12829
|
kind: literal("forward"),
|
|
12692
12830
|
source: CompositionSourceRefSchema,
|
|
12693
12831
|
cap: string().min(1),
|
|
12694
|
-
method: string().min(1)
|
|
12695
|
-
|
|
12832
|
+
method: string().min(1),
|
|
12833
|
+
args: record(string().min(1), CompositionForwardArgSchema).optional()
|
|
12834
|
+
});
|
|
12835
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
|
|
12696
12836
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12697
12837
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12698
12838
|
/**
|
|
@@ -12702,7 +12842,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
|
12702
12842
|
*/
|
|
12703
12843
|
var CompositionItemEntrySchema = object({
|
|
12704
12844
|
label: string().min(1).max(80),
|
|
12705
|
-
fields: record(string().min(1), CompositionFieldSourceSchema)
|
|
12845
|
+
fields: record(string().min(1), CompositionFieldSourceSchema),
|
|
12846
|
+
/**
|
|
12847
|
+
* What the cap's `reset` does for THIS item (consumables). Present = the item
|
|
12848
|
+
* is resettable and a `reset` call naming its key forwards here. The item key
|
|
12849
|
+
* selects the command, so it is never an argument: there is no pass-through.
|
|
12850
|
+
*/
|
|
12851
|
+
reset: CompositionForwardCommandSchema.optional()
|
|
12706
12852
|
});
|
|
12707
12853
|
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
12708
12854
|
kind: literal("fields"),
|
|
@@ -12714,7 +12860,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
|
12714
12860
|
* owns the WHOLE array field: the composed value holds exactly these items.
|
|
12715
12861
|
*/
|
|
12716
12862
|
items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
|
|
12717
|
-
/**
|
|
12863
|
+
/** Method name → what serves it. Only a BUILT capability takes commands (D967). */
|
|
12718
12864
|
commands: record(string().min(1), CompositionCommandTargetSchema).optional()
|
|
12719
12865
|
}), object({
|
|
12720
12866
|
kind: literal("passthrough"),
|
|
@@ -16857,6 +17003,538 @@ object({
|
|
|
16857
17003
|
path: ["expr"]
|
|
16858
17004
|
});
|
|
16859
17005
|
});
|
|
17006
|
+
/**
|
|
17007
|
+
* Identity envelope for a device's upstream-system metadata.
|
|
17008
|
+
*
|
|
17009
|
+
* Two jobs:
|
|
17010
|
+
*
|
|
17011
|
+
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
17012
|
+
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
17013
|
+
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
17014
|
+
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
17015
|
+
* without round-tripping through `stableId` / `id` mappings.
|
|
17016
|
+
*
|
|
17017
|
+
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
17018
|
+
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
17019
|
+
* for vendors where MAC is the natural key). For systems where the
|
|
17020
|
+
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
17021
|
+
* providers detect renames by matching `uniqueId` and patch `id`
|
|
17022
|
+
* in-place so dispatch keeps working.
|
|
17023
|
+
*
|
|
17024
|
+
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
17025
|
+
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
17026
|
+
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
17027
|
+
* neither is repeated on each sample. The `device_class` tag is an
|
|
17028
|
+
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
17029
|
+
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
17030
|
+
*
|
|
17031
|
+
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
17032
|
+
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
17033
|
+
* details, …). Providers control what lands here.
|
|
17034
|
+
*/
|
|
17035
|
+
var SourceInfoSchema = object({
|
|
17036
|
+
/** Live dispatch key — mutable when the source system allows rename. */
|
|
17037
|
+
id: string(),
|
|
17038
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
17039
|
+
system: string(),
|
|
17040
|
+
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
17041
|
+
* Used to detect rename when `id` changes. */
|
|
17042
|
+
uniqueId: string().optional(),
|
|
17043
|
+
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
17044
|
+
quantity: ReadingQuantitySchema.optional(),
|
|
17045
|
+
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
17046
|
+
dateTimeShape: _enum([
|
|
17047
|
+
"date",
|
|
17048
|
+
"time",
|
|
17049
|
+
"datetime"
|
|
17050
|
+
]).optional(),
|
|
17051
|
+
/** Provider-specific extras that don't fit the structured slots.
|
|
17052
|
+
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
17053
|
+
* attribute blobs (would explode the meta JSON column). */
|
|
17054
|
+
raw: record(string(), unknown()).optional()
|
|
17055
|
+
});
|
|
17056
|
+
/**
|
|
17057
|
+
* Build the synthetic SourceInfo every existing provider falls back to
|
|
17058
|
+
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
17059
|
+
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
17060
|
+
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
17061
|
+
* id. Real upstream identity replaces this once a provider migrates and
|
|
17062
|
+
* calls `updateSourceInfo()` with concrete values.
|
|
17063
|
+
*/
|
|
17064
|
+
function synthesizeSourceInfo(input) {
|
|
17065
|
+
return {
|
|
17066
|
+
id: input.stableId,
|
|
17067
|
+
system: input.addonId
|
|
17068
|
+
};
|
|
17069
|
+
}
|
|
17070
|
+
/**
|
|
17071
|
+
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
17072
|
+
* patch values are ignored (no field clobber); to clear a field
|
|
17073
|
+
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
17074
|
+
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
17075
|
+
* bother with a `null`-means-delete sentinel.
|
|
17076
|
+
*
|
|
17077
|
+
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
17078
|
+
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
17079
|
+
*/
|
|
17080
|
+
function mergeSourceInfo(prev, patch) {
|
|
17081
|
+
const next = { ...prev };
|
|
17082
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
17083
|
+
if (v === void 0) continue;
|
|
17084
|
+
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
17085
|
+
next.raw = {
|
|
17086
|
+
...prev.raw,
|
|
17087
|
+
...v
|
|
17088
|
+
};
|
|
17089
|
+
continue;
|
|
17090
|
+
}
|
|
17091
|
+
next[k] = v;
|
|
17092
|
+
}
|
|
17093
|
+
return next;
|
|
17094
|
+
}
|
|
17095
|
+
/**
|
|
17096
|
+
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
17097
|
+
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
17098
|
+
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
17099
|
+
* lives under a single nested key to keep it composable with those.
|
|
17100
|
+
*/
|
|
17101
|
+
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
17102
|
+
/**
|
|
17103
|
+
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
17104
|
+
* with Zod. Returns `null` when the blob is missing or the nested
|
|
17105
|
+
* `sourceInfo` key fails validation — callers fall back to the
|
|
17106
|
+
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
17107
|
+
*/
|
|
17108
|
+
function extractSourceInfoFromMetadata(metadata) {
|
|
17109
|
+
if (!metadata) return null;
|
|
17110
|
+
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
17111
|
+
if (!raw) return null;
|
|
17112
|
+
const parsed = SourceInfoSchema.safeParse(raw);
|
|
17113
|
+
if (!parsed.success) return null;
|
|
17114
|
+
return parsed.data;
|
|
17115
|
+
}
|
|
17116
|
+
/**
|
|
17117
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
17118
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
17119
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
17120
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
17121
|
+
*/
|
|
17122
|
+
var UNIT_TABLE = {
|
|
17123
|
+
"°C": {
|
|
17124
|
+
dimension: "temperature",
|
|
17125
|
+
factor: 1,
|
|
17126
|
+
offset: 0
|
|
17127
|
+
},
|
|
17128
|
+
"°F": {
|
|
17129
|
+
dimension: "temperature",
|
|
17130
|
+
factor: 5 / 9,
|
|
17131
|
+
offset: -160 / 9
|
|
17132
|
+
},
|
|
17133
|
+
K: {
|
|
17134
|
+
dimension: "temperature",
|
|
17135
|
+
factor: 1,
|
|
17136
|
+
offset: -273.15
|
|
17137
|
+
},
|
|
17138
|
+
hPa: {
|
|
17139
|
+
dimension: "pressure",
|
|
17140
|
+
factor: 1,
|
|
17141
|
+
offset: 0
|
|
17142
|
+
},
|
|
17143
|
+
kPa: {
|
|
17144
|
+
dimension: "pressure",
|
|
17145
|
+
factor: 10,
|
|
17146
|
+
offset: 0
|
|
17147
|
+
},
|
|
17148
|
+
Pa: {
|
|
17149
|
+
dimension: "pressure",
|
|
17150
|
+
factor: .01,
|
|
17151
|
+
offset: 0
|
|
17152
|
+
},
|
|
17153
|
+
mbar: {
|
|
17154
|
+
dimension: "pressure",
|
|
17155
|
+
factor: 1,
|
|
17156
|
+
offset: 0
|
|
17157
|
+
},
|
|
17158
|
+
bar: {
|
|
17159
|
+
dimension: "pressure",
|
|
17160
|
+
factor: 1e3,
|
|
17161
|
+
offset: 0
|
|
17162
|
+
},
|
|
17163
|
+
inHg: {
|
|
17164
|
+
dimension: "pressure",
|
|
17165
|
+
factor: 33.8639,
|
|
17166
|
+
offset: 0
|
|
17167
|
+
},
|
|
17168
|
+
mmHg: {
|
|
17169
|
+
dimension: "pressure",
|
|
17170
|
+
factor: 1.33322,
|
|
17171
|
+
offset: 0
|
|
17172
|
+
},
|
|
17173
|
+
psi: {
|
|
17174
|
+
dimension: "pressure",
|
|
17175
|
+
factor: 68.9476,
|
|
17176
|
+
offset: 0
|
|
17177
|
+
},
|
|
17178
|
+
"m/s": {
|
|
17179
|
+
dimension: "speed",
|
|
17180
|
+
factor: 1,
|
|
17181
|
+
offset: 0
|
|
17182
|
+
},
|
|
17183
|
+
"km/h": {
|
|
17184
|
+
dimension: "speed",
|
|
17185
|
+
factor: 1 / 3.6,
|
|
17186
|
+
offset: 0
|
|
17187
|
+
},
|
|
17188
|
+
mph: {
|
|
17189
|
+
dimension: "speed",
|
|
17190
|
+
factor: .44704,
|
|
17191
|
+
offset: 0
|
|
17192
|
+
},
|
|
17193
|
+
kn: {
|
|
17194
|
+
dimension: "speed",
|
|
17195
|
+
factor: .514444,
|
|
17196
|
+
offset: 0
|
|
17197
|
+
},
|
|
17198
|
+
"mm/h": {
|
|
17199
|
+
dimension: "precipitation-rate",
|
|
17200
|
+
factor: 1,
|
|
17201
|
+
offset: 0
|
|
17202
|
+
},
|
|
17203
|
+
"in/h": {
|
|
17204
|
+
dimension: "precipitation-rate",
|
|
17205
|
+
factor: 25.4,
|
|
17206
|
+
offset: 0
|
|
17207
|
+
},
|
|
17208
|
+
m: {
|
|
17209
|
+
dimension: "length",
|
|
17210
|
+
factor: 1,
|
|
17211
|
+
offset: 0
|
|
17212
|
+
},
|
|
17213
|
+
mm: {
|
|
17214
|
+
dimension: "length",
|
|
17215
|
+
factor: .001,
|
|
17216
|
+
offset: 0
|
|
17217
|
+
},
|
|
17218
|
+
cm: {
|
|
17219
|
+
dimension: "length",
|
|
17220
|
+
factor: .01,
|
|
17221
|
+
offset: 0
|
|
17222
|
+
},
|
|
17223
|
+
km: {
|
|
17224
|
+
dimension: "length",
|
|
17225
|
+
factor: 1e3,
|
|
17226
|
+
offset: 0
|
|
17227
|
+
},
|
|
17228
|
+
in: {
|
|
17229
|
+
dimension: "length",
|
|
17230
|
+
factor: .0254,
|
|
17231
|
+
offset: 0
|
|
17232
|
+
},
|
|
17233
|
+
ft: {
|
|
17234
|
+
dimension: "length",
|
|
17235
|
+
factor: .3048,
|
|
17236
|
+
offset: 0
|
|
17237
|
+
},
|
|
17238
|
+
mi: {
|
|
17239
|
+
dimension: "length",
|
|
17240
|
+
factor: 1609.344,
|
|
17241
|
+
offset: 0
|
|
17242
|
+
},
|
|
17243
|
+
"m²": {
|
|
17244
|
+
dimension: "area",
|
|
17245
|
+
factor: 1,
|
|
17246
|
+
offset: 0
|
|
17247
|
+
},
|
|
17248
|
+
lx: {
|
|
17249
|
+
dimension: "illuminance",
|
|
17250
|
+
factor: 1,
|
|
17251
|
+
offset: 0
|
|
17252
|
+
},
|
|
17253
|
+
"W/m²": {
|
|
17254
|
+
dimension: "irradiance",
|
|
17255
|
+
factor: 1,
|
|
17256
|
+
offset: 0
|
|
17257
|
+
},
|
|
17258
|
+
W: {
|
|
17259
|
+
dimension: "power",
|
|
17260
|
+
factor: 1,
|
|
17261
|
+
offset: 0
|
|
17262
|
+
},
|
|
17263
|
+
kW: {
|
|
17264
|
+
dimension: "power",
|
|
17265
|
+
factor: 1e3,
|
|
17266
|
+
offset: 0
|
|
17267
|
+
},
|
|
17268
|
+
VA: {
|
|
17269
|
+
dimension: "apparent-power",
|
|
17270
|
+
factor: 1,
|
|
17271
|
+
offset: 0
|
|
17272
|
+
},
|
|
17273
|
+
Wh: {
|
|
17274
|
+
dimension: "energy",
|
|
17275
|
+
factor: 1,
|
|
17276
|
+
offset: 0
|
|
17277
|
+
},
|
|
17278
|
+
kWh: {
|
|
17279
|
+
dimension: "energy",
|
|
17280
|
+
factor: 1e3,
|
|
17281
|
+
offset: 0
|
|
17282
|
+
},
|
|
17283
|
+
MWh: {
|
|
17284
|
+
dimension: "energy",
|
|
17285
|
+
factor: 1e6,
|
|
17286
|
+
offset: 0
|
|
17287
|
+
},
|
|
17288
|
+
V: {
|
|
17289
|
+
dimension: "voltage",
|
|
17290
|
+
factor: 1,
|
|
17291
|
+
offset: 0
|
|
17292
|
+
},
|
|
17293
|
+
mV: {
|
|
17294
|
+
dimension: "voltage",
|
|
17295
|
+
factor: .001,
|
|
17296
|
+
offset: 0
|
|
17297
|
+
},
|
|
17298
|
+
A: {
|
|
17299
|
+
dimension: "current",
|
|
17300
|
+
factor: 1,
|
|
17301
|
+
offset: 0
|
|
17302
|
+
},
|
|
17303
|
+
mA: {
|
|
17304
|
+
dimension: "current",
|
|
17305
|
+
factor: .001,
|
|
17306
|
+
offset: 0
|
|
17307
|
+
},
|
|
17308
|
+
ppm: {
|
|
17309
|
+
dimension: "concentration-volume",
|
|
17310
|
+
factor: 1,
|
|
17311
|
+
offset: 0
|
|
17312
|
+
},
|
|
17313
|
+
ppb: {
|
|
17314
|
+
dimension: "concentration-volume",
|
|
17315
|
+
factor: .001,
|
|
17316
|
+
offset: 0
|
|
17317
|
+
},
|
|
17318
|
+
"µg/m³": {
|
|
17319
|
+
dimension: "concentration-mass",
|
|
17320
|
+
factor: 1,
|
|
17321
|
+
offset: 0
|
|
17322
|
+
},
|
|
17323
|
+
"mg/m³": {
|
|
17324
|
+
dimension: "concentration-mass",
|
|
17325
|
+
factor: 1e3,
|
|
17326
|
+
offset: 0
|
|
17327
|
+
},
|
|
17328
|
+
kg: {
|
|
17329
|
+
dimension: "mass",
|
|
17330
|
+
factor: 1,
|
|
17331
|
+
offset: 0
|
|
17332
|
+
},
|
|
17333
|
+
g: {
|
|
17334
|
+
dimension: "mass",
|
|
17335
|
+
factor: .001,
|
|
17336
|
+
offset: 0
|
|
17337
|
+
},
|
|
17338
|
+
lb: {
|
|
17339
|
+
dimension: "mass",
|
|
17340
|
+
factor: .453592,
|
|
17341
|
+
offset: 0
|
|
17342
|
+
},
|
|
17343
|
+
oz: {
|
|
17344
|
+
dimension: "mass",
|
|
17345
|
+
factor: .0283495,
|
|
17346
|
+
offset: 0
|
|
17347
|
+
},
|
|
17348
|
+
"%": {
|
|
17349
|
+
dimension: "percentage",
|
|
17350
|
+
factor: 1,
|
|
17351
|
+
offset: 0
|
|
17352
|
+
},
|
|
17353
|
+
L: {
|
|
17354
|
+
dimension: "volume",
|
|
17355
|
+
factor: 1,
|
|
17356
|
+
offset: 0
|
|
17357
|
+
},
|
|
17358
|
+
mL: {
|
|
17359
|
+
dimension: "volume",
|
|
17360
|
+
factor: .001,
|
|
17361
|
+
offset: 0
|
|
17362
|
+
},
|
|
17363
|
+
gal: {
|
|
17364
|
+
dimension: "volume",
|
|
17365
|
+
factor: 3.785411784,
|
|
17366
|
+
offset: 0
|
|
17367
|
+
},
|
|
17368
|
+
s: {
|
|
17369
|
+
dimension: "duration",
|
|
17370
|
+
factor: 1,
|
|
17371
|
+
offset: 0
|
|
17372
|
+
},
|
|
17373
|
+
ms: {
|
|
17374
|
+
dimension: "duration",
|
|
17375
|
+
factor: .001,
|
|
17376
|
+
offset: 0
|
|
17377
|
+
},
|
|
17378
|
+
min: {
|
|
17379
|
+
dimension: "duration",
|
|
17380
|
+
factor: 60,
|
|
17381
|
+
offset: 0
|
|
17382
|
+
},
|
|
17383
|
+
h: {
|
|
17384
|
+
dimension: "duration",
|
|
17385
|
+
factor: 3600,
|
|
17386
|
+
offset: 0
|
|
17387
|
+
},
|
|
17388
|
+
d: {
|
|
17389
|
+
dimension: "duration",
|
|
17390
|
+
factor: 86400,
|
|
17391
|
+
offset: 0
|
|
17392
|
+
},
|
|
17393
|
+
B: {
|
|
17394
|
+
dimension: "information",
|
|
17395
|
+
factor: 1,
|
|
17396
|
+
offset: 0
|
|
17397
|
+
},
|
|
17398
|
+
KiB: {
|
|
17399
|
+
dimension: "information",
|
|
17400
|
+
factor: 1024,
|
|
17401
|
+
offset: 0
|
|
17402
|
+
},
|
|
17403
|
+
MiB: {
|
|
17404
|
+
dimension: "information",
|
|
17405
|
+
factor: 1024 ** 2,
|
|
17406
|
+
offset: 0
|
|
17407
|
+
},
|
|
17408
|
+
GiB: {
|
|
17409
|
+
dimension: "information",
|
|
17410
|
+
factor: 1024 ** 3,
|
|
17411
|
+
offset: 0
|
|
17412
|
+
},
|
|
17413
|
+
TiB: {
|
|
17414
|
+
dimension: "information",
|
|
17415
|
+
factor: 1024 ** 4,
|
|
17416
|
+
offset: 0
|
|
17417
|
+
},
|
|
17418
|
+
kB: {
|
|
17419
|
+
dimension: "information",
|
|
17420
|
+
factor: 1e3,
|
|
17421
|
+
offset: 0
|
|
17422
|
+
},
|
|
17423
|
+
MB: {
|
|
17424
|
+
dimension: "information",
|
|
17425
|
+
factor: 1e6,
|
|
17426
|
+
offset: 0
|
|
17427
|
+
},
|
|
17428
|
+
GB: {
|
|
17429
|
+
dimension: "information",
|
|
17430
|
+
factor: 1e9,
|
|
17431
|
+
offset: 0
|
|
17432
|
+
},
|
|
17433
|
+
"°": {
|
|
17434
|
+
dimension: "angle",
|
|
17435
|
+
factor: 1,
|
|
17436
|
+
offset: 0
|
|
17437
|
+
},
|
|
17438
|
+
dBFS: {
|
|
17439
|
+
dimension: "sound-level",
|
|
17440
|
+
factor: 1,
|
|
17441
|
+
offset: 0
|
|
17442
|
+
},
|
|
17443
|
+
dBm: {
|
|
17444
|
+
dimension: "signal-level",
|
|
17445
|
+
factor: 1,
|
|
17446
|
+
offset: 0
|
|
17447
|
+
},
|
|
17448
|
+
mired: {
|
|
17449
|
+
dimension: "colour-temperature",
|
|
17450
|
+
factor: 1,
|
|
17451
|
+
offset: 0
|
|
17452
|
+
},
|
|
17453
|
+
kbps: {
|
|
17454
|
+
dimension: "bitrate",
|
|
17455
|
+
factor: 1,
|
|
17456
|
+
offset: 0
|
|
17457
|
+
},
|
|
17458
|
+
Mbps: {
|
|
17459
|
+
dimension: "bitrate",
|
|
17460
|
+
factor: 1e3,
|
|
17461
|
+
offset: 0
|
|
17462
|
+
},
|
|
17463
|
+
fps: {
|
|
17464
|
+
dimension: "rate",
|
|
17465
|
+
factor: 1,
|
|
17466
|
+
offset: 0
|
|
17467
|
+
}
|
|
17468
|
+
};
|
|
17469
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
17470
|
+
function specFor(unit) {
|
|
17471
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
17472
|
+
}
|
|
17473
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
17474
|
+
function toCanonical(value, spec) {
|
|
17475
|
+
return value * spec.factor + spec.offset;
|
|
17476
|
+
}
|
|
17477
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
17478
|
+
function fromCanonical(canonical, spec) {
|
|
17479
|
+
return (canonical - spec.offset) / spec.factor;
|
|
17480
|
+
}
|
|
17481
|
+
/**
|
|
17482
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
17483
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
17484
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
17485
|
+
*/
|
|
17486
|
+
function tryConvertUnit(value, from, to) {
|
|
17487
|
+
if (from === to) return value;
|
|
17488
|
+
const fromSpec = specFor(from);
|
|
17489
|
+
if (!fromSpec) return void 0;
|
|
17490
|
+
const toSpec = specFor(to);
|
|
17491
|
+
if (!toSpec) return void 0;
|
|
17492
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
17493
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
17494
|
+
}
|
|
17495
|
+
/**
|
|
17496
|
+
* A number a person types for a physical quantity — the input side of the
|
|
17497
|
+
* value + unit contract (D972).
|
|
17498
|
+
*
|
|
17499
|
+
* Readings are stored in the base unit of their quantity (D944); a cap method
|
|
17500
|
+
* param is stored in the unit its input schema DECLARES (`paramMeta`). The
|
|
17501
|
+
* wire pair {@link QuantityValue} says "this number is expressed in this
|
|
17502
|
+
* unit"; {@link convertQuantityInput} brings it to the stored unit and refuses,
|
|
17503
|
+
* with a typed {@link QuantityUnitError}, a unit that cannot be that quantity.
|
|
17504
|
+
*/
|
|
17505
|
+
/** A unit that cannot be applied to the number it was given with. */
|
|
17506
|
+
var QuantityUnitError = class extends Error {
|
|
17507
|
+
code;
|
|
17508
|
+
constructor(code, message) {
|
|
17509
|
+
super(`[${code}] ${message}`);
|
|
17510
|
+
this.name = "QuantityUnitError";
|
|
17511
|
+
this.code = code;
|
|
17512
|
+
}
|
|
17513
|
+
};
|
|
17514
|
+
/** The wire pair: `value` is expressed in `unit`. */
|
|
17515
|
+
var QuantityValueSchema = object({
|
|
17516
|
+
value: number(),
|
|
17517
|
+
unit: string().min(1)
|
|
17518
|
+
});
|
|
17519
|
+
/**
|
|
17520
|
+
* Zod `.meta()` bag for a cap METHOD param that is a physical quantity — the
|
|
17521
|
+
* input-side twin of `readingMeta`. The value is carried in `unit` (a gram
|
|
17522
|
+
* portion stays in grams); the unit must belong to the quantity, checked once
|
|
17523
|
+
* when the cap is declared.
|
|
17524
|
+
*/
|
|
17525
|
+
function paramMeta(declared) {
|
|
17526
|
+
const base = baseUnitOf(declared.quantity);
|
|
17527
|
+
if (base === void 0) throw new QuantityUnitError("unknown-unit", `quantity "${declared.quantity}" has no unit`);
|
|
17528
|
+
if (tryConvertUnit(1, declared.unit, base) === void 0) throw new QuantityUnitError("cross-dimension", `"${declared.unit}" is not a unit of ${declared.quantity}`);
|
|
17529
|
+
return {
|
|
17530
|
+
quantity: declared.quantity,
|
|
17531
|
+
unit: declared.unit
|
|
17532
|
+
};
|
|
17533
|
+
}
|
|
17534
|
+
object({
|
|
17535
|
+
quantity: ReadingQuantitySchema,
|
|
17536
|
+
unit: string().min(1)
|
|
17537
|
+
});
|
|
16860
17538
|
/** How a leaf compares a device field to a value. Derived from the field's
|
|
16861
17539
|
* `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
16862
17540
|
var AutomationConditionOperatorSchema = _enum([
|
|
@@ -16879,8 +17557,10 @@ var AutomationConditionLeafSchema = object({
|
|
|
16879
17557
|
string(),
|
|
16880
17558
|
number(),
|
|
16881
17559
|
boolean(),
|
|
16882
|
-
array(union([string(), number()]))
|
|
16883
|
-
|
|
17560
|
+
array(union([string(), number()])),
|
|
17561
|
+
QuantityValueSchema
|
|
17562
|
+
]),
|
|
17563
|
+
unit: string().min(1).optional()
|
|
16884
17564
|
});
|
|
16885
17565
|
var AutomationConditionStationaryPresentSchema = object({
|
|
16886
17566
|
kind: literal("stationary-present"),
|
|
@@ -17033,7 +17713,13 @@ var AutomationCapStepSchema = object({
|
|
|
17033
17713
|
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
17034
17714
|
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
17035
17715
|
* the expression leaf. */
|
|
17036
|
-
args: record(string(), unknown()).optional()
|
|
17716
|
+
args: record(string(), unknown()).optional(),
|
|
17717
|
+
/**
|
|
17718
|
+
* The unit the person picked per arg (display only). `args` always hold the
|
|
17719
|
+
* value in the unit the method's input DECLARES (`paramMeta`); on a write an
|
|
17720
|
+
* arg may be a {@link QuantityValue}, which the save boundary converts.
|
|
17721
|
+
*/
|
|
17722
|
+
argUnits: record(string(), string().min(1)).optional()
|
|
17037
17723
|
});
|
|
17038
17724
|
/**
|
|
17039
17725
|
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
@@ -17435,6 +18121,437 @@ object({
|
|
|
17435
18121
|
lastRunAt: number().nullable(),
|
|
17436
18122
|
lastError: string().nullable()
|
|
17437
18123
|
});
|
|
18124
|
+
/** A bare number accepted as a base unit — the save succeeded, say so. */
|
|
18125
|
+
var UnitWarningSchema = object({
|
|
18126
|
+
/** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
|
|
18127
|
+
path: string(),
|
|
18128
|
+
deviceId: number().int().optional(),
|
|
18129
|
+
field: string(),
|
|
18130
|
+
value: number(),
|
|
18131
|
+
assumedUnit: string(),
|
|
18132
|
+
message: string()
|
|
18133
|
+
});
|
|
18134
|
+
/**
|
|
18135
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
18136
|
+
*
|
|
18137
|
+
* Apprise-derived model (see
|
|
18138
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
18139
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
18140
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
18141
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
18142
|
+
* message to what the kind supports — callers never special-case a service.
|
|
18143
|
+
*
|
|
18144
|
+
* DESIGN DECISIONS (locked):
|
|
18145
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
18146
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
18147
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
18148
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
18149
|
+
* alternative would fork the UI per addon and cannot host the
|
|
18150
|
+
* discovery→adopt flow.
|
|
18151
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
18152
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
18153
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
18154
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
18155
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
18156
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
18157
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
18158
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
18159
|
+
* base64 fallback needed.
|
|
18160
|
+
*
|
|
18161
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
18162
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
18163
|
+
* admin "Integrations" page.
|
|
18164
|
+
*/
|
|
18165
|
+
/**
|
|
18166
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
18167
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
18168
|
+
*/
|
|
18169
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
18170
|
+
"image",
|
|
18171
|
+
"video",
|
|
18172
|
+
"gif",
|
|
18173
|
+
"audio",
|
|
18174
|
+
"icon"
|
|
18175
|
+
]);
|
|
18176
|
+
/**
|
|
18177
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
18178
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
18179
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
18180
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
18181
|
+
*/
|
|
18182
|
+
var AttachmentSchema = object({
|
|
18183
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
18184
|
+
url: string().optional(),
|
|
18185
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
18186
|
+
mime: string().optional(),
|
|
18187
|
+
name: string().optional()
|
|
18188
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
18189
|
+
var NotificationFormatSchema = _enum([
|
|
18190
|
+
"text",
|
|
18191
|
+
"markdown",
|
|
18192
|
+
"html"
|
|
18193
|
+
]);
|
|
18194
|
+
/**
|
|
18195
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
18196
|
+
*
|
|
18197
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
18198
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
18199
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
18200
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
18201
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
18202
|
+
*
|
|
18203
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
18204
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
18205
|
+
* differently.
|
|
18206
|
+
*
|
|
18207
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
18208
|
+
*
|
|
18209
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
18210
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
18211
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
18212
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
18213
|
+
* decides its glyph, which is the only place that decision can be made
|
|
18214
|
+
* honestly.
|
|
18215
|
+
*
|
|
18216
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
18217
|
+
* `disarm` straight through; iOS feeds that string to
|
|
18218
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
18219
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
18220
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
18221
|
+
*
|
|
18222
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
18223
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
18224
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
18225
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
18226
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
18227
|
+
*/
|
|
18228
|
+
var NotificationActionIconSchema = _enum([
|
|
18229
|
+
"acknowledge",
|
|
18230
|
+
"dismiss",
|
|
18231
|
+
"silence",
|
|
18232
|
+
"snooze",
|
|
18233
|
+
"view",
|
|
18234
|
+
"play",
|
|
18235
|
+
"open",
|
|
18236
|
+
"close",
|
|
18237
|
+
"lock",
|
|
18238
|
+
"unlock",
|
|
18239
|
+
"arm",
|
|
18240
|
+
"arm-home",
|
|
18241
|
+
"arm-away",
|
|
18242
|
+
"arm-night",
|
|
18243
|
+
"disarm",
|
|
18244
|
+
"light",
|
|
18245
|
+
"alert",
|
|
18246
|
+
"camera"
|
|
18247
|
+
]);
|
|
18248
|
+
/** A single tap-through action button. */
|
|
18249
|
+
var NotificationActionSchema = object({
|
|
18250
|
+
id: string(),
|
|
18251
|
+
label: string(),
|
|
18252
|
+
url: string().optional(),
|
|
18253
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
18254
|
+
icon: NotificationActionIconSchema.optional(),
|
|
18255
|
+
/**
|
|
18256
|
+
* Renders in a warning style where the notifier supports it.
|
|
18257
|
+
*
|
|
18258
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
18259
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
18260
|
+
* does not buy.
|
|
18261
|
+
*/
|
|
18262
|
+
destructive: boolean().optional(),
|
|
18263
|
+
/**
|
|
18264
|
+
* How the tap should REACH the url.
|
|
18265
|
+
*
|
|
18266
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
18267
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
18268
|
+
* That is right for a button whose answer the operator wants to read.
|
|
18269
|
+
*
|
|
18270
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
18271
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
18272
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
18273
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
18274
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
18275
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
18276
|
+
* a requirement.
|
|
18277
|
+
*/
|
|
18278
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
18279
|
+
});
|
|
18280
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
18281
|
+
var DeliveryParamValueSchema = union([
|
|
18282
|
+
string(),
|
|
18283
|
+
number(),
|
|
18284
|
+
boolean()
|
|
18285
|
+
]);
|
|
18286
|
+
/**
|
|
18287
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
18288
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
18289
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
18290
|
+
* no editor knows a service by name.
|
|
18291
|
+
*
|
|
18292
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
18293
|
+
* run on the addon cap path).
|
|
18294
|
+
*/
|
|
18295
|
+
var DeliveryParamSchema = object({
|
|
18296
|
+
key: string().min(1),
|
|
18297
|
+
label: string(),
|
|
18298
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
18299
|
+
labelKey: string().optional(),
|
|
18300
|
+
type: _enum([
|
|
18301
|
+
"enum",
|
|
18302
|
+
"number",
|
|
18303
|
+
"boolean",
|
|
18304
|
+
"string"
|
|
18305
|
+
]),
|
|
18306
|
+
/** `enum` only: the closed set of accepted values. */
|
|
18307
|
+
options: array(object({
|
|
18308
|
+
value: string(),
|
|
18309
|
+
label: string()
|
|
18310
|
+
})).optional(),
|
|
18311
|
+
/** `number` only. */
|
|
18312
|
+
min: number().optional(),
|
|
18313
|
+
max: number().optional(),
|
|
18314
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
18315
|
+
default: DeliveryParamValueSchema.optional()
|
|
18316
|
+
});
|
|
18317
|
+
/**
|
|
18318
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
18319
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
18320
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
18321
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
18322
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
18323
|
+
* `priority` for that one target.
|
|
18324
|
+
*/
|
|
18325
|
+
var NotificationSchema = object({
|
|
18326
|
+
body: string(),
|
|
18327
|
+
title: string().optional(),
|
|
18328
|
+
format: NotificationFormatSchema.default("text"),
|
|
18329
|
+
priority: number().int().min(1).max(5).default(3),
|
|
18330
|
+
level: string().optional(),
|
|
18331
|
+
attachments: array(AttachmentSchema).optional(),
|
|
18332
|
+
clickUrl: string().optional(),
|
|
18333
|
+
actions: array(NotificationActionSchema).optional(),
|
|
18334
|
+
sound: string().optional(),
|
|
18335
|
+
ttl: number().optional(),
|
|
18336
|
+
tag: string().optional(),
|
|
18337
|
+
deviceId: number().optional(),
|
|
18338
|
+
eventId: string().optional(),
|
|
18339
|
+
metadata: record(string(), unknown()).optional(),
|
|
18340
|
+
/**
|
|
18341
|
+
* Values for the parameters the target's kind DECLARES in
|
|
18342
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
18343
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
18344
|
+
*/
|
|
18345
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
18346
|
+
});
|
|
18347
|
+
/** One declared native severity/priority level for a kind. */
|
|
18348
|
+
var TargetKindLevelSchema = object({
|
|
18349
|
+
id: string(),
|
|
18350
|
+
label: string(),
|
|
18351
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
18352
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
18353
|
+
flags: object({
|
|
18354
|
+
critical: boolean().optional(),
|
|
18355
|
+
silent: boolean().optional(),
|
|
18356
|
+
noPush: boolean().optional()
|
|
18357
|
+
}).optional(),
|
|
18358
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
18359
|
+
requires: array(string()).optional(),
|
|
18360
|
+
description: string().optional()
|
|
18361
|
+
});
|
|
18362
|
+
/** The full capability block consulted before dispatch. */
|
|
18363
|
+
var TargetKindCapsSchema = object({
|
|
18364
|
+
attachments: object({
|
|
18365
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
18366
|
+
mode: _enum([
|
|
18367
|
+
"url",
|
|
18368
|
+
"bytes",
|
|
18369
|
+
"both"
|
|
18370
|
+
]),
|
|
18371
|
+
max: number().int().nonnegative(),
|
|
18372
|
+
maxBytes: number().int().positive().optional()
|
|
18373
|
+
}),
|
|
18374
|
+
/** Max action buttons (0 = none). */
|
|
18375
|
+
actions: number().int().nonnegative(),
|
|
18376
|
+
/**
|
|
18377
|
+
* Whether this kind renders a per-action ICON.
|
|
18378
|
+
*
|
|
18379
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
18380
|
+
* run on the addon cap path — three production failures in one day taught
|
|
18381
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
18382
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
18383
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
18384
|
+
*/
|
|
18385
|
+
actionIcons: boolean().optional(),
|
|
18386
|
+
levels: array(TargetKindLevelSchema),
|
|
18387
|
+
format: array(NotificationFormatSchema),
|
|
18388
|
+
clickUrl: boolean(),
|
|
18389
|
+
sound: boolean(),
|
|
18390
|
+
ttl: boolean(),
|
|
18391
|
+
bodyMaxLen: number().int().positive()
|
|
18392
|
+
});
|
|
18393
|
+
/**
|
|
18394
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
18395
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
18396
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
18397
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
18398
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
18399
|
+
*/
|
|
18400
|
+
var ConfigSchemaPassthrough$3 = unknown();
|
|
18401
|
+
var TargetKindSchema = object({
|
|
18402
|
+
kind: string(),
|
|
18403
|
+
label: string(),
|
|
18404
|
+
icon: string(),
|
|
18405
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
18406
|
+
addonId: string(),
|
|
18407
|
+
/**
|
|
18408
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
18409
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
18410
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
18411
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
18412
|
+
*
|
|
18413
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
18414
|
+
* client, and a native client joins it onto its own hub base.
|
|
18415
|
+
*
|
|
18416
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
18417
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
18418
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
18419
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
18420
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
18421
|
+
*/
|
|
18422
|
+
iconUrl: string().optional(),
|
|
18423
|
+
/**
|
|
18424
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
18425
|
+
*
|
|
18426
|
+
* The server knows this and therefore says it, because the client cannot
|
|
18427
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
18428
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
18429
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
18430
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
18431
|
+
*
|
|
18432
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
18433
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
18434
|
+
* its raster path, which is the safe default for an unknown image.
|
|
18435
|
+
*/
|
|
18436
|
+
iconMediaType: string().optional(),
|
|
18437
|
+
configSchema: ConfigSchemaPassthrough$3,
|
|
18438
|
+
/**
|
|
18439
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
18440
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
18441
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
18442
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
18443
|
+
* on every edit.
|
|
18444
|
+
*
|
|
18445
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
18446
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
18447
|
+
* it absent must assume ANY edit may need the secret.
|
|
18448
|
+
*/
|
|
18449
|
+
destinationKeys: array(string()).optional(),
|
|
18450
|
+
supportsDiscovery: boolean(),
|
|
18451
|
+
/**
|
|
18452
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
18453
|
+
* Absent = none; the rule editor then offers nothing.
|
|
18454
|
+
*/
|
|
18455
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
18456
|
+
caps: TargetKindCapsSchema
|
|
18457
|
+
});
|
|
18458
|
+
/**
|
|
18459
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
18460
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
18461
|
+
* round-trip a stored secret to the UI.
|
|
18462
|
+
*/
|
|
18463
|
+
var TargetSchema = object({
|
|
18464
|
+
id: string(),
|
|
18465
|
+
name: string(),
|
|
18466
|
+
kind: string(),
|
|
18467
|
+
addonId: string(),
|
|
18468
|
+
enabled: boolean(),
|
|
18469
|
+
config: record(string(), unknown())
|
|
18470
|
+
});
|
|
18471
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
18472
|
+
var DiscoveredTargetSchema = object({
|
|
18473
|
+
kind: string(),
|
|
18474
|
+
suggestedName: string(),
|
|
18475
|
+
config: record(string(), unknown())
|
|
18476
|
+
});
|
|
18477
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
18478
|
+
var RenderedAsSchema = object({
|
|
18479
|
+
level: string(),
|
|
18480
|
+
format: NotificationFormatSchema,
|
|
18481
|
+
attachmentsSent: number().int().nonnegative(),
|
|
18482
|
+
actionsSent: number().int().nonnegative(),
|
|
18483
|
+
truncated: boolean(),
|
|
18484
|
+
dropped: array(string())
|
|
18485
|
+
});
|
|
18486
|
+
var SendResultSchema = object({
|
|
18487
|
+
success: boolean(),
|
|
18488
|
+
error: string().optional(),
|
|
18489
|
+
renderedAs: RenderedAsSchema.optional()
|
|
18490
|
+
});
|
|
18491
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
18492
|
+
var TestResultSchema = SendResultSchema;
|
|
18493
|
+
/**
|
|
18494
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
18495
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
18496
|
+
*/
|
|
18497
|
+
var ProbeTargetConfigResultSchema = object({
|
|
18498
|
+
ok: boolean(),
|
|
18499
|
+
resolvedUrl: string(),
|
|
18500
|
+
httpStatus: number().int().optional(),
|
|
18501
|
+
stateInstance: string().optional(),
|
|
18502
|
+
error: string().optional()
|
|
18503
|
+
});
|
|
18504
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
18505
|
+
kind: string(),
|
|
18506
|
+
config: record(string(), unknown()).optional(),
|
|
18507
|
+
/**
|
|
18508
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
18509
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
18510
|
+
* secrets server-side before dialling (same merge as upsert).
|
|
18511
|
+
*/
|
|
18512
|
+
targetId: string().optional()
|
|
18513
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
18514
|
+
kind: string(),
|
|
18515
|
+
config: record(string(), unknown()),
|
|
18516
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
18517
|
+
targetId: string().optional()
|
|
18518
|
+
}), ProbeTargetConfigResultSchema, {
|
|
18519
|
+
kind: "mutation",
|
|
18520
|
+
auth: "admin"
|
|
18521
|
+
}), method(object({
|
|
18522
|
+
targetId: string(),
|
|
18523
|
+
notification: NotificationSchema
|
|
18524
|
+
}), SendResultSchema, {
|
|
18525
|
+
kind: "mutation",
|
|
18526
|
+
auth: "admin"
|
|
18527
|
+
}), method(object({
|
|
18528
|
+
targetId: string(),
|
|
18529
|
+
sample: NotificationSchema.optional()
|
|
18530
|
+
}), TestResultSchema, {
|
|
18531
|
+
kind: "mutation",
|
|
18532
|
+
auth: "admin"
|
|
18533
|
+
}), method(object({
|
|
18534
|
+
target: TargetSchema,
|
|
18535
|
+
/**
|
|
18536
|
+
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
18537
|
+
* existing row. The UI never holds the stored token; blank / redacted
|
|
18538
|
+
* password fields are restored from this source (D634 still applies if
|
|
18539
|
+
* destinations diverge without a re-typed secret).
|
|
18540
|
+
*/
|
|
18541
|
+
cloneSecretsFrom: string().optional()
|
|
18542
|
+
}), TargetSchema, {
|
|
18543
|
+
kind: "mutation",
|
|
18544
|
+
auth: "admin"
|
|
18545
|
+
}), method(object({ targetId: string() }), _void(), {
|
|
18546
|
+
kind: "mutation",
|
|
18547
|
+
auth: "admin"
|
|
18548
|
+
}), method(object({
|
|
18549
|
+
targetId: string(),
|
|
18550
|
+
enabled: boolean()
|
|
18551
|
+
}), _void(), {
|
|
18552
|
+
kind: "mutation",
|
|
18553
|
+
auth: "admin"
|
|
18554
|
+
});
|
|
17438
18555
|
/**
|
|
17439
18556
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
17440
18557
|
* surface that admin-ui consumes through `useAddonPagesListPages()`.
|
|
@@ -17467,52 +18584,6 @@ var AddonPageInfoSchema = object({
|
|
|
17467
18584
|
bundleUrl: string()
|
|
17468
18585
|
});
|
|
17469
18586
|
method(_void(), array(AddonPageInfoSchema).readonly());
|
|
17470
|
-
/**
|
|
17471
|
-
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
17472
|
-
* declarations. Every addon that contributes a UI page registers a
|
|
17473
|
-
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
17474
|
-
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
17475
|
-
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
17476
|
-
* that admin-ui consumes.
|
|
17477
|
-
*
|
|
17478
|
-
* The split exists because the public listing has a different output
|
|
17479
|
-
* shape than the per-provider raw declarations, and we want both ends
|
|
17480
|
-
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
17481
|
-
*/
|
|
17482
|
-
var AddonPageDeclarationSchema = object({
|
|
17483
|
-
id: string(),
|
|
17484
|
-
label: string(),
|
|
17485
|
-
icon: string(),
|
|
17486
|
-
path: string(),
|
|
17487
|
-
/**
|
|
17488
|
-
* Module Federation remote name — must match the `name` field on the
|
|
17489
|
-
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
17490
|
-
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
17491
|
-
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
17492
|
-
* contain hyphens).
|
|
17493
|
-
*/
|
|
17494
|
-
remoteName: string(),
|
|
17495
|
-
/**
|
|
17496
|
-
* Bundle filename inside the addon's `dist/` dir served at
|
|
17497
|
-
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
17498
|
-
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
17499
|
-
* the static-file route can compute an mtime-based cache-buster URL
|
|
17500
|
-
* without a separate filesystem stat.
|
|
17501
|
-
*/
|
|
17502
|
-
bundle: string(),
|
|
17503
|
-
/**
|
|
17504
|
-
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
17505
|
-
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
17506
|
-
* Any OTHER string creates (or joins) a custom section rendered after
|
|
17507
|
-
* the built-in groups; its label comes from `sectionLabel` (first
|
|
17508
|
-
* declaration wins), falling back to the id. Absent → the legacy
|
|
17509
|
-
* "Addon Pages" group.
|
|
17510
|
-
*/
|
|
17511
|
-
section: string().optional(),
|
|
17512
|
-
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
17513
|
-
sectionLabel: string().optional()
|
|
17514
|
-
});
|
|
17515
|
-
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
17516
18587
|
var AddonHttpRouteSchema = object({
|
|
17517
18588
|
method: _enum([
|
|
17518
18589
|
"GET",
|
|
@@ -20184,7 +21255,11 @@ var CompositionProblemSchema = object({
|
|
|
20184
21255
|
"mixed-quantity",
|
|
20185
21256
|
"helper-mismatch",
|
|
20186
21257
|
"member-constraint",
|
|
20187
|
-
"retention"
|
|
21258
|
+
"retention",
|
|
21259
|
+
"command-method-unknown",
|
|
21260
|
+
"command-source-unknown",
|
|
21261
|
+
"command-input-unknown",
|
|
21262
|
+
"command-input-missing"
|
|
20188
21263
|
]),
|
|
20189
21264
|
path: string(),
|
|
20190
21265
|
message: string()
|
|
@@ -20223,7 +21298,9 @@ var CompositionFieldRoleSchema = _enum([
|
|
|
20223
21298
|
var CompositionFieldItemSchema = object({
|
|
20224
21299
|
arrayPath: string(),
|
|
20225
21300
|
key: string(),
|
|
20226
|
-
label: string()
|
|
21301
|
+
label: string(),
|
|
21302
|
+
/** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
|
|
21303
|
+
resettable: boolean().optional()
|
|
20227
21304
|
});
|
|
20228
21305
|
var CompositionFieldPlanSchema = object({
|
|
20229
21306
|
cap: string(),
|
|
@@ -20428,7 +21505,7 @@ Object.freeze({
|
|
|
20428
21505
|
* One catalog row per base reading a group helper can build (D944 §3).
|
|
20429
21506
|
* Data, not a form default written into the block.
|
|
20430
21507
|
*/
|
|
20431
|
-
var NOW$
|
|
21508
|
+
var NOW$3 = {
|
|
20432
21509
|
fieldPath: "lastFetchedAt",
|
|
20433
21510
|
expr: "now"
|
|
20434
21511
|
};
|
|
@@ -20480,7 +21557,7 @@ var GROUP_TEMPLATES = [
|
|
|
20480
21557
|
quantity: "temperature",
|
|
20481
21558
|
defaultAggregate: "mean",
|
|
20482
21559
|
empty: "null",
|
|
20483
|
-
companions: [NOW$
|
|
21560
|
+
companions: [NOW$3],
|
|
20484
21561
|
alsoFrom: {
|
|
20485
21562
|
deviceTypes: CLIMATE_DEVICES,
|
|
20486
21563
|
accept: {
|
|
@@ -20498,7 +21575,7 @@ var GROUP_TEMPLATES = [
|
|
|
20498
21575
|
quantity: "humidity",
|
|
20499
21576
|
defaultAggregate: "mean",
|
|
20500
21577
|
empty: "null",
|
|
20501
|
-
companions: [NOW$
|
|
21578
|
+
companions: [NOW$3],
|
|
20502
21579
|
alsoFrom: {
|
|
20503
21580
|
deviceTypes: CLIMATE_DEVICES,
|
|
20504
21581
|
accept: {
|
|
@@ -20516,7 +21593,7 @@ var GROUP_TEMPLATES = [
|
|
|
20516
21593
|
quantity: "pressure",
|
|
20517
21594
|
defaultAggregate: "mean",
|
|
20518
21595
|
empty: "null",
|
|
20519
|
-
companions: [NOW$
|
|
21596
|
+
companions: [NOW$3]
|
|
20520
21597
|
}),
|
|
20521
21598
|
numeric({
|
|
20522
21599
|
id: "illuminance",
|
|
@@ -20527,7 +21604,7 @@ var GROUP_TEMPLATES = [
|
|
|
20527
21604
|
quantity: "illuminance",
|
|
20528
21605
|
defaultAggregate: "mean",
|
|
20529
21606
|
empty: "null",
|
|
20530
|
-
companions: [NOW$
|
|
21607
|
+
companions: [NOW$3]
|
|
20531
21608
|
}),
|
|
20532
21609
|
numeric({
|
|
20533
21610
|
id: "power",
|
|
@@ -20538,7 +21615,7 @@ var GROUP_TEMPLATES = [
|
|
|
20538
21615
|
quantity: "power",
|
|
20539
21616
|
defaultAggregate: "sum",
|
|
20540
21617
|
empty: "omit",
|
|
20541
|
-
companions: [NOW$
|
|
21618
|
+
companions: [NOW$3]
|
|
20542
21619
|
}),
|
|
20543
21620
|
numeric({
|
|
20544
21621
|
id: "energy",
|
|
@@ -20549,7 +21626,7 @@ var GROUP_TEMPLATES = [
|
|
|
20549
21626
|
quantity: "energy",
|
|
20550
21627
|
defaultAggregate: "sum",
|
|
20551
21628
|
empty: "omit",
|
|
20552
|
-
companions: [NOW$
|
|
21629
|
+
companions: [NOW$3]
|
|
20553
21630
|
}),
|
|
20554
21631
|
numeric({
|
|
20555
21632
|
id: "battery",
|
|
@@ -20584,7 +21661,7 @@ var GROUP_TEMPLATES = [
|
|
|
20584
21661
|
quantity: "device",
|
|
20585
21662
|
defaultAggregate: "mean",
|
|
20586
21663
|
empty: "null",
|
|
20587
|
-
companions: [NOW$
|
|
21664
|
+
companions: [NOW$3]
|
|
20588
21665
|
}),
|
|
20589
21666
|
binary({
|
|
20590
21667
|
id: "motion",
|
|
@@ -22534,116 +23611,6 @@ var DeviceAdminLinkSchema = object({
|
|
|
22534
23611
|
});
|
|
22535
23612
|
method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
|
|
22536
23613
|
/**
|
|
22537
|
-
* Identity envelope for a device's upstream-system metadata.
|
|
22538
|
-
*
|
|
22539
|
-
* Two jobs:
|
|
22540
|
-
*
|
|
22541
|
-
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
22542
|
-
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
22543
|
-
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
22544
|
-
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
22545
|
-
* without round-tripping through `stableId` / `id` mappings.
|
|
22546
|
-
*
|
|
22547
|
-
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
22548
|
-
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
22549
|
-
* for vendors where MAC is the natural key). For systems where the
|
|
22550
|
-
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
22551
|
-
* providers detect renames by matching `uniqueId` and patch `id`
|
|
22552
|
-
* in-place so dispatch keeps working.
|
|
22553
|
-
*
|
|
22554
|
-
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
22555
|
-
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
22556
|
-
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
22557
|
-
* neither is repeated on each sample. The `device_class` tag is an
|
|
22558
|
-
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
22559
|
-
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
22560
|
-
*
|
|
22561
|
-
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
22562
|
-
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
22563
|
-
* details, …). Providers control what lands here.
|
|
22564
|
-
*/
|
|
22565
|
-
var SourceInfoSchema = object({
|
|
22566
|
-
/** Live dispatch key — mutable when the source system allows rename. */
|
|
22567
|
-
id: string(),
|
|
22568
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
22569
|
-
system: string(),
|
|
22570
|
-
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
22571
|
-
* Used to detect rename when `id` changes. */
|
|
22572
|
-
uniqueId: string().optional(),
|
|
22573
|
-
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
22574
|
-
quantity: ReadingQuantitySchema.optional(),
|
|
22575
|
-
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
22576
|
-
dateTimeShape: _enum([
|
|
22577
|
-
"date",
|
|
22578
|
-
"time",
|
|
22579
|
-
"datetime"
|
|
22580
|
-
]).optional(),
|
|
22581
|
-
/** Provider-specific extras that don't fit the structured slots.
|
|
22582
|
-
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
22583
|
-
* attribute blobs (would explode the meta JSON column). */
|
|
22584
|
-
raw: record(string(), unknown()).optional()
|
|
22585
|
-
});
|
|
22586
|
-
/**
|
|
22587
|
-
* Build the synthetic SourceInfo every existing provider falls back to
|
|
22588
|
-
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
22589
|
-
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
22590
|
-
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
22591
|
-
* id. Real upstream identity replaces this once a provider migrates and
|
|
22592
|
-
* calls `updateSourceInfo()` with concrete values.
|
|
22593
|
-
*/
|
|
22594
|
-
function synthesizeSourceInfo(input) {
|
|
22595
|
-
return {
|
|
22596
|
-
id: input.stableId,
|
|
22597
|
-
system: input.addonId
|
|
22598
|
-
};
|
|
22599
|
-
}
|
|
22600
|
-
/**
|
|
22601
|
-
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
22602
|
-
* patch values are ignored (no field clobber); to clear a field
|
|
22603
|
-
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
22604
|
-
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
22605
|
-
* bother with a `null`-means-delete sentinel.
|
|
22606
|
-
*
|
|
22607
|
-
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
22608
|
-
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
22609
|
-
*/
|
|
22610
|
-
function mergeSourceInfo(prev, patch) {
|
|
22611
|
-
const next = { ...prev };
|
|
22612
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
22613
|
-
if (v === void 0) continue;
|
|
22614
|
-
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
22615
|
-
next.raw = {
|
|
22616
|
-
...prev.raw,
|
|
22617
|
-
...v
|
|
22618
|
-
};
|
|
22619
|
-
continue;
|
|
22620
|
-
}
|
|
22621
|
-
next[k] = v;
|
|
22622
|
-
}
|
|
22623
|
-
return next;
|
|
22624
|
-
}
|
|
22625
|
-
/**
|
|
22626
|
-
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
22627
|
-
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
22628
|
-
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
22629
|
-
* lives under a single nested key to keep it composable with those.
|
|
22630
|
-
*/
|
|
22631
|
-
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
22632
|
-
/**
|
|
22633
|
-
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
22634
|
-
* with Zod. Returns `null` when the blob is missing or the nested
|
|
22635
|
-
* `sourceInfo` key fails validation — callers fall back to the
|
|
22636
|
-
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
22637
|
-
*/
|
|
22638
|
-
function extractSourceInfoFromMetadata(metadata) {
|
|
22639
|
-
if (!metadata) return null;
|
|
22640
|
-
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
22641
|
-
if (!raw) return null;
|
|
22642
|
-
const parsed = SourceInfoSchema.safeParse(raw);
|
|
22643
|
-
if (!parsed.success) return null;
|
|
22644
|
-
return parsed.data;
|
|
22645
|
-
}
|
|
22646
|
-
/**
|
|
22647
23614
|
* device-discovery — device-scoped capability for parents that host /
|
|
22648
23615
|
* enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
|
|
22649
23616
|
* integrations). The parent advertises a list of discoverable children,
|
|
@@ -23048,12 +24015,48 @@ var ExposeInputSchema = object({
|
|
|
23048
24015
|
capabilities: array(string()).optional()
|
|
23049
24016
|
});
|
|
23050
24017
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
24018
|
+
/**
|
|
24019
|
+
* What an exporter may expose besides devices (D968). An action and a
|
|
24020
|
+
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
24021
|
+
* exporter's own selection, not the device registry.
|
|
24022
|
+
*/
|
|
24023
|
+
var ExportEntityKindSchema = _enum([
|
|
24024
|
+
"device",
|
|
24025
|
+
"action",
|
|
24026
|
+
"notifier"
|
|
24027
|
+
]);
|
|
24028
|
+
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
24029
|
+
object({
|
|
24030
|
+
kind: literal("device"),
|
|
24031
|
+
deviceId: string()
|
|
24032
|
+
}),
|
|
24033
|
+
object({
|
|
24034
|
+
kind: literal("action"),
|
|
24035
|
+
actionId: string().min(1)
|
|
24036
|
+
}),
|
|
24037
|
+
object({
|
|
24038
|
+
kind: literal("notifier"),
|
|
24039
|
+
targetId: string().min(1)
|
|
24040
|
+
})
|
|
24041
|
+
]);
|
|
24042
|
+
var ExposedEntitySchema = object({
|
|
24043
|
+
entity: ExportEntityRefSchema,
|
|
24044
|
+
exposedAs: string().optional()
|
|
24045
|
+
});
|
|
24046
|
+
var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
24047
|
+
var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
23051
24048
|
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
23052
24049
|
kind: "mutation",
|
|
23053
24050
|
auth: "admin"
|
|
23054
24051
|
}), method(UnexposeInputSchema, _void(), {
|
|
23055
24052
|
kind: "mutation",
|
|
23056
24053
|
auth: "admin"
|
|
24054
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
24055
|
+
kind: "mutation",
|
|
24056
|
+
auth: "admin"
|
|
24057
|
+
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
24058
|
+
kind: "mutation",
|
|
24059
|
+
auth: "admin"
|
|
23057
24060
|
});
|
|
23058
24061
|
/** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
|
|
23059
24062
|
var ICON_CATEGORY_IDS = [
|
|
@@ -45119,7 +46122,7 @@ var LlmProviderWidgetSchema = object({
|
|
|
45119
46122
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
45120
46123
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
45121
46124
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
45122
|
-
var ConfigSchemaPassthrough$
|
|
46125
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
45123
46126
|
var LlmProfileKindDescriptorSchema = object({
|
|
45124
46127
|
kind: LlmProfileKindSchema,
|
|
45125
46128
|
label: string(),
|
|
@@ -45128,9 +46131,9 @@ var LlmProfileKindDescriptorSchema = object({
|
|
|
45128
46131
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
45129
46132
|
addonId: string(),
|
|
45130
46133
|
/** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
|
|
45131
|
-
connectionSchema: ConfigSchemaPassthrough$
|
|
46134
|
+
connectionSchema: ConfigSchemaPassthrough$2,
|
|
45132
46135
|
/** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
|
|
45133
|
-
advancedSchema: ConfigSchemaPassthrough$
|
|
46136
|
+
advancedSchema: ConfigSchemaPassthrough$2.optional(),
|
|
45134
46137
|
/** The kind's defaults; a model from `listModels` may override any of them. */
|
|
45135
46138
|
features: LlmModelFeaturesSchema,
|
|
45136
46139
|
/** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
|
|
@@ -45656,10 +46659,10 @@ var CpuBreakdownSchema = object({
|
|
|
45656
46659
|
cores: number()
|
|
45657
46660
|
});
|
|
45658
46661
|
var MemoryInfoSchema = object({
|
|
45659
|
-
percent: number(),
|
|
46662
|
+
percent: number().nullable(),
|
|
45660
46663
|
totalBytes: number(),
|
|
45661
|
-
usedBytes: number(),
|
|
45662
|
-
availableBytes: number(),
|
|
46664
|
+
usedBytes: number().nullable(),
|
|
46665
|
+
availableBytes: number().nullable(),
|
|
45663
46666
|
swapUsedBytes: number(),
|
|
45664
46667
|
swapTotalBytes: number()
|
|
45665
46668
|
});
|
|
@@ -45963,8 +46966,9 @@ var GetLoadSeriesInputSchema = object({
|
|
|
45963
46966
|
});
|
|
45964
46967
|
var SystemMetricsSchema = object({
|
|
45965
46968
|
cpuPercent: number(),
|
|
45966
|
-
|
|
45967
|
-
|
|
46969
|
+
/** `null` when the memory sample failed or was not taken yet (D393). */
|
|
46970
|
+
memoryPercent: number().nullable(),
|
|
46971
|
+
memoryUsedMB: number().nullable(),
|
|
45968
46972
|
memoryTotalMB: number(),
|
|
45969
46973
|
diskPercent: number().optional(),
|
|
45970
46974
|
temperature: number().optional(),
|
|
@@ -46200,427 +47204,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
46200
47204
|
sourcePort: number().optional()
|
|
46201
47205
|
});
|
|
46202
47206
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
46203
|
-
/**
|
|
46204
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
46205
|
-
*
|
|
46206
|
-
* Apprise-derived model (see
|
|
46207
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
46208
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
46209
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
46210
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
46211
|
-
* message to what the kind supports — callers never special-case a service.
|
|
46212
|
-
*
|
|
46213
|
-
* DESIGN DECISIONS (locked):
|
|
46214
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
46215
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
46216
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
46217
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
46218
|
-
* alternative would fork the UI per addon and cannot host the
|
|
46219
|
-
* discovery→adopt flow.
|
|
46220
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
46221
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
46222
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
46223
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
46224
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
46225
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
46226
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
46227
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
46228
|
-
* base64 fallback needed.
|
|
46229
|
-
*
|
|
46230
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
46231
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
46232
|
-
* admin "Integrations" page.
|
|
46233
|
-
*/
|
|
46234
|
-
/**
|
|
46235
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
46236
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
46237
|
-
*/
|
|
46238
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
46239
|
-
"image",
|
|
46240
|
-
"video",
|
|
46241
|
-
"gif",
|
|
46242
|
-
"audio",
|
|
46243
|
-
"icon"
|
|
46244
|
-
]);
|
|
46245
|
-
/**
|
|
46246
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
46247
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
46248
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
46249
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
46250
|
-
*/
|
|
46251
|
-
var AttachmentSchema = object({
|
|
46252
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
46253
|
-
url: string().optional(),
|
|
46254
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
46255
|
-
mime: string().optional(),
|
|
46256
|
-
name: string().optional()
|
|
46257
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
46258
|
-
var NotificationFormatSchema = _enum([
|
|
46259
|
-
"text",
|
|
46260
|
-
"markdown",
|
|
46261
|
-
"html"
|
|
46262
|
-
]);
|
|
46263
|
-
/**
|
|
46264
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
46265
|
-
*
|
|
46266
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
46267
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
46268
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
46269
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
46270
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
46271
|
-
*
|
|
46272
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
46273
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
46274
|
-
* differently.
|
|
46275
|
-
*
|
|
46276
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
46277
|
-
*
|
|
46278
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
46279
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
46280
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
46281
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
46282
|
-
* decides its glyph, which is the only place that decision can be made
|
|
46283
|
-
* honestly.
|
|
46284
|
-
*
|
|
46285
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
46286
|
-
* `disarm` straight through; iOS feeds that string to
|
|
46287
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
46288
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
46289
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
46290
|
-
*
|
|
46291
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
46292
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
46293
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
46294
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
46295
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
46296
|
-
*/
|
|
46297
|
-
var NotificationActionIconSchema = _enum([
|
|
46298
|
-
"acknowledge",
|
|
46299
|
-
"dismiss",
|
|
46300
|
-
"silence",
|
|
46301
|
-
"snooze",
|
|
46302
|
-
"view",
|
|
46303
|
-
"play",
|
|
46304
|
-
"open",
|
|
46305
|
-
"close",
|
|
46306
|
-
"lock",
|
|
46307
|
-
"unlock",
|
|
46308
|
-
"arm",
|
|
46309
|
-
"arm-home",
|
|
46310
|
-
"arm-away",
|
|
46311
|
-
"arm-night",
|
|
46312
|
-
"disarm",
|
|
46313
|
-
"light",
|
|
46314
|
-
"alert",
|
|
46315
|
-
"camera"
|
|
46316
|
-
]);
|
|
46317
|
-
/** A single tap-through action button. */
|
|
46318
|
-
var NotificationActionSchema = object({
|
|
46319
|
-
id: string(),
|
|
46320
|
-
label: string(),
|
|
46321
|
-
url: string().optional(),
|
|
46322
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
46323
|
-
icon: NotificationActionIconSchema.optional(),
|
|
46324
|
-
/**
|
|
46325
|
-
* Renders in a warning style where the notifier supports it.
|
|
46326
|
-
*
|
|
46327
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
46328
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
46329
|
-
* does not buy.
|
|
46330
|
-
*/
|
|
46331
|
-
destructive: boolean().optional(),
|
|
46332
|
-
/**
|
|
46333
|
-
* How the tap should REACH the url.
|
|
46334
|
-
*
|
|
46335
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
46336
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
46337
|
-
* That is right for a button whose answer the operator wants to read.
|
|
46338
|
-
*
|
|
46339
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
46340
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
46341
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
46342
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
46343
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
46344
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
46345
|
-
* a requirement.
|
|
46346
|
-
*/
|
|
46347
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
46348
|
-
});
|
|
46349
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
46350
|
-
var DeliveryParamValueSchema = union([
|
|
46351
|
-
string(),
|
|
46352
|
-
number(),
|
|
46353
|
-
boolean()
|
|
46354
|
-
]);
|
|
46355
|
-
/**
|
|
46356
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
46357
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
46358
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
46359
|
-
* no editor knows a service by name.
|
|
46360
|
-
*
|
|
46361
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
46362
|
-
* run on the addon cap path).
|
|
46363
|
-
*/
|
|
46364
|
-
var DeliveryParamSchema = object({
|
|
46365
|
-
key: string().min(1),
|
|
46366
|
-
label: string(),
|
|
46367
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
46368
|
-
labelKey: string().optional(),
|
|
46369
|
-
type: _enum([
|
|
46370
|
-
"enum",
|
|
46371
|
-
"number",
|
|
46372
|
-
"boolean",
|
|
46373
|
-
"string"
|
|
46374
|
-
]),
|
|
46375
|
-
/** `enum` only: the closed set of accepted values. */
|
|
46376
|
-
options: array(object({
|
|
46377
|
-
value: string(),
|
|
46378
|
-
label: string()
|
|
46379
|
-
})).optional(),
|
|
46380
|
-
/** `number` only. */
|
|
46381
|
-
min: number().optional(),
|
|
46382
|
-
max: number().optional(),
|
|
46383
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
46384
|
-
default: DeliveryParamValueSchema.optional()
|
|
46385
|
-
});
|
|
46386
|
-
/**
|
|
46387
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
46388
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
46389
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
46390
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
46391
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
46392
|
-
* `priority` for that one target.
|
|
46393
|
-
*/
|
|
46394
|
-
var NotificationSchema = object({
|
|
46395
|
-
body: string(),
|
|
46396
|
-
title: string().optional(),
|
|
46397
|
-
format: NotificationFormatSchema.default("text"),
|
|
46398
|
-
priority: number().int().min(1).max(5).default(3),
|
|
46399
|
-
level: string().optional(),
|
|
46400
|
-
attachments: array(AttachmentSchema).optional(),
|
|
46401
|
-
clickUrl: string().optional(),
|
|
46402
|
-
actions: array(NotificationActionSchema).optional(),
|
|
46403
|
-
sound: string().optional(),
|
|
46404
|
-
ttl: number().optional(),
|
|
46405
|
-
tag: string().optional(),
|
|
46406
|
-
deviceId: number().optional(),
|
|
46407
|
-
eventId: string().optional(),
|
|
46408
|
-
metadata: record(string(), unknown()).optional(),
|
|
46409
|
-
/**
|
|
46410
|
-
* Values for the parameters the target's kind DECLARES in
|
|
46411
|
-
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
46412
|
-
* declaration by the sender; a notifier reads only the keys it declared.
|
|
46413
|
-
*/
|
|
46414
|
-
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
46415
|
-
});
|
|
46416
|
-
/** One declared native severity/priority level for a kind. */
|
|
46417
|
-
var TargetKindLevelSchema = object({
|
|
46418
|
-
id: string(),
|
|
46419
|
-
label: string(),
|
|
46420
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
46421
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
46422
|
-
flags: object({
|
|
46423
|
-
critical: boolean().optional(),
|
|
46424
|
-
silent: boolean().optional(),
|
|
46425
|
-
noPush: boolean().optional()
|
|
46426
|
-
}).optional(),
|
|
46427
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
46428
|
-
requires: array(string()).optional(),
|
|
46429
|
-
description: string().optional()
|
|
46430
|
-
});
|
|
46431
|
-
/** The full capability block consulted before dispatch. */
|
|
46432
|
-
var TargetKindCapsSchema = object({
|
|
46433
|
-
attachments: object({
|
|
46434
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
46435
|
-
mode: _enum([
|
|
46436
|
-
"url",
|
|
46437
|
-
"bytes",
|
|
46438
|
-
"both"
|
|
46439
|
-
]),
|
|
46440
|
-
max: number().int().nonnegative(),
|
|
46441
|
-
maxBytes: number().int().positive().optional()
|
|
46442
|
-
}),
|
|
46443
|
-
/** Max action buttons (0 = none). */
|
|
46444
|
-
actions: number().int().nonnegative(),
|
|
46445
|
-
/**
|
|
46446
|
-
* Whether this kind renders a per-action ICON.
|
|
46447
|
-
*
|
|
46448
|
-
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
46449
|
-
* run on the addon cap path — three production failures in one day taught
|
|
46450
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
46451
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
46452
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
46453
|
-
*/
|
|
46454
|
-
actionIcons: boolean().optional(),
|
|
46455
|
-
levels: array(TargetKindLevelSchema),
|
|
46456
|
-
format: array(NotificationFormatSchema),
|
|
46457
|
-
clickUrl: boolean(),
|
|
46458
|
-
sound: boolean(),
|
|
46459
|
-
ttl: boolean(),
|
|
46460
|
-
bodyMaxLen: number().int().positive()
|
|
46461
|
-
});
|
|
46462
|
-
/**
|
|
46463
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
46464
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
46465
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
46466
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
46467
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
46468
|
-
*/
|
|
46469
|
-
var ConfigSchemaPassthrough$2 = unknown();
|
|
46470
|
-
var TargetKindSchema = object({
|
|
46471
|
-
kind: string(),
|
|
46472
|
-
label: string(),
|
|
46473
|
-
icon: string(),
|
|
46474
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
46475
|
-
addonId: string(),
|
|
46476
|
-
/**
|
|
46477
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
46478
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
46479
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
46480
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
46481
|
-
*
|
|
46482
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
46483
|
-
* client, and a native client joins it onto its own hub base.
|
|
46484
|
-
*
|
|
46485
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
46486
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
46487
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
46488
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
46489
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
46490
|
-
*/
|
|
46491
|
-
iconUrl: string().optional(),
|
|
46492
|
-
/**
|
|
46493
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
46494
|
-
*
|
|
46495
|
-
* The server knows this and therefore says it, because the client cannot
|
|
46496
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
46497
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
46498
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
46499
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
46500
|
-
*
|
|
46501
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
46502
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
46503
|
-
* its raster path, which is the safe default for an unknown image.
|
|
46504
|
-
*/
|
|
46505
|
-
iconMediaType: string().optional(),
|
|
46506
|
-
configSchema: ConfigSchemaPassthrough$2,
|
|
46507
|
-
/**
|
|
46508
|
-
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
46509
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
46510
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
46511
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
46512
|
-
* on every edit.
|
|
46513
|
-
*
|
|
46514
|
-
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
46515
|
-
* kinds) and from a provider older than this field — an editor that finds
|
|
46516
|
-
* it absent must assume ANY edit may need the secret.
|
|
46517
|
-
*/
|
|
46518
|
-
destinationKeys: array(string()).optional(),
|
|
46519
|
-
supportsDiscovery: boolean(),
|
|
46520
|
-
/**
|
|
46521
|
-
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
46522
|
-
* Absent = none; the rule editor then offers nothing.
|
|
46523
|
-
*/
|
|
46524
|
-
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
46525
|
-
caps: TargetKindCapsSchema
|
|
46526
|
-
});
|
|
46527
|
-
/**
|
|
46528
|
-
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
46529
|
-
* (return a presence marker only) when serving `listTargets` — never
|
|
46530
|
-
* round-trip a stored secret to the UI.
|
|
46531
|
-
*/
|
|
46532
|
-
var TargetSchema = object({
|
|
46533
|
-
id: string(),
|
|
46534
|
-
name: string(),
|
|
46535
|
-
kind: string(),
|
|
46536
|
-
addonId: string(),
|
|
46537
|
-
enabled: boolean(),
|
|
46538
|
-
config: record(string(), unknown())
|
|
46539
|
-
});
|
|
46540
|
-
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
46541
|
-
var DiscoveredTargetSchema = object({
|
|
46542
|
-
kind: string(),
|
|
46543
|
-
suggestedName: string(),
|
|
46544
|
-
config: record(string(), unknown())
|
|
46545
|
-
});
|
|
46546
|
-
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
46547
|
-
var RenderedAsSchema = object({
|
|
46548
|
-
level: string(),
|
|
46549
|
-
format: NotificationFormatSchema,
|
|
46550
|
-
attachmentsSent: number().int().nonnegative(),
|
|
46551
|
-
actionsSent: number().int().nonnegative(),
|
|
46552
|
-
truncated: boolean(),
|
|
46553
|
-
dropped: array(string())
|
|
46554
|
-
});
|
|
46555
|
-
var SendResultSchema = object({
|
|
46556
|
-
success: boolean(),
|
|
46557
|
-
error: string().optional(),
|
|
46558
|
-
renderedAs: RenderedAsSchema.optional()
|
|
46559
|
-
});
|
|
46560
|
-
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
46561
|
-
var TestResultSchema = SendResultSchema;
|
|
46562
|
-
/**
|
|
46563
|
-
* Pre-save credential probe against a partial (unsaved) target config.
|
|
46564
|
-
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
46565
|
-
*/
|
|
46566
|
-
var ProbeTargetConfigResultSchema = object({
|
|
46567
|
-
ok: boolean(),
|
|
46568
|
-
resolvedUrl: string(),
|
|
46569
|
-
httpStatus: number().int().optional(),
|
|
46570
|
-
stateInstance: string().optional(),
|
|
46571
|
-
error: string().optional()
|
|
46572
|
-
});
|
|
46573
|
-
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
46574
|
-
kind: string(),
|
|
46575
|
-
config: record(string(), unknown()).optional(),
|
|
46576
|
-
/**
|
|
46577
|
-
* When editing / duplicating, the blank password field never round-trips
|
|
46578
|
-
* the stored token. Pass the source target id so the provider can restore
|
|
46579
|
-
* secrets server-side before dialling (same merge as upsert).
|
|
46580
|
-
*/
|
|
46581
|
-
targetId: string().optional()
|
|
46582
|
-
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
46583
|
-
kind: string(),
|
|
46584
|
-
config: record(string(), unknown()),
|
|
46585
|
-
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
46586
|
-
targetId: string().optional()
|
|
46587
|
-
}), ProbeTargetConfigResultSchema, {
|
|
46588
|
-
kind: "mutation",
|
|
46589
|
-
auth: "admin"
|
|
46590
|
-
}), method(object({
|
|
46591
|
-
targetId: string(),
|
|
46592
|
-
notification: NotificationSchema
|
|
46593
|
-
}), SendResultSchema, {
|
|
46594
|
-
kind: "mutation",
|
|
46595
|
-
auth: "admin"
|
|
46596
|
-
}), method(object({
|
|
46597
|
-
targetId: string(),
|
|
46598
|
-
sample: NotificationSchema.optional()
|
|
46599
|
-
}), TestResultSchema, {
|
|
46600
|
-
kind: "mutation",
|
|
46601
|
-
auth: "admin"
|
|
46602
|
-
}), method(object({
|
|
46603
|
-
target: TargetSchema,
|
|
46604
|
-
/**
|
|
46605
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
46606
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
46607
|
-
* password fields are restored from this source (D634 still applies if
|
|
46608
|
-
* destinations diverge without a re-typed secret).
|
|
46609
|
-
*/
|
|
46610
|
-
cloneSecretsFrom: string().optional()
|
|
46611
|
-
}), TargetSchema, {
|
|
46612
|
-
kind: "mutation",
|
|
46613
|
-
auth: "admin"
|
|
46614
|
-
}), method(object({ targetId: string() }), _void(), {
|
|
46615
|
-
kind: "mutation",
|
|
46616
|
-
auth: "admin"
|
|
46617
|
-
}), method(object({
|
|
46618
|
-
targetId: string(),
|
|
46619
|
-
enabled: boolean()
|
|
46620
|
-
}), _void(), {
|
|
46621
|
-
kind: "mutation",
|
|
46622
|
-
auth: "admin"
|
|
46623
|
-
});
|
|
46624
47207
|
var AUDIO_MACRO_LABELS = [
|
|
46625
47208
|
{
|
|
46626
47209
|
id: "speech",
|
|
@@ -54865,14 +55448,6 @@ var EngineProvisioningSchema = object({
|
|
|
54865
55448
|
*/
|
|
54866
55449
|
configIssues: array(string()).optional()
|
|
54867
55450
|
});
|
|
54868
|
-
var PipelineStepInputSchema = lazy(() => object({
|
|
54869
|
-
addonId: string(),
|
|
54870
|
-
modelId: string().optional(),
|
|
54871
|
-
enabled: boolean().default(true),
|
|
54872
|
-
children: array(PipelineStepInputSchema).optional(),
|
|
54873
|
-
settings: record(string(), unknown()).optional(),
|
|
54874
|
-
jumpDeviceKey: string().optional()
|
|
54875
|
-
}));
|
|
54876
55451
|
var ModelSubstitutionSchema = object({
|
|
54877
55452
|
addonId: string(),
|
|
54878
55453
|
chosen: string(),
|
|
@@ -54951,7 +55526,6 @@ var ConfigUISchemaBridge = custom();
|
|
|
54951
55526
|
var ConfigUISchemaNullableBridge = custom();
|
|
54952
55527
|
var InferenceCapabilitiesBridge = custom();
|
|
54953
55528
|
var ModelAvailabilityListBridge = custom();
|
|
54954
|
-
var PipelineRunResultBridge = custom();
|
|
54955
55529
|
method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
|
|
54956
55530
|
modelId: string(),
|
|
54957
55531
|
settings: record(string(), unknown()).readonly()
|
|
@@ -57947,11 +58521,13 @@ var ImageContractSchema = object({
|
|
|
57947
58521
|
* The shell verdict (see {@link WrapperKindSchema}) with the proofs that
|
|
57948
58522
|
* produced it.
|
|
57949
58523
|
*
|
|
57950
|
-
* There is deliberately NO `canSelfUpdate`
|
|
57951
|
-
*
|
|
57952
|
-
*
|
|
57953
|
-
*
|
|
57954
|
-
*
|
|
58524
|
+
* There is deliberately NO `canSelfUpdate` boolean: a wrapper update is an
|
|
58525
|
+
* ANNOUNCEMENT. The notification body carries the exact command for a `docker`
|
|
58526
|
+
* shell and says the app restarts into the new build for `electron`, and that
|
|
58527
|
+
* sentence is chosen from `kind` alone — a boolean nothing reads is the defect
|
|
58528
|
+
* this repo keeps paying for. The one shell that CAN be acted on is the
|
|
58529
|
+
* Electron app, and only through `installDesktopUpdate` (see
|
|
58530
|
+
* {@link DesktopAppStatusSchema} for what the node reads from the app first).
|
|
57955
58531
|
*/
|
|
57956
58532
|
var WrapperIdentitySchema = object({
|
|
57957
58533
|
kind: _enum([
|
|
@@ -57999,6 +58575,49 @@ var ServerRollbackInfoSchema = object({
|
|
|
57999
58575
|
reason: string()
|
|
58000
58576
|
});
|
|
58001
58577
|
/**
|
|
58578
|
+
* What the native desktop app (Electron) that hosts this node says about
|
|
58579
|
+
* itself, read from `<dataDir>/server-root/desktop-app-state.json` (ADR-0971).
|
|
58580
|
+
* Absent on the status = no desktop app, or it never wrote the file.
|
|
58581
|
+
*
|
|
58582
|
+
* `stale` is the honest answer to an app that stopped writing (closed, hung,
|
|
58583
|
+
* crashed): the update facts are then the LAST ones known, not current ones,
|
|
58584
|
+
* and a consumer must not read `update.available: false` from a stale record
|
|
58585
|
+
* as "no update". `stateAtMs` is when the app last wrote.
|
|
58586
|
+
*/
|
|
58587
|
+
var DesktopAppStatusSchema = object({
|
|
58588
|
+
/** The Electron app's own version (not the node closure's). */
|
|
58589
|
+
version: string(),
|
|
58590
|
+
update: object({
|
|
58591
|
+
available: boolean(),
|
|
58592
|
+
/** The new build is downloaded and `installDesktopUpdate` can apply it. */
|
|
58593
|
+
downloaded: boolean(),
|
|
58594
|
+
version: string().nullable(),
|
|
58595
|
+
checking: boolean(),
|
|
58596
|
+
/** Why the app's last update check produced no answer; `null` when it did. */
|
|
58597
|
+
error: string().nullable()
|
|
58598
|
+
}),
|
|
58599
|
+
stateAtMs: number(),
|
|
58600
|
+
stale: boolean()
|
|
58601
|
+
});
|
|
58602
|
+
/**
|
|
58603
|
+
* Why `installDesktopUpdate` did nothing. Stable names a UI maps to a sentence:
|
|
58604
|
+
* - `not-electron` — this node does not run inside the desktop app.
|
|
58605
|
+
* - `no-app-state` — the app never published its state (or the file is unreadable).
|
|
58606
|
+
* - `app-state-stale` — the app stopped writing; it is probably not running.
|
|
58607
|
+
* - `not-downloaded` — no update is downloaded yet; nothing to install.
|
|
58608
|
+
*/
|
|
58609
|
+
var InstallDesktopUpdateReasonSchema = _enum([
|
|
58610
|
+
"not-electron",
|
|
58611
|
+
"no-app-state",
|
|
58612
|
+
"app-state-stale",
|
|
58613
|
+
"not-downloaded"
|
|
58614
|
+
]);
|
|
58615
|
+
var InstallDesktopUpdateResultSchema = object({
|
|
58616
|
+
accepted: boolean(),
|
|
58617
|
+
/** Non-null exactly when `accepted` is false. */
|
|
58618
|
+
reason: InstallDesktopUpdateReasonSchema.nullable()
|
|
58619
|
+
});
|
|
58620
|
+
/**
|
|
58002
58621
|
* The runner boot check (D877): the runners this node PLANNED (hub: one per
|
|
58003
58622
|
* `resolveRunnerId` over its forked addons; agent: the runners its process
|
|
58004
58623
|
* service holds, because only the hub plans) against the states the process
|
|
@@ -58102,7 +58721,12 @@ var ServerPackageStatusSchema = object({
|
|
|
58102
58721
|
* on a provider that predates it. Absent is "not checked yet", never "all
|
|
58103
58722
|
* well": a consumer that needs the answer treats it as pending.
|
|
58104
58723
|
*/
|
|
58105
|
-
runners: RunnerBootCheckSchema.optional()
|
|
58724
|
+
runners: RunnerBootCheckSchema.optional(),
|
|
58725
|
+
/**
|
|
58726
|
+
* The desktop app hosting this node (see {@link DesktopAppStatusSchema}).
|
|
58727
|
+
* Optional for version skew AND because most nodes have no desktop app.
|
|
58728
|
+
*/
|
|
58729
|
+
desktopApp: DesktopAppStatusSchema.optional()
|
|
58106
58730
|
});
|
|
58107
58731
|
var ServerUpdateCheckResultSchema = object({
|
|
58108
58732
|
packageName: string(),
|
|
@@ -58185,6 +58809,12 @@ var ServerUpdateActionResultSchema = object({
|
|
|
58185
58809
|
*/
|
|
58186
58810
|
blockers: array(UpdateBlockerSchema).optional()
|
|
58187
58811
|
});
|
|
58812
|
+
var LaunchOverridesSchema = object({
|
|
58813
|
+
/** The persisted heap ceiling in MB, or `null` when none is set. */
|
|
58814
|
+
heapMb: number().int().nullable() });
|
|
58815
|
+
var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
|
|
58816
|
+
/** Always true: the override is read at launch, not by the running process. */
|
|
58817
|
+
restartRequired: boolean() });
|
|
58188
58818
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
58189
58819
|
kind: "mutation",
|
|
58190
58820
|
auth: "admin"
|
|
@@ -58201,12 +58831,18 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
|
|
|
58201
58831
|
kind: "mutation",
|
|
58202
58832
|
auth: "admin",
|
|
58203
58833
|
timeoutMs: 16 * 6e4
|
|
58834
|
+
}), method(_void(), InstallDesktopUpdateResultSchema, {
|
|
58835
|
+
kind: "mutation",
|
|
58836
|
+
auth: "admin"
|
|
58204
58837
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58205
58838
|
kind: "mutation",
|
|
58206
58839
|
auth: "admin"
|
|
58207
58840
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58208
58841
|
kind: "mutation",
|
|
58209
58842
|
auth: "admin"
|
|
58843
|
+
}), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
|
|
58844
|
+
kind: "mutation",
|
|
58845
|
+
auth: "admin"
|
|
58210
58846
|
});
|
|
58211
58847
|
/**
|
|
58212
58848
|
* `smtp-provider` — pluggable email delivery surface.
|
|
@@ -61348,13 +61984,19 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
|
61348
61984
|
}), method(ActionIdInput, _void(), {
|
|
61349
61985
|
kind: "mutation",
|
|
61350
61986
|
auth: "admin"
|
|
61351
|
-
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
61987
|
+
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
61988
|
+
action: ActionEntrySchema,
|
|
61989
|
+
warnings: array(UnitWarningSchema).optional()
|
|
61990
|
+
}), {
|
|
61352
61991
|
kind: "mutation",
|
|
61353
61992
|
auth: "admin"
|
|
61354
61993
|
}), method(object({
|
|
61355
61994
|
actionId: string().min(1),
|
|
61356
61995
|
action: ActionInputSchema.partial()
|
|
61357
|
-
}), object({
|
|
61996
|
+
}), object({
|
|
61997
|
+
action: ActionEntrySchema,
|
|
61998
|
+
warnings: array(UnitWarningSchema).optional()
|
|
61999
|
+
}), {
|
|
61358
62000
|
kind: "mutation",
|
|
61359
62001
|
auth: "admin"
|
|
61360
62002
|
}), method(ActionIdInput, _void(), {
|
|
@@ -61936,6 +62578,30 @@ object({
|
|
|
61936
62578
|
at: number()
|
|
61937
62579
|
}).nullable()
|
|
61938
62580
|
});
|
|
62581
|
+
var RemoteButtonShapeSchema = _enum([
|
|
62582
|
+
"rect",
|
|
62583
|
+
"rounded",
|
|
62584
|
+
"round",
|
|
62585
|
+
"pill"
|
|
62586
|
+
]);
|
|
62587
|
+
var RemoteButtonToneSchema = _enum([
|
|
62588
|
+
"neutral",
|
|
62589
|
+
"accent",
|
|
62590
|
+
"danger",
|
|
62591
|
+
"red",
|
|
62592
|
+
"green",
|
|
62593
|
+
"yellow",
|
|
62594
|
+
"blue"
|
|
62595
|
+
]);
|
|
62596
|
+
var RemoteGroupFrameSchema = _enum([
|
|
62597
|
+
"none",
|
|
62598
|
+
"panel",
|
|
62599
|
+
"divider",
|
|
62600
|
+
"dpad",
|
|
62601
|
+
"rocker-v",
|
|
62602
|
+
"rocker-h",
|
|
62603
|
+
"keypad"
|
|
62604
|
+
]);
|
|
61939
62605
|
var RemoteLayoutButtonSchema = object({
|
|
61940
62606
|
id: string().min(1).max(40),
|
|
61941
62607
|
/** 0-based grid position. */
|
|
@@ -61945,12 +62611,94 @@ var RemoteLayoutButtonSchema = object({
|
|
|
61945
62611
|
colSpan: number().int().min(1).max(24),
|
|
61946
62612
|
icon: string().min(1).max(60).optional(),
|
|
61947
62613
|
label: string().min(1).max(40).optional(),
|
|
61948
|
-
role: RemoteRoleSchema
|
|
62614
|
+
role: RemoteRoleSchema,
|
|
62615
|
+
/** Absent = the plain rendering every layout had before shapes existed. */
|
|
62616
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62617
|
+
tone: RemoteButtonToneSchema.optional(),
|
|
62618
|
+
/** The group this button belongs to; it must lie inside that group's rectangle. */
|
|
62619
|
+
groupId: string().min(1).max(40).optional()
|
|
61949
62620
|
});
|
|
62621
|
+
/**
|
|
62622
|
+
* A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
|
|
62623
|
+
* titled panel, a section divider, a D-pad ring. `componentId` names the palette
|
|
62624
|
+
* entry it came from (informational: the layout is self-contained).
|
|
62625
|
+
*/
|
|
62626
|
+
var RemoteLayoutGroupSchema = object({
|
|
62627
|
+
id: string().min(1).max(40),
|
|
62628
|
+
componentId: string().min(1).max(80),
|
|
62629
|
+
frame: RemoteGroupFrameSchema,
|
|
62630
|
+
row: number().int().min(0).max(24),
|
|
62631
|
+
col: number().int().min(0).max(24),
|
|
62632
|
+
rowSpan: number().int().min(1).max(24),
|
|
62633
|
+
colSpan: number().int().min(1).max(24),
|
|
62634
|
+
title: string().min(1).max(40).optional()
|
|
62635
|
+
});
|
|
62636
|
+
function rectsOverlap(a, b) {
|
|
62637
|
+
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;
|
|
62638
|
+
}
|
|
62639
|
+
function rectContains(outer, inner) {
|
|
62640
|
+
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;
|
|
62641
|
+
}
|
|
62642
|
+
/**
|
|
62643
|
+
* The group rules of a layout: inside the grid, no two groups overlap, a
|
|
62644
|
+
* member lies inside its group, a divider holds nothing, and a button that is
|
|
62645
|
+
* not a member never sits inside someone else's rectangle (a group moves as
|
|
62646
|
+
* one thing, so a stray key under it would be left behind).
|
|
62647
|
+
*/
|
|
62648
|
+
function refineGroups(grid, ctx) {
|
|
62649
|
+
const groups = grid.groups ?? [];
|
|
62650
|
+
const byId = /* @__PURE__ */ new Map();
|
|
62651
|
+
const fail = (message, path) => {
|
|
62652
|
+
ctx.addIssue({
|
|
62653
|
+
code: "custom",
|
|
62654
|
+
message,
|
|
62655
|
+
path
|
|
62656
|
+
});
|
|
62657
|
+
};
|
|
62658
|
+
groups.forEach((group, index) => {
|
|
62659
|
+
if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
|
|
62660
|
+
"groups",
|
|
62661
|
+
index,
|
|
62662
|
+
"id"
|
|
62663
|
+
]);
|
|
62664
|
+
byId.set(group.id, group);
|
|
62665
|
+
if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
|
|
62666
|
+
for (let other = 0; other < index; other += 1) {
|
|
62667
|
+
const earlier = groups[other];
|
|
62668
|
+
if (earlier !== void 0 && rectsOverlap(earlier, group)) {
|
|
62669
|
+
fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
|
|
62670
|
+
return;
|
|
62671
|
+
}
|
|
62672
|
+
}
|
|
62673
|
+
});
|
|
62674
|
+
grid.buttons.forEach((button, index) => {
|
|
62675
|
+
if (button.groupId !== void 0) {
|
|
62676
|
+
const group = byId.get(button.groupId);
|
|
62677
|
+
if (group === void 0) {
|
|
62678
|
+
fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
|
|
62679
|
+
"buttons",
|
|
62680
|
+
index,
|
|
62681
|
+
"groupId"
|
|
62682
|
+
]);
|
|
62683
|
+
return;
|
|
62684
|
+
}
|
|
62685
|
+
if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
|
|
62686
|
+
"buttons",
|
|
62687
|
+
index,
|
|
62688
|
+
"groupId"
|
|
62689
|
+
]);
|
|
62690
|
+
if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
|
|
62691
|
+
return;
|
|
62692
|
+
}
|
|
62693
|
+
const foreign = groups.find((group) => rectsOverlap(group, button));
|
|
62694
|
+
if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
|
|
62695
|
+
});
|
|
62696
|
+
}
|
|
61950
62697
|
object({
|
|
61951
62698
|
columns: number().int().min(1).max(24),
|
|
61952
62699
|
rows: number().int().min(1).max(24),
|
|
61953
|
-
buttons: array(RemoteLayoutButtonSchema).max(200)
|
|
62700
|
+
buttons: array(RemoteLayoutButtonSchema).max(200),
|
|
62701
|
+
groups: array(RemoteLayoutGroupSchema).max(50).optional()
|
|
61954
62702
|
}).superRefine((grid, ctx) => {
|
|
61955
62703
|
const ids = /* @__PURE__ */ new Set();
|
|
61956
62704
|
const taken = /* @__PURE__ */ new Map();
|
|
@@ -61987,6 +62735,7 @@ object({
|
|
|
61987
62735
|
taken.set(cell, button.id);
|
|
61988
62736
|
}
|
|
61989
62737
|
});
|
|
62738
|
+
refineGroups(grid, ctx);
|
|
61990
62739
|
});
|
|
61991
62740
|
var RemoteBoundCommandSchema = object({
|
|
61992
62741
|
role: RemoteRoleSchema,
|
|
@@ -62118,6 +62867,91 @@ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSche
|
|
|
62118
62867
|
kind: "mutation",
|
|
62119
62868
|
auth: "admin"
|
|
62120
62869
|
});
|
|
62870
|
+
var RemoteComponentCategorySchema = _enum([
|
|
62871
|
+
"power",
|
|
62872
|
+
"navigation",
|
|
62873
|
+
"volume",
|
|
62874
|
+
"numbers",
|
|
62875
|
+
"media",
|
|
62876
|
+
"colour",
|
|
62877
|
+
"apps",
|
|
62878
|
+
"layout"
|
|
62879
|
+
]);
|
|
62880
|
+
/** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
|
|
62881
|
+
var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
|
|
62882
|
+
var RemoteComponentButtonSchema = object({
|
|
62883
|
+
/** Joined to the instance id when the component is inserted. */
|
|
62884
|
+
idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
|
|
62885
|
+
row: number().int().min(0).max(24),
|
|
62886
|
+
col: number().int().min(0).max(24),
|
|
62887
|
+
rowSpan: number().int().min(1).max(24),
|
|
62888
|
+
colSpan: number().int().min(1).max(24),
|
|
62889
|
+
role: RemoteRoleSchema,
|
|
62890
|
+
icon: string().min(1).max(60).optional(),
|
|
62891
|
+
label: string().min(1).max(40).optional(),
|
|
62892
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62893
|
+
tone: RemoteButtonToneSchema.optional()
|
|
62894
|
+
});
|
|
62895
|
+
var RemoteLayoutComponentSchema = object({
|
|
62896
|
+
id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
|
|
62897
|
+
name: string().min(1).max(60),
|
|
62898
|
+
nameI18n: object({
|
|
62899
|
+
en: string().min(1).max(60),
|
|
62900
|
+
it: string().min(1).max(60)
|
|
62901
|
+
}).optional(),
|
|
62902
|
+
/** A display icon key, resolved by the UI like a button icon. */
|
|
62903
|
+
icon: string().min(1).max(60),
|
|
62904
|
+
category: RemoteComponentCategorySchema,
|
|
62905
|
+
size: object({
|
|
62906
|
+
rows: number().int().min(1).max(24),
|
|
62907
|
+
cols: number().int().min(1).max(24)
|
|
62908
|
+
}),
|
|
62909
|
+
/** The frame drawn around the whole component, or null for loose buttons. */
|
|
62910
|
+
group: object({
|
|
62911
|
+
frame: RemoteGroupFrameSchema,
|
|
62912
|
+
title: string().min(1).max(40).optional()
|
|
62913
|
+
}).nullable(),
|
|
62914
|
+
buttons: array(RemoteComponentButtonSchema).max(40)
|
|
62915
|
+
}).superRefine((component, ctx) => {
|
|
62916
|
+
const suffixes = /* @__PURE__ */ new Set();
|
|
62917
|
+
component.buttons.forEach((button, index) => {
|
|
62918
|
+
if (suffixes.has(button.idSuffix)) ctx.addIssue({
|
|
62919
|
+
code: "custom",
|
|
62920
|
+
message: `button "${button.idSuffix}" is listed twice`,
|
|
62921
|
+
path: [
|
|
62922
|
+
"buttons",
|
|
62923
|
+
index,
|
|
62924
|
+
"idSuffix"
|
|
62925
|
+
]
|
|
62926
|
+
});
|
|
62927
|
+
suffixes.add(button.idSuffix);
|
|
62928
|
+
if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
|
|
62929
|
+
ctx.addIssue({
|
|
62930
|
+
code: "custom",
|
|
62931
|
+
message: `button "${button.idSuffix}" leaves the component`,
|
|
62932
|
+
path: ["buttons", index]
|
|
62933
|
+
});
|
|
62934
|
+
return;
|
|
62935
|
+
}
|
|
62936
|
+
for (let other = 0; other < index; other += 1) {
|
|
62937
|
+
const earlier = component.buttons[other];
|
|
62938
|
+
if (earlier !== void 0 && rectsOverlap(earlier, button)) {
|
|
62939
|
+
ctx.addIssue({
|
|
62940
|
+
code: "custom",
|
|
62941
|
+
message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
|
|
62942
|
+
path: ["buttons", index]
|
|
62943
|
+
});
|
|
62944
|
+
return;
|
|
62945
|
+
}
|
|
62946
|
+
}
|
|
62947
|
+
});
|
|
62948
|
+
if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
|
|
62949
|
+
code: "custom",
|
|
62950
|
+
message: "a divider component holds no buttons",
|
|
62951
|
+
path: ["buttons"]
|
|
62952
|
+
});
|
|
62953
|
+
});
|
|
62954
|
+
method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
|
|
62121
62955
|
DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
62122
62956
|
kind: "mutation",
|
|
62123
62957
|
auth: "protected"
|
|
@@ -63173,14 +64007,13 @@ reset: method(object({
|
|
|
63173
64007
|
auth: "admin"
|
|
63174
64008
|
}) },
|
|
63175
64009
|
/**
|
|
63176
|
-
* A
|
|
63177
|
-
*
|
|
63178
|
-
*
|
|
63179
|
-
* commands (`forward`) arrive in slice 3 (D663).
|
|
64010
|
+
* A consumable ADDED to an existing device (a graft) refuses `reset` by
|
|
64011
|
+
* name: only a composed device of a NEW type forwards an item's reset to a
|
|
64012
|
+
* source button, and marks that item `resettable` (D967).
|
|
63180
64013
|
*/
|
|
63181
64014
|
composedMethods: { reset: {
|
|
63182
64015
|
kind: "refuse",
|
|
63183
|
-
reason: "a
|
|
64016
|
+
reason: "a consumable added to an existing device is not resettable — only a new composed device forwards a reset"
|
|
63184
64017
|
} },
|
|
63185
64018
|
status: {
|
|
63186
64019
|
schema: ConsumablesStatusSchema,
|
|
@@ -63796,7 +64629,7 @@ var DishwasherStatusSchema = object({
|
|
|
63796
64629
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
63797
64630
|
lastFetchedAt: number()
|
|
63798
64631
|
});
|
|
63799
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
64632
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
63800
64633
|
/** A value a program option or setting can take. */
|
|
63801
64634
|
var DishwasherValueSchema = union([
|
|
63802
64635
|
boolean(),
|
|
@@ -63871,77 +64704,77 @@ var dishwasherCapability = {
|
|
|
63871
64704
|
* option feature keys; each is checked against the program's constraints.
|
|
63872
64705
|
*/
|
|
63873
64706
|
start: method(object({
|
|
63874
|
-
deviceId: DeviceIdSchema,
|
|
64707
|
+
deviceId: DeviceIdSchema$1,
|
|
63875
64708
|
program: string().min(1).optional(),
|
|
63876
64709
|
options: record(string(), DishwasherValueSchema).optional()
|
|
63877
64710
|
}), _void(), {
|
|
63878
64711
|
kind: "mutation",
|
|
63879
64712
|
auth: "protected"
|
|
63880
64713
|
}),
|
|
63881
|
-
pause: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64714
|
+
pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63882
64715
|
kind: "mutation",
|
|
63883
64716
|
auth: "protected"
|
|
63884
64717
|
}),
|
|
63885
|
-
resume: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64718
|
+
resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63886
64719
|
kind: "mutation",
|
|
63887
64720
|
auth: "protected"
|
|
63888
64721
|
}),
|
|
63889
|
-
stop: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64722
|
+
stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63890
64723
|
kind: "mutation",
|
|
63891
64724
|
auth: "protected"
|
|
63892
64725
|
}),
|
|
63893
64726
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
63894
64727
|
setProgram: method(object({
|
|
63895
|
-
deviceId: DeviceIdSchema,
|
|
64728
|
+
deviceId: DeviceIdSchema$1,
|
|
63896
64729
|
program: string().min(1)
|
|
63897
64730
|
}), _void(), {
|
|
63898
64731
|
kind: "mutation",
|
|
63899
64732
|
auth: "protected"
|
|
63900
64733
|
}),
|
|
63901
64734
|
setPower: method(object({
|
|
63902
|
-
deviceId: DeviceIdSchema,
|
|
64735
|
+
deviceId: DeviceIdSchema$1,
|
|
63903
64736
|
on: boolean()
|
|
63904
64737
|
}), _void(), {
|
|
63905
64738
|
kind: "mutation",
|
|
63906
64739
|
auth: "protected"
|
|
63907
64740
|
}),
|
|
63908
64741
|
setHalfLoad: method(object({
|
|
63909
|
-
deviceId: DeviceIdSchema,
|
|
64742
|
+
deviceId: DeviceIdSchema$1,
|
|
63910
64743
|
on: boolean()
|
|
63911
64744
|
}), _void(), {
|
|
63912
64745
|
kind: "mutation",
|
|
63913
64746
|
auth: "protected"
|
|
63914
64747
|
}),
|
|
63915
64748
|
setHygienePlus: method(object({
|
|
63916
|
-
deviceId: DeviceIdSchema,
|
|
64749
|
+
deviceId: DeviceIdSchema$1,
|
|
63917
64750
|
on: boolean()
|
|
63918
64751
|
}), _void(), {
|
|
63919
64752
|
kind: "mutation",
|
|
63920
64753
|
auth: "protected"
|
|
63921
64754
|
}),
|
|
63922
64755
|
setExtraDry: method(object({
|
|
63923
|
-
deviceId: DeviceIdSchema,
|
|
64756
|
+
deviceId: DeviceIdSchema$1,
|
|
63924
64757
|
on: boolean()
|
|
63925
64758
|
}), _void(), {
|
|
63926
64759
|
kind: "mutation",
|
|
63927
64760
|
auth: "protected"
|
|
63928
64761
|
}),
|
|
63929
64762
|
setIntensiveZone: method(object({
|
|
63930
|
-
deviceId: DeviceIdSchema,
|
|
64763
|
+
deviceId: DeviceIdSchema$1,
|
|
63931
64764
|
on: boolean()
|
|
63932
64765
|
}), _void(), {
|
|
63933
64766
|
kind: "mutation",
|
|
63934
64767
|
auth: "protected"
|
|
63935
64768
|
}),
|
|
63936
64769
|
setVarioSpeedPlus: method(object({
|
|
63937
|
-
deviceId: DeviceIdSchema,
|
|
64770
|
+
deviceId: DeviceIdSchema$1,
|
|
63938
64771
|
on: boolean()
|
|
63939
64772
|
}), _void(), {
|
|
63940
64773
|
kind: "mutation",
|
|
63941
64774
|
auth: "protected"
|
|
63942
64775
|
}),
|
|
63943
64776
|
setChildLock: method(object({
|
|
63944
|
-
deviceId: DeviceIdSchema,
|
|
64777
|
+
deviceId: DeviceIdSchema$1,
|
|
63945
64778
|
on: boolean()
|
|
63946
64779
|
}), _void(), {
|
|
63947
64780
|
kind: "mutation",
|
|
@@ -63953,7 +64786,7 @@ var dishwasherCapability = {
|
|
|
63953
64786
|
* its declared range / members.
|
|
63954
64787
|
*/
|
|
63955
64788
|
setOption: method(object({
|
|
63956
|
-
deviceId: DeviceIdSchema,
|
|
64789
|
+
deviceId: DeviceIdSchema$1,
|
|
63957
64790
|
key: string().min(1),
|
|
63958
64791
|
value: DishwasherValueSchema
|
|
63959
64792
|
}), _void(), {
|
|
@@ -63961,7 +64794,7 @@ var dishwasherCapability = {
|
|
|
63961
64794
|
auth: "protected"
|
|
63962
64795
|
}),
|
|
63963
64796
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
63964
|
-
getOptions: method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" })
|
|
64797
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
63965
64798
|
},
|
|
63966
64799
|
status: {
|
|
63967
64800
|
schema: DishwasherStatusSchema,
|
|
@@ -67738,7 +68571,8 @@ var TopologyNodeSchema = object({
|
|
|
67738
68571
|
isHub: boolean(),
|
|
67739
68572
|
isOnline: boolean(),
|
|
67740
68573
|
cpuPercent: number(),
|
|
67741
|
-
|
|
68574
|
+
/** `null` when the node's memory read failed or it has not reported one (D393). */
|
|
68575
|
+
memoryPercent: number().nullable(),
|
|
67742
68576
|
uptime: number(),
|
|
67743
68577
|
lastSeen: string(),
|
|
67744
68578
|
localIps: array(string()).readonly(),
|
|
@@ -68349,6 +69183,14 @@ var PetFeederDeviceStatusSchema = _enum([
|
|
|
68349
69183
|
"on_batteries"
|
|
68350
69184
|
]);
|
|
68351
69185
|
var gramsPortion = number().int().min(4).max(200);
|
|
69186
|
+
/** A portion REQUESTED of the feeder is a mass the method takes in GRAMS (the
|
|
69187
|
+
* hardware's own unit). The bounds are grams; a caller entering another unit
|
|
69188
|
+
* converts to grams first (`paramMeta`, D972). The STATUS fields above stay
|
|
69189
|
+
* plain grams — they are not readings in the kilogram base. */
|
|
69190
|
+
var gramsParam = gramsPortion.meta(paramMeta({
|
|
69191
|
+
quantity: "mass",
|
|
69192
|
+
unit: "g"
|
|
69193
|
+
}));
|
|
68352
69194
|
var PetFeederStatusSchema = object({
|
|
68353
69195
|
/** Food in the TANK (hopper), as the feeder's own sensor reports it
|
|
68354
69196
|
* (`state.food`). A coarse level code, not grams — `0` is empty. The
|
|
@@ -68356,9 +69198,10 @@ var PetFeederStatusSchema = object({
|
|
|
68356
69198
|
* combined reading; per-hopper levels live in `food1`/`food2`. Null when
|
|
68357
69199
|
* the device has not reported yet. */
|
|
68358
69200
|
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
68359
|
-
/** Food in the BOWL
|
|
68360
|
-
* feeder
|
|
68361
|
-
|
|
69201
|
+
/** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
|
|
69202
|
+
* feeder with a bowl scale; null when the model has none or its reading is
|
|
69203
|
+
* of an unproven unit. */
|
|
69204
|
+
bowlFood: number().nullable().meta(readingMeta("mass")),
|
|
68362
69205
|
/** True while the pet is eating (`state.eating`). */
|
|
68363
69206
|
eating: boolean(),
|
|
68364
69207
|
/** Whether `markFoodReplenished` does anything on this model. When false
|
|
@@ -68436,9 +69279,9 @@ var petFeederCapability = {
|
|
|
68436
69279
|
*/
|
|
68437
69280
|
feed: method(object({
|
|
68438
69281
|
deviceId: number().int().nonnegative(),
|
|
68439
|
-
grams:
|
|
68440
|
-
hopper1:
|
|
68441
|
-
hopper2:
|
|
69282
|
+
grams: gramsParam.optional(),
|
|
69283
|
+
hopper1: gramsParam.optional(),
|
|
69284
|
+
hopper2: gramsParam.optional()
|
|
68442
69285
|
}), _void(), {
|
|
68443
69286
|
kind: "mutation",
|
|
68444
69287
|
auth: "protected"
|
|
@@ -68542,6 +69385,137 @@ var petFeederCapability = {
|
|
|
68542
69385
|
}
|
|
68543
69386
|
} }
|
|
68544
69387
|
};
|
|
69388
|
+
/**
|
|
69389
|
+
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
69390
|
+
* circulating drinking fountain: how much water is in the tank, how much the
|
|
69391
|
+
* pet drank, whether the pump runs and on what duty cycle.
|
|
69392
|
+
*
|
|
69393
|
+
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
69394
|
+
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
69395
|
+
* changed" button), and an operator BUILDS the device from them — a composed
|
|
69396
|
+
* device of this type (D659, D967). Every method is therefore a forward to ONE
|
|
69397
|
+
* method of ONE source entity, and every reading is a field derived from a
|
|
69398
|
+
* source entity; the provider-shaped interface below exists so the composed
|
|
69399
|
+
* device and any future native provider answer the same contract.
|
|
69400
|
+
*
|
|
69401
|
+
* Quantities are stored in the base unit of their kind (D944): volumes in
|
|
69402
|
+
* LITRES, durations in SECONDS, the tank level in percent. A source that
|
|
69403
|
+
* reports millilitres or minutes is converted in the composition's expression,
|
|
69404
|
+
* never here. Every reading may be `null`: a source that is offline or does
|
|
69405
|
+
* not report is unknown, never zero.
|
|
69406
|
+
*
|
|
69407
|
+
* The filter is not a field of this cap: it is an item of the `consumables`
|
|
69408
|
+
* cap on the same device (kind `wear`), reset by the same forward mechanism.
|
|
69409
|
+
*/
|
|
69410
|
+
var PetFountainStatusSchema = object({
|
|
69411
|
+
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
69412
|
+
waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
69413
|
+
/** Water in the tank, in litres. Null while the source has not reported. */
|
|
69414
|
+
waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69415
|
+
/** True when the tank is empty. Null while unknown. */
|
|
69416
|
+
empty: boolean().nullable(),
|
|
69417
|
+
/** True while the pump is running. Null while unknown. */
|
|
69418
|
+
pumpRunning: boolean().nullable(),
|
|
69419
|
+
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
69420
|
+
pumpIntervalEnabled: boolean().nullable(),
|
|
69421
|
+
/** How long the pump runs per cycle, in seconds. */
|
|
69422
|
+
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69423
|
+
/** How long the pump rests per cycle, in seconds. */
|
|
69424
|
+
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69425
|
+
/** Water drunk in the last drink, in litres. */
|
|
69426
|
+
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69427
|
+
/** Water drunk in the current hour, in litres. */
|
|
69428
|
+
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69429
|
+
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
69430
|
+
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69431
|
+
/** Water drunk since local midnight, in litres. */
|
|
69432
|
+
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69433
|
+
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
69434
|
+
error: string().nullable(),
|
|
69435
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
69436
|
+
lastFetchedAt: number()
|
|
69437
|
+
});
|
|
69438
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
69439
|
+
var MinutesSchema = number().int().min(0);
|
|
69440
|
+
var petFountainCapability = {
|
|
69441
|
+
name: "pet-fountain",
|
|
69442
|
+
scope: "device",
|
|
69443
|
+
deviceNative: true,
|
|
69444
|
+
mode: "singleton",
|
|
69445
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
69446
|
+
methods: {
|
|
69447
|
+
/** Start or stop the pump. */
|
|
69448
|
+
setPump: method(object({
|
|
69449
|
+
deviceId: DeviceIdSchema,
|
|
69450
|
+
on: boolean()
|
|
69451
|
+
}), _void(), {
|
|
69452
|
+
kind: "mutation",
|
|
69453
|
+
auth: "protected"
|
|
69454
|
+
}),
|
|
69455
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
69456
|
+
setPumpInterval: method(object({
|
|
69457
|
+
deviceId: DeviceIdSchema,
|
|
69458
|
+
on: boolean()
|
|
69459
|
+
}), _void(), {
|
|
69460
|
+
kind: "mutation",
|
|
69461
|
+
auth: "protected"
|
|
69462
|
+
}),
|
|
69463
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
69464
|
+
setPumpOnMinutes: method(object({
|
|
69465
|
+
deviceId: DeviceIdSchema,
|
|
69466
|
+
minutes: MinutesSchema
|
|
69467
|
+
}), _void(), {
|
|
69468
|
+
kind: "mutation",
|
|
69469
|
+
auth: "protected"
|
|
69470
|
+
}),
|
|
69471
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
69472
|
+
setPumpOffMinutes: method(object({
|
|
69473
|
+
deviceId: DeviceIdSchema,
|
|
69474
|
+
minutes: MinutesSchema
|
|
69475
|
+
}), _void(), {
|
|
69476
|
+
kind: "mutation",
|
|
69477
|
+
auth: "protected"
|
|
69478
|
+
}),
|
|
69479
|
+
/** Clear the rolling daily consumption counters on the device. */
|
|
69480
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
69481
|
+
kind: "mutation",
|
|
69482
|
+
auth: "protected"
|
|
69483
|
+
})
|
|
69484
|
+
},
|
|
69485
|
+
status: {
|
|
69486
|
+
schema: PetFountainStatusSchema,
|
|
69487
|
+
kind: "push"
|
|
69488
|
+
},
|
|
69489
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
69490
|
+
runtimeState: PetFountainStatusSchema,
|
|
69491
|
+
/**
|
|
69492
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
69493
|
+
*
|
|
69494
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
69495
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
69496
|
+
*/
|
|
69497
|
+
durability: "session",
|
|
69498
|
+
/** History (D850): recorded series. */
|
|
69499
|
+
history: { fields: {
|
|
69500
|
+
waterLevel: {
|
|
69501
|
+
kind: "gauge",
|
|
69502
|
+
unit: "%",
|
|
69503
|
+
deadband: 1
|
|
69504
|
+
},
|
|
69505
|
+
waterVolume: {
|
|
69506
|
+
kind: "gauge",
|
|
69507
|
+
unit: "L"
|
|
69508
|
+
},
|
|
69509
|
+
todayDrink: {
|
|
69510
|
+
kind: "gauge",
|
|
69511
|
+
unit: "L"
|
|
69512
|
+
},
|
|
69513
|
+
last24hDrink: {
|
|
69514
|
+
kind: "gauge",
|
|
69515
|
+
unit: "L"
|
|
69516
|
+
}
|
|
69517
|
+
} }
|
|
69518
|
+
};
|
|
68545
69519
|
var VehicleSchema = object({
|
|
68546
69520
|
id: string(),
|
|
68547
69521
|
name: string(),
|
|
@@ -74539,6 +75513,87 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
74539
75513
|
kind: "mutation",
|
|
74540
75514
|
auth: "admin"
|
|
74541
75515
|
});
|
|
75516
|
+
var btn = (idSuffix, row, col, init) => ({
|
|
75517
|
+
idSuffix,
|
|
75518
|
+
row,
|
|
75519
|
+
col,
|
|
75520
|
+
rowSpan: 1,
|
|
75521
|
+
colSpan: 1,
|
|
75522
|
+
...init
|
|
75523
|
+
});
|
|
75524
|
+
function rocker(id, name, it, frame, title, buttons, size) {
|
|
75525
|
+
return {
|
|
75526
|
+
id,
|
|
75527
|
+
name,
|
|
75528
|
+
nameI18n: {
|
|
75529
|
+
en: name,
|
|
75530
|
+
it
|
|
75531
|
+
},
|
|
75532
|
+
icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
|
|
75533
|
+
category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
|
|
75534
|
+
size,
|
|
75535
|
+
group: title === null ? { frame } : {
|
|
75536
|
+
frame,
|
|
75537
|
+
title
|
|
75538
|
+
},
|
|
75539
|
+
buttons
|
|
75540
|
+
};
|
|
75541
|
+
}
|
|
75542
|
+
btn("power", 0, 0, {
|
|
75543
|
+
role: "power",
|
|
75544
|
+
shape: "round",
|
|
75545
|
+
tone: "danger"
|
|
75546
|
+
}), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
|
|
75547
|
+
role: "ok",
|
|
75548
|
+
shape: "round",
|
|
75549
|
+
tone: "accent"
|
|
75550
|
+
}), 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, {
|
|
75551
|
+
role: "volume_up",
|
|
75552
|
+
shape: "pill"
|
|
75553
|
+
}), btn("down", 1, 0, {
|
|
75554
|
+
role: "volume_down",
|
|
75555
|
+
shape: "pill"
|
|
75556
|
+
})], {
|
|
75557
|
+
rows: 2,
|
|
75558
|
+
cols: 1
|
|
75559
|
+
}), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
|
|
75560
|
+
role: "channel_up",
|
|
75561
|
+
shape: "pill"
|
|
75562
|
+
}), btn("down", 1, 0, {
|
|
75563
|
+
role: "channel_down",
|
|
75564
|
+
shape: "pill"
|
|
75565
|
+
})], {
|
|
75566
|
+
rows: 2,
|
|
75567
|
+
cols: 1
|
|
75568
|
+
}), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
|
|
75569
|
+
role: "custom:minus",
|
|
75570
|
+
label: "−",
|
|
75571
|
+
shape: "pill"
|
|
75572
|
+
}), btn("plus", 0, 1, {
|
|
75573
|
+
role: "custom:plus",
|
|
75574
|
+
label: "+",
|
|
75575
|
+
shape: "pill"
|
|
75576
|
+
})], {
|
|
75577
|
+
rows: 1,
|
|
75578
|
+
cols: 2
|
|
75579
|
+
}), [...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, {
|
|
75580
|
+
role: "custom:color_red",
|
|
75581
|
+
tone: "red"
|
|
75582
|
+
}), btn("green", 0, 1, {
|
|
75583
|
+
role: "custom:color_green",
|
|
75584
|
+
tone: "green"
|
|
75585
|
+
}), btn("yellow", 0, 2, {
|
|
75586
|
+
role: "custom:color_yellow",
|
|
75587
|
+
tone: "yellow"
|
|
75588
|
+
}), btn("blue", 0, 3, {
|
|
75589
|
+
role: "custom:color_blue",
|
|
75590
|
+
tone: "blue"
|
|
75591
|
+
}), 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" }), [
|
|
75592
|
+
1,
|
|
75593
|
+
2,
|
|
75594
|
+
3,
|
|
75595
|
+
4
|
|
75596
|
+
].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" });
|
|
74542
75597
|
discriminatedUnion("kind", [object({
|
|
74543
75598
|
kind: literal("raw"),
|
|
74544
75599
|
timings: array(number().int()),
|
|
@@ -74549,7 +75604,7 @@ discriminatedUnion("kind", [object({
|
|
|
74549
75604
|
})]);
|
|
74550
75605
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
74551
75606
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
74552
|
-
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;
|
|
75607
|
+
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;
|
|
74553
75608
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
74554
75609
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
74555
75610
|
new Set(CAP_NAMES);
|
|
@@ -74953,6 +76008,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
74953
76008
|
notifier: notifierCapability,
|
|
74954
76009
|
numericSensor: numericSensorCapability,
|
|
74955
76010
|
petFeeder: petFeederCapability,
|
|
76011
|
+
petFountain: petFountainCapability,
|
|
74956
76012
|
powerMeter: powerMeterCapability,
|
|
74957
76013
|
presence: presenceCapability,
|
|
74958
76014
|
pressureSensor: pressureSensorCapability,
|
|
@@ -75720,7 +76776,7 @@ var DeclaredDevices = class {
|
|
|
75720
76776
|
return removed;
|
|
75721
76777
|
}
|
|
75722
76778
|
};
|
|
75723
|
-
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;
|
|
76779
|
+
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;
|
|
75724
76780
|
new Set(Object.values(DeviceType));
|
|
75725
76781
|
Object.freeze({
|
|
75726
76782
|
"accessories.getStatus": {
|
|
@@ -77655,6 +78711,13 @@ Object.freeze({
|
|
|
77655
78711
|
access: "create",
|
|
77656
78712
|
role: "admin"
|
|
77657
78713
|
},
|
|
78714
|
+
"deviceExport.exposeEntity": {
|
|
78715
|
+
capName: "device-export",
|
|
78716
|
+
capScope: "system",
|
|
78717
|
+
addonId: null,
|
|
78718
|
+
access: "create",
|
|
78719
|
+
role: "admin"
|
|
78720
|
+
},
|
|
77658
78721
|
"deviceExport.getDeviceLiveContribution": {
|
|
77659
78722
|
capName: "device-export",
|
|
77660
78723
|
capScope: "system",
|
|
@@ -77683,6 +78746,13 @@ Object.freeze({
|
|
|
77683
78746
|
access: "view",
|
|
77684
78747
|
role: "admin"
|
|
77685
78748
|
},
|
|
78749
|
+
"deviceExport.listExposedEntities": {
|
|
78750
|
+
capName: "device-export",
|
|
78751
|
+
capScope: "system",
|
|
78752
|
+
addonId: null,
|
|
78753
|
+
access: "view",
|
|
78754
|
+
role: "admin"
|
|
78755
|
+
},
|
|
77686
78756
|
"deviceExport.listSupportedDeviceKinds": {
|
|
77687
78757
|
capName: "device-export",
|
|
77688
78758
|
capScope: "system",
|
|
@@ -77690,6 +78760,13 @@ Object.freeze({
|
|
|
77690
78760
|
access: "view",
|
|
77691
78761
|
role: "admin"
|
|
77692
78762
|
},
|
|
78763
|
+
"deviceExport.listSupportedEntityKinds": {
|
|
78764
|
+
capName: "device-export",
|
|
78765
|
+
capScope: "system",
|
|
78766
|
+
addonId: null,
|
|
78767
|
+
access: "view",
|
|
78768
|
+
role: "admin"
|
|
78769
|
+
},
|
|
77693
78770
|
"deviceExport.unexposeDevice": {
|
|
77694
78771
|
capName: "device-export",
|
|
77695
78772
|
capScope: "system",
|
|
@@ -77697,6 +78774,13 @@ Object.freeze({
|
|
|
77697
78774
|
access: "create",
|
|
77698
78775
|
role: "admin"
|
|
77699
78776
|
},
|
|
78777
|
+
"deviceExport.unexposeEntity": {
|
|
78778
|
+
capName: "device-export",
|
|
78779
|
+
capScope: "system",
|
|
78780
|
+
addonId: null,
|
|
78781
|
+
access: "create",
|
|
78782
|
+
role: "admin"
|
|
78783
|
+
},
|
|
77700
78784
|
"deviceExtension.listCandidates": {
|
|
77701
78785
|
capName: "device-extension",
|
|
77702
78786
|
capScope: "device",
|
|
@@ -81162,6 +82246,48 @@ Object.freeze({
|
|
|
81162
82246
|
access: "create",
|
|
81163
82247
|
role: "viewer"
|
|
81164
82248
|
},
|
|
82249
|
+
"petFountain.getStatus": {
|
|
82250
|
+
capName: "pet-fountain",
|
|
82251
|
+
capScope: "device",
|
|
82252
|
+
addonId: null,
|
|
82253
|
+
access: "view",
|
|
82254
|
+
role: "viewer"
|
|
82255
|
+
},
|
|
82256
|
+
"petFountain.resetDailyConsumption": {
|
|
82257
|
+
capName: "pet-fountain",
|
|
82258
|
+
capScope: "device",
|
|
82259
|
+
addonId: null,
|
|
82260
|
+
access: "delete",
|
|
82261
|
+
role: "viewer"
|
|
82262
|
+
},
|
|
82263
|
+
"petFountain.setPump": {
|
|
82264
|
+
capName: "pet-fountain",
|
|
82265
|
+
capScope: "device",
|
|
82266
|
+
addonId: null,
|
|
82267
|
+
access: "create",
|
|
82268
|
+
role: "viewer"
|
|
82269
|
+
},
|
|
82270
|
+
"petFountain.setPumpInterval": {
|
|
82271
|
+
capName: "pet-fountain",
|
|
82272
|
+
capScope: "device",
|
|
82273
|
+
addonId: null,
|
|
82274
|
+
access: "create",
|
|
82275
|
+
role: "viewer"
|
|
82276
|
+
},
|
|
82277
|
+
"petFountain.setPumpOffMinutes": {
|
|
82278
|
+
capName: "pet-fountain",
|
|
82279
|
+
capScope: "device",
|
|
82280
|
+
addonId: null,
|
|
82281
|
+
access: "create",
|
|
82282
|
+
role: "viewer"
|
|
82283
|
+
},
|
|
82284
|
+
"petFountain.setPumpOnMinutes": {
|
|
82285
|
+
capName: "pet-fountain",
|
|
82286
|
+
capScope: "device",
|
|
82287
|
+
addonId: null,
|
|
82288
|
+
access: "create",
|
|
82289
|
+
role: "viewer"
|
|
82290
|
+
},
|
|
81165
82291
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
81166
82292
|
capName: "pipeline-analytics",
|
|
81167
82293
|
capScope: "device",
|
|
@@ -83451,6 +84577,13 @@ Object.freeze({
|
|
|
83451
84577
|
access: "create",
|
|
83452
84578
|
role: "admin"
|
|
83453
84579
|
},
|
|
84580
|
+
"remoteLayoutComponents.listComponents": {
|
|
84581
|
+
capName: "remote-layout-components",
|
|
84582
|
+
capScope: "system",
|
|
84583
|
+
addonId: null,
|
|
84584
|
+
access: "view",
|
|
84585
|
+
role: "admin"
|
|
84586
|
+
},
|
|
83454
84587
|
"runningState.getStatus": {
|
|
83455
84588
|
capName: "running-state",
|
|
83456
84589
|
capScope: "device",
|
|
@@ -83563,6 +84696,13 @@ Object.freeze({
|
|
|
83563
84696
|
access: "create",
|
|
83564
84697
|
role: "admin"
|
|
83565
84698
|
},
|
|
84699
|
+
"serverManagement.getLaunchOverrides": {
|
|
84700
|
+
capName: "server-management",
|
|
84701
|
+
capScope: "system",
|
|
84702
|
+
addonId: null,
|
|
84703
|
+
access: "view",
|
|
84704
|
+
role: "admin"
|
|
84705
|
+
},
|
|
83566
84706
|
"serverManagement.getServerPackageStatus": {
|
|
83567
84707
|
capName: "server-management",
|
|
83568
84708
|
capScope: "system",
|
|
@@ -83570,6 +84710,13 @@ Object.freeze({
|
|
|
83570
84710
|
access: "view",
|
|
83571
84711
|
role: "admin"
|
|
83572
84712
|
},
|
|
84713
|
+
"serverManagement.installDesktopUpdate": {
|
|
84714
|
+
capName: "server-management",
|
|
84715
|
+
capScope: "system",
|
|
84716
|
+
addonId: null,
|
|
84717
|
+
access: "create",
|
|
84718
|
+
role: "admin"
|
|
84719
|
+
},
|
|
83573
84720
|
"serverManagement.restartServer": {
|
|
83574
84721
|
capName: "server-management",
|
|
83575
84722
|
capScope: "system",
|
|
@@ -83584,6 +84731,13 @@ Object.freeze({
|
|
|
83584
84731
|
access: "create",
|
|
83585
84732
|
role: "admin"
|
|
83586
84733
|
},
|
|
84734
|
+
"serverManagement.setLaunchOverrides": {
|
|
84735
|
+
capName: "server-management",
|
|
84736
|
+
capScope: "system",
|
|
84737
|
+
addonId: null,
|
|
84738
|
+
access: "create",
|
|
84739
|
+
role: "admin"
|
|
84740
|
+
},
|
|
83587
84741
|
"settingsStore.aggregate": {
|
|
83588
84742
|
capName: "settings-store",
|
|
83589
84743
|
capScope: "system",
|
|
@@ -87473,7 +88627,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87473
88627
|
min: 4,
|
|
87474
88628
|
max: 200,
|
|
87475
88629
|
step: 1,
|
|
87476
|
-
unit: "g"
|
|
88630
|
+
unit: "g",
|
|
88631
|
+
quantity: "mass"
|
|
87477
88632
|
},
|
|
87478
88633
|
optional: true
|
|
87479
88634
|
}]
|
|
@@ -87492,7 +88647,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87492
88647
|
min: 4,
|
|
87493
88648
|
max: 200,
|
|
87494
88649
|
step: 1,
|
|
87495
|
-
unit: "g"
|
|
88650
|
+
unit: "g",
|
|
88651
|
+
quantity: "mass"
|
|
87496
88652
|
},
|
|
87497
88653
|
optional: true
|
|
87498
88654
|
}, {
|
|
@@ -87503,7 +88659,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87503
88659
|
min: 4,
|
|
87504
88660
|
max: 200,
|
|
87505
88661
|
step: 1,
|
|
87506
|
-
unit: "g"
|
|
88662
|
+
unit: "g",
|
|
88663
|
+
quantity: "mass"
|
|
87507
88664
|
},
|
|
87508
88665
|
optional: true
|
|
87509
88666
|
}]
|
|
@@ -87644,6 +88801,100 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87644
88801
|
}
|
|
87645
88802
|
]
|
|
87646
88803
|
},
|
|
88804
|
+
"pet-fountain": {
|
|
88805
|
+
scope: "device",
|
|
88806
|
+
noun: t("Pet fountain", "Fontana per animali"),
|
|
88807
|
+
actions: [
|
|
88808
|
+
{
|
|
88809
|
+
id: "petFountain.pumpOn",
|
|
88810
|
+
method: "setPump",
|
|
88811
|
+
label: t("Pump on", "Pompa accesa"),
|
|
88812
|
+
phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
|
|
88813
|
+
icon: "play",
|
|
88814
|
+
args: { on: true }
|
|
88815
|
+
},
|
|
88816
|
+
{
|
|
88817
|
+
id: "petFountain.pumpOff",
|
|
88818
|
+
method: "setPump",
|
|
88819
|
+
label: t("Pump off", "Pompa spenta"),
|
|
88820
|
+
phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
|
|
88821
|
+
icon: "square",
|
|
88822
|
+
args: { on: false }
|
|
88823
|
+
},
|
|
88824
|
+
{
|
|
88825
|
+
id: "petFountain.intervalOn",
|
|
88826
|
+
method: "setPumpInterval",
|
|
88827
|
+
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
88828
|
+
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
88829
|
+
icon: "timer",
|
|
88830
|
+
args: { on: true }
|
|
88831
|
+
},
|
|
88832
|
+
{
|
|
88833
|
+
id: "petFountain.intervalOff",
|
|
88834
|
+
method: "setPumpInterval",
|
|
88835
|
+
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
88836
|
+
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
88837
|
+
icon: "timer-off",
|
|
88838
|
+
args: { on: false }
|
|
88839
|
+
},
|
|
88840
|
+
{
|
|
88841
|
+
id: "petFountain.pumpOnMinutes",
|
|
88842
|
+
method: "setPumpOnMinutes",
|
|
88843
|
+
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
88844
|
+
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
88845
|
+
icon: "timer",
|
|
88846
|
+
openArgs: [{
|
|
88847
|
+
key: "minutes",
|
|
88848
|
+
label: t("Minutes", "Minuti"),
|
|
88849
|
+
offer: {
|
|
88850
|
+
kind: "number",
|
|
88851
|
+
min: 0,
|
|
88852
|
+
step: 1,
|
|
88853
|
+
unit: "min"
|
|
88854
|
+
}
|
|
88855
|
+
}]
|
|
88856
|
+
},
|
|
88857
|
+
{
|
|
88858
|
+
id: "petFountain.pumpOffMinutes",
|
|
88859
|
+
method: "setPumpOffMinutes",
|
|
88860
|
+
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
88861
|
+
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
88862
|
+
icon: "timer-off",
|
|
88863
|
+
openArgs: [{
|
|
88864
|
+
key: "minutes",
|
|
88865
|
+
label: t("Minutes", "Minuti"),
|
|
88866
|
+
offer: {
|
|
88867
|
+
kind: "number",
|
|
88868
|
+
min: 0,
|
|
88869
|
+
step: 1,
|
|
88870
|
+
unit: "min"
|
|
88871
|
+
}
|
|
88872
|
+
}]
|
|
88873
|
+
},
|
|
88874
|
+
{
|
|
88875
|
+
id: "petFountain.resetDailyConsumption",
|
|
88876
|
+
method: "resetDailyConsumption",
|
|
88877
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
88878
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
88879
|
+
icon: "rotate-ccw"
|
|
88880
|
+
}
|
|
88881
|
+
],
|
|
88882
|
+
state: [{
|
|
88883
|
+
kind: "boolean",
|
|
88884
|
+
field: "empty",
|
|
88885
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
88886
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
88887
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
88888
|
+
icon: "droplet"
|
|
88889
|
+
}, {
|
|
88890
|
+
kind: "numeric",
|
|
88891
|
+
field: "waterLevel",
|
|
88892
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
88893
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
88894
|
+
unit: "%",
|
|
88895
|
+
icon: "droplets"
|
|
88896
|
+
}]
|
|
88897
|
+
},
|
|
87647
88898
|
"media-player": {
|
|
87648
88899
|
scope: "device",
|
|
87649
88900
|
noun: t("Media player", "Lettore multimediale"),
|
|
@@ -89509,6 +90760,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
89509
90760
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
89510
90761
|
records: system(INTERNAL),
|
|
89511
90762
|
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
90763
|
+
"remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
|
|
89512
90764
|
"server-management": system(CLUSTER),
|
|
89513
90765
|
"settings-store": system(INTERNAL),
|
|
89514
90766
|
"smtp-provider": system(INTERNAL),
|
|
@@ -89581,7 +90833,7 @@ function projectAction(cap, action) {
|
|
|
89581
90833
|
};
|
|
89582
90834
|
}
|
|
89583
90835
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
89584
|
-
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;
|
|
90836
|
+
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;
|
|
89585
90837
|
DeviceFeature.BatteryOperated;
|
|
89586
90838
|
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
89587
90839
|
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.";
|
|
@@ -91132,6 +92384,36 @@ Object.freeze({
|
|
|
91132
92384
|
form: "single",
|
|
91133
92385
|
optional: false
|
|
91134
92386
|
}],
|
|
92387
|
+
"petFountain.getStatus": [{
|
|
92388
|
+
name: "deviceId",
|
|
92389
|
+
form: "single",
|
|
92390
|
+
optional: false
|
|
92391
|
+
}],
|
|
92392
|
+
"petFountain.resetDailyConsumption": [{
|
|
92393
|
+
name: "deviceId",
|
|
92394
|
+
form: "single",
|
|
92395
|
+
optional: false
|
|
92396
|
+
}],
|
|
92397
|
+
"petFountain.setPump": [{
|
|
92398
|
+
name: "deviceId",
|
|
92399
|
+
form: "single",
|
|
92400
|
+
optional: false
|
|
92401
|
+
}],
|
|
92402
|
+
"petFountain.setPumpInterval": [{
|
|
92403
|
+
name: "deviceId",
|
|
92404
|
+
form: "single",
|
|
92405
|
+
optional: false
|
|
92406
|
+
}],
|
|
92407
|
+
"petFountain.setPumpOffMinutes": [{
|
|
92408
|
+
name: "deviceId",
|
|
92409
|
+
form: "single",
|
|
92410
|
+
optional: false
|
|
92411
|
+
}],
|
|
92412
|
+
"petFountain.setPumpOnMinutes": [{
|
|
92413
|
+
name: "deviceId",
|
|
92414
|
+
form: "single",
|
|
92415
|
+
optional: false
|
|
92416
|
+
}],
|
|
91135
92417
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
91136
92418
|
name: "deviceId",
|
|
91137
92419
|
form: "single",
|
|
@@ -95528,8 +96810,6 @@ var NC_TEMPLATE_VARS = [
|
|
|
95528
96810
|
* delete it from here then, and the served catalog names it again.
|
|
95529
96811
|
*/
|
|
95530
96812
|
var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
|
|
95531
|
-
"node-restarting",
|
|
95532
|
-
"node-updating",
|
|
95533
96813
|
"alert-critical",
|
|
95534
96814
|
"alert-error",
|
|
95535
96815
|
"alert-critical-resolved",
|