@camstack/addon-agent-ui 1.2.333 → 1.2.335
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/.bundle-modules.json +33 -35
- package/dist/addon.js +1122 -1079
- package/dist/assets/{MotionZonesSettings-BveIjuoy.js → MotionZonesSettings-CWCXCBzB.js} +1 -1
- package/dist/assets/{PrivacyMaskSettings-DAF5F2AZ.js → PrivacyMaskSettings-Dyhdp0pj.js} +1 -1
- package/dist/assets/{SceneMonitorEditor-FxKxdbql.js → SceneMonitorEditor-BID-NcQd.js} +1 -1
- package/dist/assets/index-C9Oits7V.js +145 -0
- package/dist/assets/index-DghLQ7nz.css +2 -0
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/assets/index-DMfn1qze.js +0 -145
- package/dist/assets/index-OtI9eyBk.css +0 -2
package/dist/addon.js
CHANGED
|
@@ -5849,7 +5849,7 @@ var ZodIssueCode = {
|
|
|
5849
5849
|
var ZodFirstPartyTypeKind;
|
|
5850
5850
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5851
5851
|
//#endregion
|
|
5852
|
-
//#region ../types/dist/sleep-
|
|
5852
|
+
//#region ../types/dist/sleep-CdKka2ri.mjs
|
|
5853
5853
|
/**
|
|
5854
5854
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5855
5855
|
* window to float samples (D455).
|
|
@@ -6526,6 +6526,250 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
|
6526
6526
|
blockOwner
|
|
6527
6527
|
};
|
|
6528
6528
|
}
|
|
6529
|
+
/** The record calls the one-switch migration makes, over the migration API. */
|
|
6530
|
+
function switchRecordsOverMigrationApi(api) {
|
|
6531
|
+
return { kind: (definition) => {
|
|
6532
|
+
const handle = api.records.kind(definition);
|
|
6533
|
+
return {
|
|
6534
|
+
get: (id) => handle.get({ id }),
|
|
6535
|
+
put: (input, opts) => handle.put({
|
|
6536
|
+
...input,
|
|
6537
|
+
...opts?.ifRev === void 0 ? {} : { ifRev: opts.ifRev }
|
|
6538
|
+
}),
|
|
6539
|
+
patch: (id, partial) => handle.patch({
|
|
6540
|
+
id,
|
|
6541
|
+
partial
|
|
6542
|
+
})
|
|
6543
|
+
};
|
|
6544
|
+
} };
|
|
6545
|
+
}
|
|
6546
|
+
function integrationRowsApiOver(api) {
|
|
6547
|
+
return {
|
|
6548
|
+
list: async () => (await api.integrations.list({})).integrations,
|
|
6549
|
+
readLegacyIntegrationRows: (input) => api.legacyRows.read(input),
|
|
6550
|
+
clearLegacyIntegrationRows: (input) => api.legacyRows.clear(input)
|
|
6551
|
+
};
|
|
6552
|
+
}
|
|
6553
|
+
object({
|
|
6554
|
+
cap: string().min(1),
|
|
6555
|
+
kind: string().min(1).optional()
|
|
6556
|
+
});
|
|
6557
|
+
var GLOBAL_SCOPE = { kind: "global" };
|
|
6558
|
+
/** The scope every addon-wide (not per-node, not per-device) setting lives in. */
|
|
6559
|
+
function globalSecretScope() {
|
|
6560
|
+
return GLOBAL_SCOPE;
|
|
6561
|
+
}
|
|
6562
|
+
function encodeSegment(value) {
|
|
6563
|
+
return encodeURIComponent(value);
|
|
6564
|
+
}
|
|
6565
|
+
function decodeSegment(value) {
|
|
6566
|
+
try {
|
|
6567
|
+
return decodeURIComponent(value);
|
|
6568
|
+
} catch {
|
|
6569
|
+
return null;
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
/**
|
|
6573
|
+
* The field path of a value inside an array item, addressed by the item's id:
|
|
6574
|
+
* `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
|
|
6575
|
+
* `providers[authentik].clientSecret`.
|
|
6576
|
+
*/
|
|
6577
|
+
function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
|
|
6578
|
+
return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
|
|
6579
|
+
}
|
|
6580
|
+
function parseScope(segments) {
|
|
6581
|
+
switch (segments[0]) {
|
|
6582
|
+
case "global": return {
|
|
6583
|
+
scope: GLOBAL_SCOPE,
|
|
6584
|
+
used: 1
|
|
6585
|
+
};
|
|
6586
|
+
case "node": {
|
|
6587
|
+
const nodeId = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
|
|
6588
|
+
return nodeId === null || nodeId.length === 0 ? null : {
|
|
6589
|
+
scope: {
|
|
6590
|
+
kind: "node",
|
|
6591
|
+
nodeId
|
|
6592
|
+
},
|
|
6593
|
+
used: 2
|
|
6594
|
+
};
|
|
6595
|
+
}
|
|
6596
|
+
case "device": {
|
|
6597
|
+
const raw = segments[1];
|
|
6598
|
+
if (raw === void 0 || !/^\d+$/.test(raw)) return null;
|
|
6599
|
+
return {
|
|
6600
|
+
scope: {
|
|
6601
|
+
kind: "device",
|
|
6602
|
+
deviceId: Number(raw)
|
|
6603
|
+
},
|
|
6604
|
+
used: 2
|
|
6605
|
+
};
|
|
6606
|
+
}
|
|
6607
|
+
case "integration": {
|
|
6608
|
+
const id = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
|
|
6609
|
+
return id === null || id.length === 0 ? null : {
|
|
6610
|
+
scope: {
|
|
6611
|
+
kind: "integration",
|
|
6612
|
+
integrationId: id
|
|
6613
|
+
},
|
|
6614
|
+
used: 2
|
|
6615
|
+
};
|
|
6616
|
+
}
|
|
6617
|
+
case "row": {
|
|
6618
|
+
const collection = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
|
|
6619
|
+
const rowId = segments[2] !== void 0 ? decodeSegment(segments[2]) : null;
|
|
6620
|
+
if (collection === null || rowId === null || collection.length === 0 || rowId.length === 0) return null;
|
|
6621
|
+
return {
|
|
6622
|
+
scope: {
|
|
6623
|
+
kind: "row",
|
|
6624
|
+
collection,
|
|
6625
|
+
rowId
|
|
6626
|
+
},
|
|
6627
|
+
used: 3
|
|
6628
|
+
};
|
|
6629
|
+
}
|
|
6630
|
+
default: return null;
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
/** Take a ref apart; `null` when it is not a well-formed ref. */
|
|
6634
|
+
function parseSecretRef(ref) {
|
|
6635
|
+
if (!ref.startsWith("secret://")) return null;
|
|
6636
|
+
const segments = ref.slice(9).split("/");
|
|
6637
|
+
const owner = segments[0] !== void 0 ? decodeSegment(segments[0]) : null;
|
|
6638
|
+
if (owner === null || owner.length === 0) return null;
|
|
6639
|
+
const parsed = parseScope(segments.slice(1));
|
|
6640
|
+
if (parsed === null) return null;
|
|
6641
|
+
const fieldPath = segments.slice(1 + parsed.used).join("/");
|
|
6642
|
+
if (fieldPath.length === 0) return null;
|
|
6643
|
+
return {
|
|
6644
|
+
owner,
|
|
6645
|
+
scope: parsed.scope,
|
|
6646
|
+
fieldPath
|
|
6647
|
+
};
|
|
6648
|
+
}
|
|
6649
|
+
/** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
|
|
6650
|
+
/** The files a kind may live in. `history` is device-history's (D850), never records. */
|
|
6651
|
+
var RECORD_DATABASES = ["settings", "events"];
|
|
6652
|
+
/** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
|
|
6653
|
+
var RECORD_CLASSES = [
|
|
6654
|
+
"config",
|
|
6655
|
+
"ledger",
|
|
6656
|
+
"audit",
|
|
6657
|
+
"mirror",
|
|
6658
|
+
"ephemeral"
|
|
6659
|
+
];
|
|
6660
|
+
var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
|
|
6661
|
+
var RECORD_MAX_DATA_BYTES = 1024 * 1024;
|
|
6662
|
+
var RECORD_BATCH_MAX = 1e3;
|
|
6663
|
+
var RECORD_QUERY_MAX = 1e3;
|
|
6664
|
+
/** The body key of the switch; only {@link RecordIntegrationInstances.setInstanceEnabled} writes it. */
|
|
6665
|
+
var SWITCH_KEY = "enabled";
|
|
6666
|
+
/**
|
|
6667
|
+
* The one-switch migration (epic #440, step 3). An integration's on/off state used to live in the
|
|
6668
|
+
* registry's `enabled` column for a device addon; it now lives only in the instance record's
|
|
6669
|
+
* body (`enabled`, D1007 superseded). This moves a registry `enabled = false` into the body, once.
|
|
6670
|
+
*
|
|
6671
|
+
* For every registry row of the addon that is OFF:
|
|
6672
|
+
* - no record under its id: the record is created from `initialBody(name)` with `enabled: false`
|
|
6673
|
+
* (a default integration that never had settings, Reolink's among them);
|
|
6674
|
+
* - a record whose body states no `enabled`: `enabled: false` is written into it;
|
|
6675
|
+
* - a record that already states `enabled` (either way) is the newer truth and is left alone.
|
|
6676
|
+
* A row that is ON writes nothing: an absent field reads as on. A rerun finds every off row with
|
|
6677
|
+
* a stated switch and does nothing, so nothing switched off comes back on and nothing switched on
|
|
6678
|
+
* goes off. The registry column itself is neither read afterwards nor written; its drop is step 5.
|
|
6679
|
+
*
|
|
6680
|
+
* It runs in the addon process, BEFORE the addon registers its `integration-instances` provider,
|
|
6681
|
+
* which is also why `integrations.list` still answers the stored column at that point.
|
|
6682
|
+
*/
|
|
6683
|
+
function registrySwitchToBodyId(addonId, date) {
|
|
6684
|
+
return `${addonId}/${date}-registry-switch-to-body`;
|
|
6685
|
+
}
|
|
6686
|
+
async function moveRegistrySwitchToBody(ports, spec) {
|
|
6687
|
+
const rows = await ports.listIntegrations(spec.addonId);
|
|
6688
|
+
let created = 0;
|
|
6689
|
+
let switched = 0;
|
|
6690
|
+
let kept = 0;
|
|
6691
|
+
for (const row of rows) {
|
|
6692
|
+
if (row.enabled) continue;
|
|
6693
|
+
const body = await ports.readBody(row.id);
|
|
6694
|
+
if (body === null) {
|
|
6695
|
+
if (await ports.createRecord(row.id, {
|
|
6696
|
+
...spec.initialBody(row.name),
|
|
6697
|
+
["enabled"]: false
|
|
6698
|
+
}) === "created") created += 1;
|
|
6699
|
+
else kept += 1;
|
|
6700
|
+
continue;
|
|
6701
|
+
}
|
|
6702
|
+
if ("enabled" in body) {
|
|
6703
|
+
kept += 1;
|
|
6704
|
+
continue;
|
|
6705
|
+
}
|
|
6706
|
+
await ports.switchOff(row.id);
|
|
6707
|
+
switched += 1;
|
|
6708
|
+
}
|
|
6709
|
+
const summary = {
|
|
6710
|
+
integrations: rows.length,
|
|
6711
|
+
off: rows.filter((r) => !r.enabled).length,
|
|
6712
|
+
created,
|
|
6713
|
+
switched,
|
|
6714
|
+
kept
|
|
6715
|
+
};
|
|
6716
|
+
return created === 0 && switched === 0 ? {
|
|
6717
|
+
outcome: "already-done",
|
|
6718
|
+
summary
|
|
6719
|
+
} : {
|
|
6720
|
+
outcome: "done",
|
|
6721
|
+
summary
|
|
6722
|
+
};
|
|
6723
|
+
}
|
|
6724
|
+
/** A migration entry: an addon states its kind and how a new body starts, nothing else. */
|
|
6725
|
+
function registrySwitchToBodyMigration(spec) {
|
|
6726
|
+
return {
|
|
6727
|
+
meta: {
|
|
6728
|
+
id: registrySwitchToBodyId(spec.addonId, spec.date),
|
|
6729
|
+
owner: spec.addonId,
|
|
6730
|
+
level: "addon-state",
|
|
6731
|
+
seq: spec.seq,
|
|
6732
|
+
dependsOn: [],
|
|
6733
|
+
store: "settings",
|
|
6734
|
+
scope: "cluster",
|
|
6735
|
+
onFailure: "block-owner",
|
|
6736
|
+
bodyKind: "procedure",
|
|
6737
|
+
description: "Move the registry enabled flag of every switched-off integration into its instance record body, creating the record when there is none",
|
|
6738
|
+
fixtureMark: "enabled"
|
|
6739
|
+
},
|
|
6740
|
+
procedure: (ports) => moveRegistrySwitchToBody(ports, {
|
|
6741
|
+
addonId: spec.addonId,
|
|
6742
|
+
initialBody: spec.initialBody
|
|
6743
|
+
})
|
|
6744
|
+
};
|
|
6745
|
+
}
|
|
6746
|
+
/** The hub and records sides of {@link registrySwitchToBodyMigration}. */
|
|
6747
|
+
function registrySwitchPortsOver(deps) {
|
|
6748
|
+
const handle = deps.records.kind({
|
|
6749
|
+
name: deps.kind.name,
|
|
6750
|
+
declaration: deps.kind.declaration,
|
|
6751
|
+
schema: record(string(), unknown())
|
|
6752
|
+
});
|
|
6753
|
+
return {
|
|
6754
|
+
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
6755
|
+
id: i.id,
|
|
6756
|
+
name: i.name,
|
|
6757
|
+
enabled: i.enabled !== false
|
|
6758
|
+
})),
|
|
6759
|
+
readBody: async (id) => (await handle.get(id))?.data ?? null,
|
|
6760
|
+
createRecord: async (id, body) => {
|
|
6761
|
+
if (await handle.get(id) !== null) return "exists";
|
|
6762
|
+
await handle.put({
|
|
6763
|
+
id,
|
|
6764
|
+
data: body
|
|
6765
|
+
}, { ifRev: 0 });
|
|
6766
|
+
return "created";
|
|
6767
|
+
},
|
|
6768
|
+
switchOff: async (id) => {
|
|
6769
|
+
await handle.patch(id, { [SWITCH_KEY]: false });
|
|
6770
|
+
}
|
|
6771
|
+
};
|
|
6772
|
+
}
|
|
6529
6773
|
/**
|
|
6530
6774
|
* Build a {@link DurableState} over a single store key. Transport-agnostic:
|
|
6531
6775
|
* `read`/`write` are the addon-store or device-store accessors. The whole
|
|
@@ -6657,98 +6901,6 @@ var SCOPED_SETTINGS_SCOPES = [
|
|
|
6657
6901
|
"device-defaults",
|
|
6658
6902
|
"node"
|
|
6659
6903
|
];
|
|
6660
|
-
var GLOBAL_SCOPE = { kind: "global" };
|
|
6661
|
-
/** The scope every addon-wide (not per-node, not per-device) setting lives in. */
|
|
6662
|
-
function globalSecretScope() {
|
|
6663
|
-
return GLOBAL_SCOPE;
|
|
6664
|
-
}
|
|
6665
|
-
function encodeSegment(value) {
|
|
6666
|
-
return encodeURIComponent(value);
|
|
6667
|
-
}
|
|
6668
|
-
function decodeSegment(value) {
|
|
6669
|
-
try {
|
|
6670
|
-
return decodeURIComponent(value);
|
|
6671
|
-
} catch {
|
|
6672
|
-
return null;
|
|
6673
|
-
}
|
|
6674
|
-
}
|
|
6675
|
-
/**
|
|
6676
|
-
* The field path of a value inside an array item, addressed by the item's id:
|
|
6677
|
-
* `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
|
|
6678
|
-
* `providers[authentik].clientSecret`.
|
|
6679
|
-
*/
|
|
6680
|
-
function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
|
|
6681
|
-
return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
|
|
6682
|
-
}
|
|
6683
|
-
function parseScope(segments) {
|
|
6684
|
-
switch (segments[0]) {
|
|
6685
|
-
case "global": return {
|
|
6686
|
-
scope: GLOBAL_SCOPE,
|
|
6687
|
-
used: 1
|
|
6688
|
-
};
|
|
6689
|
-
case "node": {
|
|
6690
|
-
const nodeId = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
|
|
6691
|
-
return nodeId === null || nodeId.length === 0 ? null : {
|
|
6692
|
-
scope: {
|
|
6693
|
-
kind: "node",
|
|
6694
|
-
nodeId
|
|
6695
|
-
},
|
|
6696
|
-
used: 2
|
|
6697
|
-
};
|
|
6698
|
-
}
|
|
6699
|
-
case "device": {
|
|
6700
|
-
const raw = segments[1];
|
|
6701
|
-
if (raw === void 0 || !/^\d+$/.test(raw)) return null;
|
|
6702
|
-
return {
|
|
6703
|
-
scope: {
|
|
6704
|
-
kind: "device",
|
|
6705
|
-
deviceId: Number(raw)
|
|
6706
|
-
},
|
|
6707
|
-
used: 2
|
|
6708
|
-
};
|
|
6709
|
-
}
|
|
6710
|
-
case "integration": {
|
|
6711
|
-
const id = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
|
|
6712
|
-
return id === null || id.length === 0 ? null : {
|
|
6713
|
-
scope: {
|
|
6714
|
-
kind: "integration",
|
|
6715
|
-
integrationId: id
|
|
6716
|
-
},
|
|
6717
|
-
used: 2
|
|
6718
|
-
};
|
|
6719
|
-
}
|
|
6720
|
-
case "row": {
|
|
6721
|
-
const collection = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
|
|
6722
|
-
const rowId = segments[2] !== void 0 ? decodeSegment(segments[2]) : null;
|
|
6723
|
-
if (collection === null || rowId === null || collection.length === 0 || rowId.length === 0) return null;
|
|
6724
|
-
return {
|
|
6725
|
-
scope: {
|
|
6726
|
-
kind: "row",
|
|
6727
|
-
collection,
|
|
6728
|
-
rowId
|
|
6729
|
-
},
|
|
6730
|
-
used: 3
|
|
6731
|
-
};
|
|
6732
|
-
}
|
|
6733
|
-
default: return null;
|
|
6734
|
-
}
|
|
6735
|
-
}
|
|
6736
|
-
/** Take a ref apart; `null` when it is not a well-formed ref. */
|
|
6737
|
-
function parseSecretRef(ref) {
|
|
6738
|
-
if (!ref.startsWith("secret://")) return null;
|
|
6739
|
-
const segments = ref.slice(9).split("/");
|
|
6740
|
-
const owner = segments[0] !== void 0 ? decodeSegment(segments[0]) : null;
|
|
6741
|
-
if (owner === null || owner.length === 0) return null;
|
|
6742
|
-
const parsed = parseScope(segments.slice(1));
|
|
6743
|
-
if (parsed === null) return null;
|
|
6744
|
-
const fieldPath = segments.slice(1 + parsed.used).join("/");
|
|
6745
|
-
if (fieldPath.length === 0) return null;
|
|
6746
|
-
return {
|
|
6747
|
-
owner,
|
|
6748
|
-
scope: parsed.scope,
|
|
6749
|
-
fieldPath
|
|
6750
|
-
};
|
|
6751
|
-
}
|
|
6752
6904
|
/** A write to a secret path could not be stored in the keychain, so it was refused. */
|
|
6753
6905
|
var KeychainWriteRefusedError = class extends Error {
|
|
6754
6906
|
fieldPath;
|
|
@@ -6759,7 +6911,7 @@ var KeychainWriteRefusedError = class extends Error {
|
|
|
6759
6911
|
}
|
|
6760
6912
|
};
|
|
6761
6913
|
var KEEP = { kind: "keep" };
|
|
6762
|
-
function isRecord$
|
|
6914
|
+
function isRecord$4(value) {
|
|
6763
6915
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6764
6916
|
}
|
|
6765
6917
|
function defaultMintId() {
|
|
@@ -6772,7 +6924,7 @@ function collectSecretMarkerRefs(value, out = []) {
|
|
|
6772
6924
|
return out;
|
|
6773
6925
|
}
|
|
6774
6926
|
if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
|
|
6775
|
-
else if (isRecord$
|
|
6927
|
+
else if (isRecord$4(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
|
|
6776
6928
|
return out;
|
|
6777
6929
|
}
|
|
6778
6930
|
async function storedValueEquals(secrets, marker, candidate) {
|
|
@@ -6827,7 +6979,7 @@ function rowId(row, idKey) {
|
|
|
6827
6979
|
return null;
|
|
6828
6980
|
}
|
|
6829
6981
|
async function writeArray(ctx, node, incoming, stored, fieldPath) {
|
|
6830
|
-
const storedRows = Array.isArray(stored) ? stored.filter(isRecord$
|
|
6982
|
+
const storedRows = Array.isArray(stored) ? stored.filter(isRecord$4) : [];
|
|
6831
6983
|
if (!Array.isArray(incoming)) {
|
|
6832
6984
|
ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
|
|
6833
6985
|
return {
|
|
@@ -6843,7 +6995,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
|
|
|
6843
6995
|
const kept = /* @__PURE__ */ new Set();
|
|
6844
6996
|
const rows = [];
|
|
6845
6997
|
for (const row of incoming) {
|
|
6846
|
-
if (!isRecord$
|
|
6998
|
+
if (!isRecord$4(row)) {
|
|
6847
6999
|
rows.push(row);
|
|
6848
7000
|
continue;
|
|
6849
7001
|
}
|
|
@@ -6868,7 +7020,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
|
|
|
6868
7020
|
case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
|
|
6869
7021
|
case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
|
|
6870
7022
|
case "object": {
|
|
6871
|
-
if (!isRecord$
|
|
7023
|
+
if (!isRecord$4(incoming)) {
|
|
6872
7024
|
if (incoming === void 0) return KEEP;
|
|
6873
7025
|
ctx.deletes.push(...collectSecretMarkerRefs(stored));
|
|
6874
7026
|
return {
|
|
@@ -6876,7 +7028,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
|
|
|
6876
7028
|
value: incoming
|
|
6877
7029
|
};
|
|
6878
7030
|
}
|
|
6879
|
-
const storedObj = isRecord$
|
|
7031
|
+
const storedObj = isRecord$4(stored) ? stored : {};
|
|
6880
7032
|
return {
|
|
6881
7033
|
kind: "value",
|
|
6882
7034
|
value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
|
|
@@ -6999,7 +7151,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
|
|
|
6999
7151
|
value: items
|
|
7000
7152
|
};
|
|
7001
7153
|
}
|
|
7002
|
-
if (isRecord$
|
|
7154
|
+
if (isRecord$4(v)) {
|
|
7003
7155
|
const out = {};
|
|
7004
7156
|
for (const [k, child] of Object.entries(v)) {
|
|
7005
7157
|
const s = substitute(child);
|
|
@@ -7031,7 +7183,7 @@ var SecretOverwriteRefusedError = class extends Error {
|
|
|
7031
7183
|
this.name = "SecretOverwriteRefusedError";
|
|
7032
7184
|
}
|
|
7033
7185
|
};
|
|
7034
|
-
function isRecord$
|
|
7186
|
+
function isRecord$3(value) {
|
|
7035
7187
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7036
7188
|
}
|
|
7037
7189
|
function resolvedValue(pass, marker) {
|
|
@@ -7060,7 +7212,7 @@ function inArray(pass, incoming, stored) {
|
|
|
7060
7212
|
return incoming.map((item, index) => {
|
|
7061
7213
|
const twin = stored[index];
|
|
7062
7214
|
if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
|
|
7063
|
-
if (isRecord$
|
|
7215
|
+
if (isRecord$3(item) && isRecord$3(twin)) return inArrayRow(pass, item, twin);
|
|
7064
7216
|
if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
|
|
7065
7217
|
return item;
|
|
7066
7218
|
});
|
|
@@ -7071,7 +7223,7 @@ function inArrayRow(pass, incoming, stored) {
|
|
|
7071
7223
|
const twin = stored[key];
|
|
7072
7224
|
if (isSecretMarker(twin)) {
|
|
7073
7225
|
if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
|
|
7074
|
-
} else if (isRecord$
|
|
7226
|
+
} else if (isRecord$3(value) && isRecord$3(twin)) out[key] = inArrayRow(pass, value, twin);
|
|
7075
7227
|
else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
|
|
7076
7228
|
}
|
|
7077
7229
|
return out;
|
|
@@ -7079,7 +7231,7 @@ function inArrayRow(pass, incoming, stored) {
|
|
|
7079
7231
|
async function walk(pass, incoming, stored) {
|
|
7080
7232
|
if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
|
|
7081
7233
|
if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
|
|
7082
|
-
if (!isRecord$
|
|
7234
|
+
if (!isRecord$3(incoming) || !isRecord$3(stored)) return incoming;
|
|
7083
7235
|
const out = { ...incoming };
|
|
7084
7236
|
for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
|
|
7085
7237
|
return out;
|
|
@@ -7110,7 +7262,7 @@ async function restoreUnchangedSecrets(input) {
|
|
|
7110
7262
|
function isJsonEqual(a, b) {
|
|
7111
7263
|
if (Object.is(a, b)) return true;
|
|
7112
7264
|
if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
|
|
7113
|
-
if (isRecord$
|
|
7265
|
+
if (isRecord$3(a) && isRecord$3(b)) {
|
|
7114
7266
|
const keys = Object.keys(a);
|
|
7115
7267
|
if (keys.length !== Object.keys(b).length) return false;
|
|
7116
7268
|
return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
|
|
@@ -7134,7 +7286,7 @@ function markerKeysOf(blob, owner) {
|
|
|
7134
7286
|
}
|
|
7135
7287
|
function nodeFromStored(value, owner) {
|
|
7136
7288
|
if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
|
|
7137
|
-
if (!isRecord$
|
|
7289
|
+
if (!isRecord$3(value)) return null;
|
|
7138
7290
|
const children = /* @__PURE__ */ new Map();
|
|
7139
7291
|
for (const [key, child] of Object.entries(value)) {
|
|
7140
7292
|
const node = nodeFromStored(child, owner);
|
|
@@ -7631,13 +7783,48 @@ var BaseAddon = class {
|
|
|
7631
7783
|
if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
|
|
7632
7784
|
return this.runBound(sets.flatMap((set) => set.bind(api)), options);
|
|
7633
7785
|
}
|
|
7634
|
-
|
|
7786
|
+
/**
|
|
7787
|
+
* A device addon states its instance kind once, before its migrations run, and the one-switch
|
|
7788
|
+
* migration (registry `enabled` to the instance body, epic #440 step 3) joins whatever
|
|
7789
|
+
* catalogue the addon runs, in the SAME declaration (the engine replaces an owner's catalogue
|
|
7790
|
+
* on every declare).
|
|
7791
|
+
*/
|
|
7792
|
+
declareRegistrySwitch(spec) {
|
|
7793
|
+
this.registrySwitchSpec = spec;
|
|
7794
|
+
}
|
|
7795
|
+
registrySwitchSpec = null;
|
|
7796
|
+
registrySwitchBound() {
|
|
7797
|
+
const spec = this.registrySwitchSpec;
|
|
7798
|
+
if (spec === null) return [];
|
|
7799
|
+
const api = this.ctx.migrationApi;
|
|
7800
|
+
if (api === void 0) {
|
|
7801
|
+
this.ctx.logger.warn("the registry switch migration is skipped: the migration API is not available on this context");
|
|
7802
|
+
return [];
|
|
7803
|
+
}
|
|
7804
|
+
return migrationSet([registrySwitchToBodyMigration({
|
|
7805
|
+
addonId: this.ctx.id,
|
|
7806
|
+
date: "2026-11-06",
|
|
7807
|
+
seq: 1e3,
|
|
7808
|
+
initialBody: spec.initialBody
|
|
7809
|
+
})], (a) => registrySwitchPortsOver({
|
|
7810
|
+
api: integrationRowsApiOver(a),
|
|
7811
|
+
records: switchRecordsOverMigrationApi(a),
|
|
7812
|
+
kind: spec.kind
|
|
7813
|
+
})).bind(api);
|
|
7814
|
+
}
|
|
7815
|
+
async runBound(declared, options) {
|
|
7816
|
+
const added = this.registrySwitchBound();
|
|
7817
|
+
const migrations = [...declared, ...added];
|
|
7818
|
+
const phased = options?.only === void 0 || added.length === 0 ? options : {
|
|
7819
|
+
...options,
|
|
7820
|
+
only: [...options.only, ...added.map((m) => m.meta.id)]
|
|
7821
|
+
};
|
|
7635
7822
|
const api = this.ctx.api.migrations;
|
|
7636
7823
|
const report = await runAddonMigrations({
|
|
7637
7824
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7638
7825
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7639
7826
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7640
|
-
}, migrations, this.ctx.logger,
|
|
7827
|
+
}, migrations, this.ctx.logger, phased);
|
|
7641
7828
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7642
7829
|
return report;
|
|
7643
7830
|
}
|
|
@@ -8710,125 +8897,6 @@ function normalizeAddonInitResult(result) {
|
|
|
8710
8897
|
if (Array.isArray(result)) return { providers: result };
|
|
8711
8898
|
return result;
|
|
8712
8899
|
}
|
|
8713
|
-
/** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
|
|
8714
|
-
/** The files a kind may live in. `history` is device-history's (D850), never records. */
|
|
8715
|
-
var RECORD_DATABASES = ["settings", "events"];
|
|
8716
|
-
/** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
|
|
8717
|
-
var RECORD_CLASSES = [
|
|
8718
|
-
"config",
|
|
8719
|
-
"ledger",
|
|
8720
|
-
"audit",
|
|
8721
|
-
"mirror",
|
|
8722
|
-
"ephemeral"
|
|
8723
|
-
];
|
|
8724
|
-
var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
|
|
8725
|
-
var RECORD_MAX_DATA_BYTES = 1024 * 1024;
|
|
8726
|
-
var RECORD_BATCH_MAX = 1e3;
|
|
8727
|
-
var RECORD_QUERY_MAX = 1e3;
|
|
8728
|
-
/**
|
|
8729
|
-
* The share-view scope kinds a share-link token can carry. The hub mints
|
|
8730
|
-
* and validates them (`ShareViewGrantSchema` builds its
|
|
8731
|
-
* enum from this list); a data plane names the kinds it ACCEPTS in `share`.
|
|
8732
|
-
*/
|
|
8733
|
-
var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
|
|
8734
|
-
/** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
|
|
8735
|
-
var PeerBytesTicketSchema = object({
|
|
8736
|
-
/** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
|
|
8737
|
-
url: string().min(1),
|
|
8738
|
-
/**
|
|
8739
|
-
* The HOST node this URL means something on — the hub or a named agent,
|
|
8740
|
-
* never a runner. {@link AddonPeerBytes.open} compares it to its own and
|
|
8741
|
-
* refuses `cross-node` by name when they differ, without dialling.
|
|
8742
|
-
*/
|
|
8743
|
-
hostNodeId: string().min(1),
|
|
8744
|
-
expiresAtMs: number().int().nonnegative(),
|
|
8745
|
-
/**
|
|
8746
|
-
* What the producer DECLARED the body to be, when it knows — `null` when it
|
|
8747
|
-
* does not. Never `0` for unknown (D393): a consumer sizing a bound off this
|
|
8748
|
-
* must be able to tell "the producer did not say" from "the body is empty".
|
|
8749
|
-
*/
|
|
8750
|
-
declaredBytes: number().int().nonnegative().nullable()
|
|
8751
|
-
});
|
|
8752
|
-
/**
|
|
8753
|
-
* Record kinds (D881): the declaration an addon makes of one kind of fact, the
|
|
8754
|
-
* Zod row schema that describes it, and the helper that turns the two into the
|
|
8755
|
-
* JSON the engine stores.
|
|
8756
|
-
*
|
|
8757
|
-
* A kind is declared ONCE, next to the code that owns it, and the engine's
|
|
8758
|
-
* reconciler creates its indexes - nothing creates DDL at runtime from a write.
|
|
8759
|
-
* Everything the generic machinery needs (secret fields, the entry descriptor)
|
|
8760
|
-
* is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
|
|
8761
|
-
* never a field name (D879).
|
|
8762
|
-
*/
|
|
8763
|
-
/** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
|
|
8764
|
-
var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
|
|
8765
|
-
var RecordIndexSchema = object({
|
|
8766
|
-
name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
|
|
8767
|
-
fields: array(RecordFieldPathSchema).min(1).max(4),
|
|
8768
|
-
unique: boolean().optional()
|
|
8769
|
-
}).strict();
|
|
8770
|
-
var RecordRetentionSchema = union([
|
|
8771
|
-
object({
|
|
8772
|
-
ttlMs: number().int().positive(),
|
|
8773
|
-
from: _enum(["at", "write"])
|
|
8774
|
-
}).strict(),
|
|
8775
|
-
object({
|
|
8776
|
-
maxRows: number().int().positive().optional(),
|
|
8777
|
-
maxRowsPerDevice: number().int().positive().optional()
|
|
8778
|
-
}).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
|
|
8779
|
-
object({
|
|
8780
|
-
owner: string().min(1),
|
|
8781
|
-
reason: string().min(1)
|
|
8782
|
-
}).strict(),
|
|
8783
|
-
object({ none: string().min(1) }).strict()
|
|
8784
|
-
]);
|
|
8785
|
-
/** The descriptor a generic entries UI reads (D881 "generic entries"). */
|
|
8786
|
-
var EntryDescriptorSchema = object({
|
|
8787
|
-
title: string().min(1),
|
|
8788
|
-
/** The record body field shown as the entry's name. */
|
|
8789
|
-
nameField: string().min(1),
|
|
8790
|
-
/** JSON Schema of the entry body; `secret: true` marks a password field. */
|
|
8791
|
-
fields: record(string(), unknown())
|
|
8792
|
-
}).strict();
|
|
8793
|
-
/** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
|
|
8794
|
-
var RecordKindDeclarationSchema = object({
|
|
8795
|
-
database: _enum(RECORD_DATABASES).default("settings"),
|
|
8796
|
-
class: _enum(RECORD_CLASSES),
|
|
8797
|
-
writeTrigger: string().min(1),
|
|
8798
|
-
device: _enum([
|
|
8799
|
-
"required",
|
|
8800
|
-
"optional",
|
|
8801
|
-
"none"
|
|
8802
|
-
]).default("none"),
|
|
8803
|
-
at: _enum(["required", "none"]).default("none"),
|
|
8804
|
-
indexes: array(RecordIndexSchema).max(8).default([]),
|
|
8805
|
-
retention: RecordRetentionSchema,
|
|
8806
|
-
deviceRemoval: _enum(["delete", "none"]).default("none"),
|
|
8807
|
-
maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
|
|
8808
|
-
/** Derived from the Zod schema; never written by hand. */
|
|
8809
|
-
secretPaths: array(string().min(1)).default([]),
|
|
8810
|
-
/** The id key of each array that carries a secret (`{ headers: 'name' }`). */
|
|
8811
|
-
secretArrayKeys: record(string(), string()).default({}),
|
|
8812
|
-
/** Kinds that must agree through `transact` sit in one file under one family name. */
|
|
8813
|
-
transactFamily: string().min(1).optional(),
|
|
8814
|
-
entry: EntryDescriptorSchema.optional()
|
|
8815
|
-
}).strict();
|
|
8816
|
-
/** The kind names: lower-case, dash separated, short enough for an index name. */
|
|
8817
|
-
var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
|
|
8818
|
-
/**
|
|
8819
|
-
* Entry kinds (D881): every list of user-created entries (notification
|
|
8820
|
-
* targets, brokers, LLM profiles) is ONE record kind with an id, a name and a
|
|
8821
|
-
* kind-specific body, and the admin UI lists/adds/edits/deletes all of them
|
|
8822
|
-
* through the `record-entries` capability with password fields derived from
|
|
8823
|
-
* the schema (`.meta({ secret: true })`).
|
|
8824
|
-
*/
|
|
8825
|
-
/** The body of a plain entry: a name and the fields the kind adds. */
|
|
8826
|
-
function entryBody(shape) {
|
|
8827
|
-
return object({
|
|
8828
|
-
name: string().min(1),
|
|
8829
|
-
...shape
|
|
8830
|
-
});
|
|
8831
|
-
}
|
|
8832
8900
|
object({
|
|
8833
8901
|
field: string().min(1),
|
|
8834
8902
|
on: literal("advance")
|
|
@@ -8917,6 +8985,96 @@ function optionalMethod(input, output, options) {
|
|
|
8917
8985
|
providerOptional: true
|
|
8918
8986
|
};
|
|
8919
8987
|
}
|
|
8988
|
+
/**
|
|
8989
|
+
* Record kinds (D881): the declaration an addon makes of one kind of fact, the
|
|
8990
|
+
* Zod row schema that describes it, and the helper that turns the two into the
|
|
8991
|
+
* JSON the engine stores.
|
|
8992
|
+
*
|
|
8993
|
+
* A kind is declared ONCE, next to the code that owns it, and the engine's
|
|
8994
|
+
* reconciler creates its indexes - nothing creates DDL at runtime from a write.
|
|
8995
|
+
* Everything the generic machinery needs (secret fields, the entry descriptor)
|
|
8996
|
+
* is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
|
|
8997
|
+
* never a field name (D879).
|
|
8998
|
+
*/
|
|
8999
|
+
/** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
|
|
9000
|
+
var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
|
|
9001
|
+
var RecordIndexSchema = object({
|
|
9002
|
+
name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
|
|
9003
|
+
fields: array(RecordFieldPathSchema).min(1).max(4),
|
|
9004
|
+
unique: boolean().optional()
|
|
9005
|
+
}).strict();
|
|
9006
|
+
var RecordRetentionSchema = union([
|
|
9007
|
+
object({
|
|
9008
|
+
ttlMs: number().int().positive(),
|
|
9009
|
+
from: _enum(["at", "write"])
|
|
9010
|
+
}).strict(),
|
|
9011
|
+
object({
|
|
9012
|
+
maxRows: number().int().positive().optional(),
|
|
9013
|
+
maxRowsPerDevice: number().int().positive().optional()
|
|
9014
|
+
}).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
|
|
9015
|
+
object({
|
|
9016
|
+
owner: string().min(1),
|
|
9017
|
+
reason: string().min(1)
|
|
9018
|
+
}).strict(),
|
|
9019
|
+
object({ none: string().min(1) }).strict()
|
|
9020
|
+
]);
|
|
9021
|
+
/** The descriptor a generic entries UI reads (D881 "generic entries"). */
|
|
9022
|
+
var EntryDescriptorSchema = object({
|
|
9023
|
+
title: string().min(1),
|
|
9024
|
+
/** The record body field shown as the entry's name. */
|
|
9025
|
+
nameField: string().min(1),
|
|
9026
|
+
/** JSON Schema of the entry body; `secret: true` marks a password field. */
|
|
9027
|
+
fields: record(string(), unknown())
|
|
9028
|
+
}).strict();
|
|
9029
|
+
/** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
|
|
9030
|
+
var RecordKindDeclarationSchema = object({
|
|
9031
|
+
database: _enum(RECORD_DATABASES).default("settings"),
|
|
9032
|
+
class: _enum(RECORD_CLASSES),
|
|
9033
|
+
writeTrigger: string().min(1),
|
|
9034
|
+
device: _enum([
|
|
9035
|
+
"required",
|
|
9036
|
+
"optional",
|
|
9037
|
+
"none"
|
|
9038
|
+
]).default("none"),
|
|
9039
|
+
at: _enum(["required", "none"]).default("none"),
|
|
9040
|
+
indexes: array(RecordIndexSchema).max(8).default([]),
|
|
9041
|
+
retention: RecordRetentionSchema,
|
|
9042
|
+
deviceRemoval: _enum(["delete", "none"]).default("none"),
|
|
9043
|
+
maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
|
|
9044
|
+
/** Derived from the Zod schema; never written by hand. */
|
|
9045
|
+
secretPaths: array(string().min(1)).default([]),
|
|
9046
|
+
/** The id key of each array that carries a secret (`{ headers: 'name' }`). */
|
|
9047
|
+
secretArrayKeys: record(string(), string()).default({}),
|
|
9048
|
+
/** Kinds that must agree through `transact` sit in one file under one family name. */
|
|
9049
|
+
transactFamily: string().min(1).optional(),
|
|
9050
|
+
entry: EntryDescriptorSchema.optional()
|
|
9051
|
+
}).strict();
|
|
9052
|
+
/** The kind names: lower-case, dash separated, short enough for an index name. */
|
|
9053
|
+
var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
|
|
9054
|
+
/**
|
|
9055
|
+
* The share-view scope kinds a share-link token can carry. The hub mints
|
|
9056
|
+
* and validates them (`ShareViewGrantSchema` builds its
|
|
9057
|
+
* enum from this list); a data plane names the kinds it ACCEPTS in `share`.
|
|
9058
|
+
*/
|
|
9059
|
+
var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
|
|
9060
|
+
/** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
|
|
9061
|
+
var PeerBytesTicketSchema = object({
|
|
9062
|
+
/** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
|
|
9063
|
+
url: string().min(1),
|
|
9064
|
+
/**
|
|
9065
|
+
* The HOST node this URL means something on — the hub or a named agent,
|
|
9066
|
+
* never a runner. {@link AddonPeerBytes.open} compares it to its own and
|
|
9067
|
+
* refuses `cross-node` by name when they differ, without dialling.
|
|
9068
|
+
*/
|
|
9069
|
+
hostNodeId: string().min(1),
|
|
9070
|
+
expiresAtMs: number().int().nonnegative(),
|
|
9071
|
+
/**
|
|
9072
|
+
* What the producer DECLARED the body to be, when it knows — `null` when it
|
|
9073
|
+
* does not. Never `0` for unknown (D393): a consumer sizing a bound off this
|
|
9074
|
+
* must be able to tell "the producer did not say" from "the body is empty".
|
|
9075
|
+
*/
|
|
9076
|
+
declaredBytes: number().int().nonnegative().nullable()
|
|
9077
|
+
});
|
|
8920
9078
|
var READING_QUANTITIES = [
|
|
8921
9079
|
"temperature",
|
|
8922
9080
|
"pressure",
|
|
@@ -9513,7 +9671,6 @@ var CAP_NAMES = Object.freeze([
|
|
|
9513
9671
|
"climate-control",
|
|
9514
9672
|
"color",
|
|
9515
9673
|
"composer",
|
|
9516
|
-
"connection-test",
|
|
9517
9674
|
"connectivity",
|
|
9518
9675
|
"consumables",
|
|
9519
9676
|
"contact",
|
|
@@ -10660,6 +10817,10 @@ Object.freeze({
|
|
|
10660
10817
|
"all",
|
|
10661
10818
|
"one"
|
|
10662
10819
|
]
|
|
10820
|
+
},
|
|
10821
|
+
{
|
|
10822
|
+
"path": "positionUpdatedAt",
|
|
10823
|
+
"valueType": "number"
|
|
10663
10824
|
}
|
|
10664
10825
|
],
|
|
10665
10826
|
"meter": [{
|
|
@@ -10914,22 +11075,6 @@ Object.freeze({
|
|
|
10914
11075
|
"path": "pumpRunning",
|
|
10915
11076
|
"valueType": "boolean"
|
|
10916
11077
|
},
|
|
10917
|
-
{
|
|
10918
|
-
"path": "lastDrink",
|
|
10919
|
-
"valueType": "number"
|
|
10920
|
-
},
|
|
10921
|
-
{
|
|
10922
|
-
"path": "currentHourDrink",
|
|
10923
|
-
"valueType": "number"
|
|
10924
|
-
},
|
|
10925
|
-
{
|
|
10926
|
-
"path": "last24hDrink",
|
|
10927
|
-
"valueType": "number"
|
|
10928
|
-
},
|
|
10929
|
-
{
|
|
10930
|
-
"path": "todayDrink",
|
|
10931
|
-
"valueType": "number"
|
|
10932
|
-
},
|
|
10933
11078
|
{
|
|
10934
11079
|
"path": "error",
|
|
10935
11080
|
"valueType": "string"
|
|
@@ -11495,6 +11640,59 @@ Object.freeze({
|
|
|
11495
11640
|
}
|
|
11496
11641
|
]
|
|
11497
11642
|
});
|
|
11643
|
+
Object.freeze({
|
|
11644
|
+
"actions": { "run": 363e4 },
|
|
11645
|
+
"audio-analyzer": { "classify": 3e4 },
|
|
11646
|
+
"custom-model-registry": { "importModelBundle": 18e5 },
|
|
11647
|
+
"database-snapshot": { "writeSnapshot": 18e5 },
|
|
11648
|
+
"device-manager": {
|
|
11649
|
+
"migrateDevice": 72e4,
|
|
11650
|
+
"runDiscoverySweep": 12e4
|
|
11651
|
+
},
|
|
11652
|
+
"device-provider": { "reloadDevice": 18e4 },
|
|
11653
|
+
"llm": {
|
|
11654
|
+
"generate": 6e5,
|
|
11655
|
+
"generateVision": 6e5,
|
|
11656
|
+
"testProfile": 6e5
|
|
11657
|
+
},
|
|
11658
|
+
"llm-runtime": {
|
|
11659
|
+
"complete": 6e5,
|
|
11660
|
+
"completeApple": 6e5,
|
|
11661
|
+
"ensureStarted": 6e5,
|
|
11662
|
+
"installModel": 36e5
|
|
11663
|
+
},
|
|
11664
|
+
"local-network": { "scanNetwork": 9e4 },
|
|
11665
|
+
"model-convert": { "convert": 6e5 },
|
|
11666
|
+
"pipeline-analytics": {
|
|
11667
|
+
"addRetrainFrameFromRecording": 6e4,
|
|
11668
|
+
"offerRetrainArchive": 12e4,
|
|
11669
|
+
"proposeRetrainAnnotationsWithLlm": 18e4,
|
|
11670
|
+
"resolveRetrainTrainingCorpus": 6e4,
|
|
11671
|
+
"writeRetrainArchive": 36e5
|
|
11672
|
+
},
|
|
11673
|
+
"server-management": { "applyServerUpdate": 96e4 },
|
|
11674
|
+
"terminal-session": { "pullOutput": 15e3 },
|
|
11675
|
+
"training": {
|
|
11676
|
+
"canaryResult": 12e4,
|
|
11677
|
+
"cancelJob": 12e4,
|
|
11678
|
+
"planJob": 6e4,
|
|
11679
|
+
"registerResult": 18e5,
|
|
11680
|
+
"rollbackCanary": 12e4,
|
|
11681
|
+
"startJob": 12e4,
|
|
11682
|
+
"startJobBatch": 12e4
|
|
11683
|
+
},
|
|
11684
|
+
"training-provider": {
|
|
11685
|
+
"cancelJob": 6e4,
|
|
11686
|
+
"cleanupJob": 3e5,
|
|
11687
|
+
"getJob": 6e4,
|
|
11688
|
+
"listJobEvents": 6e4,
|
|
11689
|
+
"listJobs": 12e4,
|
|
11690
|
+
"offerArtifact": 3e5,
|
|
11691
|
+
"readResult": 3e5,
|
|
11692
|
+
"submitJob": 18e5,
|
|
11693
|
+
"uploadDataset": 72e5
|
|
11694
|
+
}
|
|
11695
|
+
});
|
|
11498
11696
|
object({
|
|
11499
11697
|
code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
|
|
11500
11698
|
params: record(string(), union([string(), number()])).optional()
|
|
@@ -12847,10 +13045,432 @@ var IntegrationInstanceRefSchema = object({
|
|
|
12847
13045
|
]),
|
|
12848
13046
|
candidateStableId: string().optional()
|
|
12849
13047
|
});
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
13048
|
+
/**
|
|
13049
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
13050
|
+
*
|
|
13051
|
+
* Apprise-derived model (see
|
|
13052
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
13053
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
13054
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
13055
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
13056
|
+
* message to what the kind supports — callers never special-case a service.
|
|
13057
|
+
*
|
|
13058
|
+
* DESIGN DECISIONS (locked):
|
|
13059
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
13060
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
13061
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
13062
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
13063
|
+
* alternative would fork the UI per addon and cannot host the
|
|
13064
|
+
* discovery→adopt flow.
|
|
13065
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
13066
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
13067
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
13068
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
13069
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
13070
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
13071
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
13072
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
13073
|
+
* base64 fallback needed.
|
|
13074
|
+
*
|
|
13075
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
13076
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
13077
|
+
* admin "Integrations" page.
|
|
13078
|
+
*/
|
|
13079
|
+
/**
|
|
13080
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
13081
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
13082
|
+
*/
|
|
13083
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
13084
|
+
"image",
|
|
13085
|
+
"video",
|
|
13086
|
+
"gif",
|
|
13087
|
+
"audio",
|
|
13088
|
+
"icon"
|
|
13089
|
+
]);
|
|
13090
|
+
/**
|
|
13091
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
13092
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
13093
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
13094
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
13095
|
+
*/
|
|
13096
|
+
var AttachmentSchema = object({
|
|
13097
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
13098
|
+
url: string().optional(),
|
|
13099
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
13100
|
+
mime: string().optional(),
|
|
13101
|
+
name: string().optional()
|
|
13102
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
13103
|
+
var NotificationFormatSchema = _enum([
|
|
13104
|
+
"text",
|
|
13105
|
+
"markdown",
|
|
13106
|
+
"html"
|
|
13107
|
+
]);
|
|
13108
|
+
/**
|
|
13109
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
13110
|
+
*
|
|
13111
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
13112
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
13113
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
13114
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
13115
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
13116
|
+
*
|
|
13117
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
13118
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
13119
|
+
* differently.
|
|
13120
|
+
*
|
|
13121
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
13122
|
+
*
|
|
13123
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
13124
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
13125
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
13126
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
13127
|
+
* decides its glyph, which is the only place that decision can be made
|
|
13128
|
+
* honestly.
|
|
13129
|
+
*
|
|
13130
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
13131
|
+
* `disarm` straight through; iOS feeds that string to
|
|
13132
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
13133
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
13134
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
13135
|
+
*
|
|
13136
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
13137
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
13138
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
13139
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
13140
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
13141
|
+
*/
|
|
13142
|
+
var NotificationActionIconSchema = _enum([
|
|
13143
|
+
"acknowledge",
|
|
13144
|
+
"dismiss",
|
|
13145
|
+
"silence",
|
|
13146
|
+
"snooze",
|
|
13147
|
+
"view",
|
|
13148
|
+
"play",
|
|
13149
|
+
"open",
|
|
13150
|
+
"close",
|
|
13151
|
+
"lock",
|
|
13152
|
+
"unlock",
|
|
13153
|
+
"arm",
|
|
13154
|
+
"arm-home",
|
|
13155
|
+
"arm-away",
|
|
13156
|
+
"arm-night",
|
|
13157
|
+
"disarm",
|
|
13158
|
+
"light",
|
|
13159
|
+
"alert",
|
|
13160
|
+
"camera"
|
|
13161
|
+
]);
|
|
13162
|
+
/** A single tap-through action button. */
|
|
13163
|
+
var NotificationActionSchema = object({
|
|
13164
|
+
id: string(),
|
|
13165
|
+
label: string(),
|
|
13166
|
+
url: string().optional(),
|
|
13167
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
13168
|
+
icon: NotificationActionIconSchema.optional(),
|
|
13169
|
+
/**
|
|
13170
|
+
* Renders in a warning style where the notifier supports it.
|
|
13171
|
+
*
|
|
13172
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
13173
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
13174
|
+
* does not buy.
|
|
13175
|
+
*/
|
|
13176
|
+
destructive: boolean().optional(),
|
|
13177
|
+
/**
|
|
13178
|
+
* How the tap should REACH the url.
|
|
13179
|
+
*
|
|
13180
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
13181
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
13182
|
+
* That is right for a button whose answer the operator wants to read.
|
|
13183
|
+
*
|
|
13184
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
13185
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
13186
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
13187
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
13188
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
13189
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
13190
|
+
* a requirement.
|
|
13191
|
+
*/
|
|
13192
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
13193
|
+
});
|
|
13194
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
13195
|
+
var DeliveryParamValueSchema = union([
|
|
13196
|
+
string(),
|
|
13197
|
+
number(),
|
|
13198
|
+
boolean()
|
|
13199
|
+
]);
|
|
13200
|
+
/**
|
|
13201
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
13202
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
13203
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
13204
|
+
* no editor knows a service by name.
|
|
13205
|
+
*
|
|
13206
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
13207
|
+
* run on the addon cap path).
|
|
13208
|
+
*/
|
|
13209
|
+
var DeliveryParamSchema = object({
|
|
13210
|
+
key: string().min(1),
|
|
13211
|
+
label: string(),
|
|
13212
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
13213
|
+
labelKey: string().optional(),
|
|
13214
|
+
type: _enum([
|
|
13215
|
+
"enum",
|
|
13216
|
+
"number",
|
|
13217
|
+
"boolean",
|
|
13218
|
+
"string"
|
|
13219
|
+
]),
|
|
13220
|
+
/** `enum` only: the closed set of accepted values. */
|
|
13221
|
+
options: array(object({
|
|
13222
|
+
value: string(),
|
|
13223
|
+
label: string()
|
|
13224
|
+
})).optional(),
|
|
13225
|
+
/** `number` only. */
|
|
13226
|
+
min: number().optional(),
|
|
13227
|
+
max: number().optional(),
|
|
13228
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
13229
|
+
default: DeliveryParamValueSchema.optional()
|
|
13230
|
+
});
|
|
13231
|
+
/**
|
|
13232
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
13233
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
13234
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
13235
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
13236
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
13237
|
+
* `priority` for that one target.
|
|
13238
|
+
*/
|
|
13239
|
+
var NotificationSchema = object({
|
|
13240
|
+
body: string(),
|
|
13241
|
+
title: string().optional(),
|
|
13242
|
+
format: NotificationFormatSchema.default("text"),
|
|
13243
|
+
priority: number().int().min(1).max(5).default(3),
|
|
13244
|
+
level: string().optional(),
|
|
13245
|
+
attachments: array(AttachmentSchema).optional(),
|
|
13246
|
+
clickUrl: string().optional(),
|
|
13247
|
+
actions: array(NotificationActionSchema).optional(),
|
|
13248
|
+
sound: string().optional(),
|
|
13249
|
+
ttl: number().optional(),
|
|
13250
|
+
tag: string().optional(),
|
|
13251
|
+
deviceId: number().optional(),
|
|
13252
|
+
eventId: string().optional(),
|
|
13253
|
+
metadata: record(string(), unknown()).optional(),
|
|
13254
|
+
/**
|
|
13255
|
+
* Values for the parameters the target's kind DECLARES in
|
|
13256
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
13257
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
13258
|
+
*/
|
|
13259
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
13260
|
+
});
|
|
13261
|
+
/** One declared native severity/priority level for a kind. */
|
|
13262
|
+
var TargetKindLevelSchema = object({
|
|
13263
|
+
id: string(),
|
|
13264
|
+
label: string(),
|
|
13265
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
13266
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
13267
|
+
flags: object({
|
|
13268
|
+
critical: boolean().optional(),
|
|
13269
|
+
silent: boolean().optional(),
|
|
13270
|
+
noPush: boolean().optional()
|
|
13271
|
+
}).optional(),
|
|
13272
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
13273
|
+
requires: array(string()).optional(),
|
|
13274
|
+
description: string().optional()
|
|
13275
|
+
});
|
|
13276
|
+
/** The full capability block consulted before dispatch. */
|
|
13277
|
+
var TargetKindCapsSchema = object({
|
|
13278
|
+
attachments: object({
|
|
13279
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
13280
|
+
mode: _enum([
|
|
13281
|
+
"url",
|
|
13282
|
+
"bytes",
|
|
13283
|
+
"both"
|
|
13284
|
+
]),
|
|
13285
|
+
max: number().int().nonnegative(),
|
|
13286
|
+
maxBytes: number().int().positive().optional()
|
|
13287
|
+
}),
|
|
13288
|
+
/** Max action buttons (0 = none). */
|
|
13289
|
+
actions: number().int().nonnegative(),
|
|
13290
|
+
/**
|
|
13291
|
+
* Whether this kind renders a per-action ICON.
|
|
13292
|
+
*
|
|
13293
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
13294
|
+
* run on the addon cap path — three production failures in one day taught
|
|
13295
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
13296
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
13297
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
13298
|
+
*/
|
|
13299
|
+
actionIcons: boolean().optional(),
|
|
13300
|
+
levels: array(TargetKindLevelSchema),
|
|
13301
|
+
format: array(NotificationFormatSchema),
|
|
13302
|
+
clickUrl: boolean(),
|
|
13303
|
+
sound: boolean(),
|
|
13304
|
+
ttl: boolean(),
|
|
13305
|
+
bodyMaxLen: number().int().positive()
|
|
12853
13306
|
});
|
|
13307
|
+
/**
|
|
13308
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
13309
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
13310
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
13311
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
13312
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
13313
|
+
*/
|
|
13314
|
+
var ConfigSchemaPassthrough$4 = unknown();
|
|
13315
|
+
var TargetKindSchema = object({
|
|
13316
|
+
kind: string(),
|
|
13317
|
+
label: string(),
|
|
13318
|
+
icon: string(),
|
|
13319
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
13320
|
+
addonId: string(),
|
|
13321
|
+
/**
|
|
13322
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
13323
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
13324
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
13325
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
13326
|
+
*
|
|
13327
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
13328
|
+
* client, and a native client joins it onto its own hub base.
|
|
13329
|
+
*
|
|
13330
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
13331
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
13332
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
13333
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
13334
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
13335
|
+
*/
|
|
13336
|
+
iconUrl: string().optional(),
|
|
13337
|
+
/**
|
|
13338
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
13339
|
+
*
|
|
13340
|
+
* The server knows this and therefore says it, because the client cannot
|
|
13341
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
13342
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
13343
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
13344
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
13345
|
+
*
|
|
13346
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
13347
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
13348
|
+
* its raster path, which is the safe default for an unknown image.
|
|
13349
|
+
*/
|
|
13350
|
+
iconMediaType: string().optional(),
|
|
13351
|
+
configSchema: ConfigSchemaPassthrough$4,
|
|
13352
|
+
/**
|
|
13353
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
13354
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
13355
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
13356
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
13357
|
+
* on every edit.
|
|
13358
|
+
*
|
|
13359
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
13360
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
13361
|
+
* it absent must assume ANY edit may need the secret.
|
|
13362
|
+
*/
|
|
13363
|
+
destinationKeys: array(string()).optional(),
|
|
13364
|
+
supportsDiscovery: boolean(),
|
|
13365
|
+
/**
|
|
13366
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
13367
|
+
* Absent = none; the rule editor then offers nothing.
|
|
13368
|
+
*/
|
|
13369
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
13370
|
+
caps: TargetKindCapsSchema
|
|
13371
|
+
});
|
|
13372
|
+
/**
|
|
13373
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
13374
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
13375
|
+
* round-trip a stored secret to the UI.
|
|
13376
|
+
*/
|
|
13377
|
+
var TargetSchema = object({
|
|
13378
|
+
id: string(),
|
|
13379
|
+
name: string(),
|
|
13380
|
+
kind: string(),
|
|
13381
|
+
addonId: string(),
|
|
13382
|
+
enabled: boolean(),
|
|
13383
|
+
config: record(string(), unknown())
|
|
13384
|
+
});
|
|
13385
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
13386
|
+
var DiscoveredTargetSchema = object({
|
|
13387
|
+
kind: string(),
|
|
13388
|
+
suggestedName: string(),
|
|
13389
|
+
config: record(string(), unknown())
|
|
13390
|
+
});
|
|
13391
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
13392
|
+
var RenderedAsSchema = object({
|
|
13393
|
+
level: string(),
|
|
13394
|
+
format: NotificationFormatSchema,
|
|
13395
|
+
attachmentsSent: number().int().nonnegative(),
|
|
13396
|
+
actionsSent: number().int().nonnegative(),
|
|
13397
|
+
truncated: boolean(),
|
|
13398
|
+
dropped: array(string())
|
|
13399
|
+
});
|
|
13400
|
+
var SendResultSchema = object({
|
|
13401
|
+
success: boolean(),
|
|
13402
|
+
error: string().optional(),
|
|
13403
|
+
renderedAs: RenderedAsSchema.optional()
|
|
13404
|
+
});
|
|
13405
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
13406
|
+
var TestResultSchema = SendResultSchema;
|
|
13407
|
+
/**
|
|
13408
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
13409
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
13410
|
+
*/
|
|
13411
|
+
var ProbeTargetConfigResultSchema = object({
|
|
13412
|
+
ok: boolean(),
|
|
13413
|
+
resolvedUrl: string(),
|
|
13414
|
+
httpStatus: number().int().optional(),
|
|
13415
|
+
stateInstance: string().optional(),
|
|
13416
|
+
error: string().optional()
|
|
13417
|
+
});
|
|
13418
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
13419
|
+
kind: string(),
|
|
13420
|
+
config: record(string(), unknown()).optional(),
|
|
13421
|
+
/**
|
|
13422
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
13423
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
13424
|
+
* secrets server-side before dialling (same merge as upsert).
|
|
13425
|
+
*/
|
|
13426
|
+
targetId: string().optional()
|
|
13427
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
13428
|
+
kind: string(),
|
|
13429
|
+
config: record(string(), unknown()),
|
|
13430
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
13431
|
+
targetId: string().optional()
|
|
13432
|
+
}), ProbeTargetConfigResultSchema, {
|
|
13433
|
+
kind: "mutation",
|
|
13434
|
+
auth: "admin"
|
|
13435
|
+
}), method(object({
|
|
13436
|
+
targetId: string(),
|
|
13437
|
+
notification: NotificationSchema
|
|
13438
|
+
}), SendResultSchema, {
|
|
13439
|
+
kind: "mutation",
|
|
13440
|
+
auth: "admin"
|
|
13441
|
+
}), method(object({
|
|
13442
|
+
targetId: string(),
|
|
13443
|
+
sample: NotificationSchema.optional()
|
|
13444
|
+
}), TestResultSchema, {
|
|
13445
|
+
kind: "mutation",
|
|
13446
|
+
auth: "admin"
|
|
13447
|
+
}), method(object({
|
|
13448
|
+
target: TargetSchema,
|
|
13449
|
+
/**
|
|
13450
|
+
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
13451
|
+
* existing row. The UI never holds the stored token; blank / redacted
|
|
13452
|
+
* password fields are restored from this source (D634 still applies if
|
|
13453
|
+
* destinations diverge without a re-typed secret).
|
|
13454
|
+
*/
|
|
13455
|
+
cloneSecretsFrom: string().optional()
|
|
13456
|
+
}), TargetSchema, {
|
|
13457
|
+
kind: "mutation",
|
|
13458
|
+
auth: "admin"
|
|
13459
|
+
}), method(object({ targetId: string() }), _void(), {
|
|
13460
|
+
kind: "mutation",
|
|
13461
|
+
auth: "admin"
|
|
13462
|
+
}), method(object({
|
|
13463
|
+
targetId: string(),
|
|
13464
|
+
enabled: boolean()
|
|
13465
|
+
}), _void(), {
|
|
13466
|
+
kind: "mutation",
|
|
13467
|
+
auth: "admin"
|
|
13468
|
+
});
|
|
13469
|
+
looseObject({
|
|
13470
|
+
name: string().min(1),
|
|
13471
|
+
enabled: boolean().optional()
|
|
13472
|
+
});
|
|
13473
|
+
TargetSchema.omit({ id: true });
|
|
12854
13474
|
var EncodeProfileSchema = object({
|
|
12855
13475
|
video: object({
|
|
12856
13476
|
codec: _enum([
|
|
@@ -19025,31 +19645,21 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19025
19645
|
"pet-fountain": {
|
|
19026
19646
|
scope: "device",
|
|
19027
19647
|
noun: t("Pet fountain", "Fontana per animali"),
|
|
19028
|
-
actions: [
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
args: { on: false }
|
|
19044
|
-
},
|
|
19045
|
-
{
|
|
19046
|
-
id: "petFountain.resetDailyConsumption",
|
|
19047
|
-
method: "resetDailyConsumption",
|
|
19048
|
-
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
19049
|
-
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
19050
|
-
icon: "rotate-ccw"
|
|
19051
|
-
}
|
|
19052
|
-
],
|
|
19648
|
+
actions: [{
|
|
19649
|
+
id: "petFountain.pumpOn",
|
|
19650
|
+
method: "setPump",
|
|
19651
|
+
label: t("Pump on", "Pompa accesa"),
|
|
19652
|
+
phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
|
|
19653
|
+
icon: "play",
|
|
19654
|
+
args: { on: true }
|
|
19655
|
+
}, {
|
|
19656
|
+
id: "petFountain.pumpOff",
|
|
19657
|
+
method: "setPump",
|
|
19658
|
+
label: t("Pump off", "Pompa spenta"),
|
|
19659
|
+
phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
|
|
19660
|
+
icon: "square",
|
|
19661
|
+
args: { on: false }
|
|
19662
|
+
}],
|
|
19053
19663
|
state: [{
|
|
19054
19664
|
kind: "boolean",
|
|
19055
19665
|
field: "empty",
|
|
@@ -21068,7 +21678,6 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
21068
21678
|
"calendar-source": system(INTERNAL),
|
|
21069
21679
|
calendars: system("calendars feed schedules and conditions, not device actions"),
|
|
21070
21680
|
composer: system("device composition — an authoring tool, not an action"),
|
|
21071
|
-
"connection-test": system(INTERNAL),
|
|
21072
21681
|
"core-blocks": system(INTERNAL),
|
|
21073
21682
|
"custom-model-registry": system(ML),
|
|
21074
21683
|
"data-store-provider": system(INTERNAL),
|
|
@@ -42760,644 +43369,230 @@ var StepDeviceTraceSchema = object({
|
|
|
42760
43369
|
deviceId: number().int(),
|
|
42761
43370
|
outcome: _enum(["ok", "error"]),
|
|
42762
43371
|
message: string().optional()
|
|
42763
|
-
});
|
|
42764
|
-
var AutomationStepTraceSchema = object({
|
|
42765
|
-
/** `actions.<i>` */
|
|
42766
|
-
path: string().min(1),
|
|
42767
|
-
kind: string().min(1),
|
|
42768
|
-
cap: string().optional(),
|
|
42769
|
-
method: string().optional(),
|
|
42770
|
-
/** The vocabulary action the step performs (`cover.open`), when it names one. */
|
|
42771
|
-
actionId: string().optional(),
|
|
42772
|
-
deviceId: number().int().optional(),
|
|
42773
|
-
/** What happened per device, when the step drove several (or a domain). */
|
|
42774
|
-
devices: array(StepDeviceTraceSchema).optional(),
|
|
42775
|
-
startedAt: number().optional(),
|
|
42776
|
-
endedAt: number().optional(),
|
|
42777
|
-
/**
|
|
42778
|
-
* The parameters the step SENT, after `{{vars.x}}` substitution — what the
|
|
42779
|
-
* device was actually asked. Bounded like trigger `vars`; a secret parameter
|
|
42780
|
-
* is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
|
|
42781
|
-
*/
|
|
42782
|
-
args: record(string(), TraceValueSchema).optional(),
|
|
42783
|
-
/** `empty`: the target resolved to no device, so nothing was done — never silently `ok`. */
|
|
42784
|
-
outcome: _enum([
|
|
42785
|
-
"ok",
|
|
42786
|
-
"error",
|
|
42787
|
-
"not-run",
|
|
42788
|
-
"empty"
|
|
42789
|
-
]),
|
|
42790
|
-
message: string().optional()
|
|
42791
|
-
});
|
|
42792
|
-
var AutomationRunTraceSchema = object({
|
|
42793
|
-
runId: string().min(1),
|
|
42794
|
-
startedAt: number(),
|
|
42795
|
-
endedAt: number(),
|
|
42796
|
-
trigger: AutomationTriggerTraceSchema,
|
|
42797
|
-
conditions: array(AutomationConditionTraceNodeSchema),
|
|
42798
|
-
verdict: AutomationRunVerdictSchema.optional(),
|
|
42799
|
-
steps: array(AutomationStepTraceSchema),
|
|
42800
|
-
outcome: AutomationRunOutcomeSchema,
|
|
42801
|
-
/** How many identical consecutive skips this trace stands for (>= 1). */
|
|
42802
|
-
repeat: number().int().min(1),
|
|
42803
|
-
/** When the latest of those repeats happened. */
|
|
42804
|
-
lastAt: number(),
|
|
42805
|
-
/**
|
|
42806
|
-
* A scheduler point's own outcome (D983). `outcome` carries the closest
|
|
42807
|
-
* automation outcome so a reader that does not know schedulers still reads
|
|
42808
|
-
* the trace; this field says exactly what happened.
|
|
42809
|
-
*/
|
|
42810
|
-
scheduler: SchedulerOutcomeSchema.optional()
|
|
42811
|
-
});
|
|
42812
|
-
/**
|
|
42813
|
-
* What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
|
|
42814
|
-
* `action:<actionId>`): the cooldown stamp and the trace ring.
|
|
42815
|
-
*/
|
|
42816
|
-
var AutomationStateDocSchema = object({
|
|
42817
|
-
version: literal(1),
|
|
42818
|
-
lastRunStartedAt: number().nullable(),
|
|
42819
|
-
traces: array(AutomationRunTraceSchema)
|
|
42820
|
-
});
|
|
42821
|
-
/**
|
|
42822
|
-
* Actions — named, runnable things an operator (or an automation) can start,
|
|
42823
|
-
* declared as LIST ENTRIES, never as devices.
|
|
42824
|
-
*
|
|
42825
|
-
* An entry is either **internal** (a short list of cap/wait steps kept in the
|
|
42826
|
-
* hub's `actions` record kind) or declared by an addon through the
|
|
42827
|
-
* `action-source` collection (a Home Assistant script, a scene …). The one
|
|
42828
|
-
* surface the UI and automations talk to is the `actions` cap; it keeps no
|
|
42829
|
-
* catalog of its own (D3) — every `list` asks the providers live.
|
|
42830
|
-
*
|
|
42831
|
-
* This file is the data half: the entry schema, the variables an entry may
|
|
42832
|
-
* declare, and the pure resolution of the values a run was given. It holds no
|
|
42833
|
-
* runtime.
|
|
42834
|
-
*/
|
|
42835
|
-
/** What a variable's value is. `select` takes one of `options`. */
|
|
42836
|
-
var ActionVariableTypeSchema = _enum([
|
|
42837
|
-
"string",
|
|
42838
|
-
"number",
|
|
42839
|
-
"boolean",
|
|
42840
|
-
"select"
|
|
42841
|
-
]);
|
|
42842
|
-
var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
42843
|
-
var ActionVariableValueSchema = union([
|
|
42844
|
-
string(),
|
|
42845
|
-
number(),
|
|
42846
|
-
boolean()
|
|
42847
|
-
]);
|
|
42848
|
-
/**
|
|
42849
|
-
* One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
|
|
42850
|
-
* value) or `{{vars.name}}` (inside a string) in their args.
|
|
42851
|
-
*/
|
|
42852
|
-
var ActionVariableSchema = object({
|
|
42853
|
-
name: string().regex(ACTION_VARIABLE_NAME_RE),
|
|
42854
|
-
label: string().min(1).max(120).optional(),
|
|
42855
|
-
type: ActionVariableTypeSchema,
|
|
42856
|
-
required: boolean().optional(),
|
|
42857
|
-
default: ActionVariableValueSchema.optional(),
|
|
42858
|
-
/** The allowed values of a `select`. */
|
|
42859
|
-
options: array(string().min(1)).min(1).optional()
|
|
42860
|
-
}).superRefine((v, ctx) => {
|
|
42861
|
-
if (v.type === "select" && v.options === void 0) ctx.addIssue({
|
|
42862
|
-
code: "custom",
|
|
42863
|
-
message: "a select variable needs options",
|
|
42864
|
-
path: ["options"]
|
|
42865
|
-
});
|
|
42866
|
-
if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
|
|
42867
|
-
code: "custom",
|
|
42868
|
-
message: variableValueError(v, v.default) ?? "invalid default",
|
|
42869
|
-
path: ["default"]
|
|
42870
|
-
});
|
|
42871
|
-
});
|
|
42872
|
-
/** Why `value` is not a legal value for the variable, or null when it is. */
|
|
42873
|
-
function variableValueError(variable, value) {
|
|
42874
|
-
switch (variable.type) {
|
|
42875
|
-
case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
|
|
42876
|
-
case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
|
|
42877
|
-
case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
|
|
42878
|
-
case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
|
|
42879
|
-
}
|
|
42880
|
-
}
|
|
42881
|
-
var ActionSourceKindSchema = _enum(["internal", "addon"]);
|
|
42882
|
-
function isInternalActionId(actionId) {
|
|
42883
|
-
return actionId.startsWith("internal:") && actionId.length > 9;
|
|
42884
|
-
}
|
|
42885
|
-
/** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
|
|
42886
|
-
function addonIdOfActionId(actionId) {
|
|
42887
|
-
if (isInternalActionId(actionId)) return null;
|
|
42888
|
-
const at = actionId.indexOf(":");
|
|
42889
|
-
return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
|
|
42890
|
-
}
|
|
42891
|
-
var ActionStatusSchema = object({
|
|
42892
|
-
isRunning: boolean(),
|
|
42893
|
-
/** Ms epoch the latest run STARTED; null when it never ran. */
|
|
42894
|
-
lastRunAt: number().nullable(),
|
|
42895
|
-
/** The latest run's failure; null on success or when it never ran. */
|
|
42896
|
-
lastError: string().nullable()
|
|
42897
|
-
});
|
|
42898
|
-
var ActionEntrySchema = object({
|
|
42899
|
-
/** `internal:<uuid>` or `<addonId>:<localId>`. */
|
|
42900
|
-
id: string().min(3),
|
|
42901
|
-
name: string().min(1).max(120),
|
|
42902
|
-
/** The addon that declared it; `core-blocks` for an internal one. */
|
|
42903
|
-
addonId: string().min(1),
|
|
42904
|
-
source: ActionSourceKindSchema,
|
|
42905
|
-
/** An addon-declared entry is edited where it was declared. */
|
|
42906
|
-
readOnly: boolean(),
|
|
42907
|
-
/** Internal only: cap and wait steps. No action-in-action, so no cycles. */
|
|
42908
|
-
steps: array(ActionStepSchema).min(1).max(50).optional(),
|
|
42909
|
-
variables: array(ActionVariableSchema).max(20).optional(),
|
|
42910
|
-
status: ActionStatusSchema
|
|
42911
|
-
}).superRefine((entry, ctx) => {
|
|
42912
|
-
if (entry.source === "internal") {
|
|
42913
|
-
if (!isInternalActionId(entry.id)) ctx.addIssue({
|
|
42914
|
-
code: "custom",
|
|
42915
|
-
message: "an internal action id is internal:<uuid>",
|
|
42916
|
-
path: ["id"]
|
|
42917
|
-
});
|
|
42918
|
-
return;
|
|
42919
|
-
}
|
|
42920
|
-
if (entry.steps !== void 0) ctx.addIssue({
|
|
42921
|
-
code: "custom",
|
|
42922
|
-
message: "only an internal action carries steps",
|
|
42923
|
-
path: ["steps"]
|
|
42924
|
-
});
|
|
42925
|
-
if (!entry.readOnly) ctx.addIssue({
|
|
42926
|
-
code: "custom",
|
|
42927
|
-
message: "an addon action is read-only here",
|
|
42928
|
-
path: ["readOnly"]
|
|
42929
|
-
});
|
|
42930
|
-
if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
|
|
42931
|
-
code: "custom",
|
|
42932
|
-
message: "an addon action id is <addonId>:<localId>",
|
|
42933
|
-
path: ["id"]
|
|
42934
|
-
});
|
|
42935
|
-
});
|
|
42936
|
-
/** What an operator writes for an internal action. */
|
|
42937
|
-
var ActionInputSchema = object({
|
|
42938
|
-
name: string().min(1).max(120),
|
|
42939
|
-
steps: array(ActionStepSchema).min(1).max(50),
|
|
42940
|
-
variables: array(ActionVariableSchema).max(20).optional()
|
|
42941
|
-
});
|
|
42942
|
-
/** Who started a run. Absent = the operator pressed it. */
|
|
42943
|
-
var ActionRunOriginSchema = object({
|
|
42944
|
-
/** The automation block whose step started it. */
|
|
42945
|
-
blockId: string().min(1).optional() });
|
|
42946
|
-
/** `started` = the caller did not ask to wait. */
|
|
42947
|
-
var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
|
|
42948
|
-
var ActionRunResultSchema = object({
|
|
42949
|
-
runId: string().min(1),
|
|
42950
|
-
outcome: ActionRunOutcomeSchema,
|
|
42951
|
-
/** Why it did not run, or what failed. */
|
|
42952
|
-
message: string().optional()
|
|
42953
|
-
});
|
|
42954
|
-
_enum([
|
|
42955
|
-
"not-found",
|
|
42956
|
-
"read-only",
|
|
42957
|
-
"invalid-variables",
|
|
42958
|
-
"invalid-steps",
|
|
42959
|
-
"not-linked",
|
|
42960
|
-
"busy"
|
|
42961
|
-
]);
|
|
42962
|
-
object({
|
|
42963
|
-
actionId: string(),
|
|
42964
|
-
runId: string(),
|
|
42965
|
-
isRunning: boolean(),
|
|
42966
|
-
/** Set when the run ended. */
|
|
42967
|
-
outcome: AutomationRunOutcomeSchema.optional(),
|
|
42968
|
-
lastRunAt: number().nullable(),
|
|
42969
|
-
lastError: string().nullable()
|
|
42970
|
-
});
|
|
42971
|
-
/** A bare number accepted as a base unit — the save succeeded, say so. */
|
|
42972
|
-
var UnitWarningSchema = object({
|
|
42973
|
-
/** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
|
|
42974
|
-
path: string(),
|
|
42975
|
-
deviceId: number().int().optional(),
|
|
42976
|
-
field: string(),
|
|
42977
|
-
value: number(),
|
|
42978
|
-
assumedUnit: string(),
|
|
42979
|
-
message: string()
|
|
42980
|
-
});
|
|
42981
|
-
/**
|
|
42982
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
42983
|
-
*
|
|
42984
|
-
* Apprise-derived model (see
|
|
42985
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
42986
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
42987
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
42988
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
42989
|
-
* message to what the kind supports — callers never special-case a service.
|
|
42990
|
-
*
|
|
42991
|
-
* DESIGN DECISIONS (locked):
|
|
42992
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
42993
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
42994
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
42995
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
42996
|
-
* alternative would fork the UI per addon and cannot host the
|
|
42997
|
-
* discovery→adopt flow.
|
|
42998
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
42999
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
43000
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
43001
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
43002
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
43003
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
43004
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
43005
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
43006
|
-
* base64 fallback needed.
|
|
43007
|
-
*
|
|
43008
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
43009
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
43010
|
-
* admin "Integrations" page.
|
|
43011
|
-
*/
|
|
43012
|
-
/**
|
|
43013
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
43014
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
43015
|
-
*/
|
|
43016
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
43017
|
-
"image",
|
|
43018
|
-
"video",
|
|
43019
|
-
"gif",
|
|
43020
|
-
"audio",
|
|
43021
|
-
"icon"
|
|
43022
|
-
]);
|
|
43023
|
-
/**
|
|
43024
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
43025
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
43026
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
43027
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
43028
|
-
*/
|
|
43029
|
-
var AttachmentSchema = object({
|
|
43030
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
43031
|
-
url: string().optional(),
|
|
43032
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
43033
|
-
mime: string().optional(),
|
|
43034
|
-
name: string().optional()
|
|
43035
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
43036
|
-
var NotificationFormatSchema = _enum([
|
|
43037
|
-
"text",
|
|
43038
|
-
"markdown",
|
|
43039
|
-
"html"
|
|
43040
|
-
]);
|
|
43041
|
-
/**
|
|
43042
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
43043
|
-
*
|
|
43044
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
43045
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
43046
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
43047
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
43048
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
43049
|
-
*
|
|
43050
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
43051
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
43052
|
-
* differently.
|
|
43053
|
-
*
|
|
43054
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
43055
|
-
*
|
|
43056
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
43057
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
43058
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
43059
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
43060
|
-
* decides its glyph, which is the only place that decision can be made
|
|
43061
|
-
* honestly.
|
|
43062
|
-
*
|
|
43063
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
43064
|
-
* `disarm` straight through; iOS feeds that string to
|
|
43065
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
43066
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
43067
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
43068
|
-
*
|
|
43069
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
43070
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
43071
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
43072
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
43073
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
43074
|
-
*/
|
|
43075
|
-
var NotificationActionIconSchema = _enum([
|
|
43076
|
-
"acknowledge",
|
|
43077
|
-
"dismiss",
|
|
43078
|
-
"silence",
|
|
43079
|
-
"snooze",
|
|
43080
|
-
"view",
|
|
43081
|
-
"play",
|
|
43082
|
-
"open",
|
|
43083
|
-
"close",
|
|
43084
|
-
"lock",
|
|
43085
|
-
"unlock",
|
|
43086
|
-
"arm",
|
|
43087
|
-
"arm-home",
|
|
43088
|
-
"arm-away",
|
|
43089
|
-
"arm-night",
|
|
43090
|
-
"disarm",
|
|
43091
|
-
"light",
|
|
43092
|
-
"alert",
|
|
43093
|
-
"camera"
|
|
43094
|
-
]);
|
|
43095
|
-
/** A single tap-through action button. */
|
|
43096
|
-
var NotificationActionSchema = object({
|
|
43097
|
-
id: string(),
|
|
43098
|
-
label: string(),
|
|
43099
|
-
url: string().optional(),
|
|
43100
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
43101
|
-
icon: NotificationActionIconSchema.optional(),
|
|
43102
|
-
/**
|
|
43103
|
-
* Renders in a warning style where the notifier supports it.
|
|
43104
|
-
*
|
|
43105
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
43106
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
43107
|
-
* does not buy.
|
|
43108
|
-
*/
|
|
43109
|
-
destructive: boolean().optional(),
|
|
43110
|
-
/**
|
|
43111
|
-
* How the tap should REACH the url.
|
|
43112
|
-
*
|
|
43113
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
43114
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
43115
|
-
* That is right for a button whose answer the operator wants to read.
|
|
43116
|
-
*
|
|
43117
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
43118
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
43119
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
43120
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
43121
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
43122
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
43123
|
-
* a requirement.
|
|
43124
|
-
*/
|
|
43125
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
43126
|
-
});
|
|
43127
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
43128
|
-
var DeliveryParamValueSchema = union([
|
|
43129
|
-
string(),
|
|
43130
|
-
number(),
|
|
43131
|
-
boolean()
|
|
43132
|
-
]);
|
|
43133
|
-
/**
|
|
43134
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
43135
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
43136
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
43137
|
-
* no editor knows a service by name.
|
|
43138
|
-
*
|
|
43139
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
43140
|
-
* run on the addon cap path).
|
|
43141
|
-
*/
|
|
43142
|
-
var DeliveryParamSchema = object({
|
|
43143
|
-
key: string().min(1),
|
|
43144
|
-
label: string(),
|
|
43145
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
43146
|
-
labelKey: string().optional(),
|
|
43147
|
-
type: _enum([
|
|
43148
|
-
"enum",
|
|
43149
|
-
"number",
|
|
43150
|
-
"boolean",
|
|
43151
|
-
"string"
|
|
43152
|
-
]),
|
|
43153
|
-
/** `enum` only: the closed set of accepted values. */
|
|
43154
|
-
options: array(object({
|
|
43155
|
-
value: string(),
|
|
43156
|
-
label: string()
|
|
43157
|
-
})).optional(),
|
|
43158
|
-
/** `number` only. */
|
|
43159
|
-
min: number().optional(),
|
|
43160
|
-
max: number().optional(),
|
|
43161
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
43162
|
-
default: DeliveryParamValueSchema.optional()
|
|
43163
|
-
});
|
|
43164
|
-
/**
|
|
43165
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
43166
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
43167
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
43168
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
43169
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
43170
|
-
* `priority` for that one target.
|
|
43171
|
-
*/
|
|
43172
|
-
var NotificationSchema = object({
|
|
43173
|
-
body: string(),
|
|
43174
|
-
title: string().optional(),
|
|
43175
|
-
format: NotificationFormatSchema.default("text"),
|
|
43176
|
-
priority: number().int().min(1).max(5).default(3),
|
|
43177
|
-
level: string().optional(),
|
|
43178
|
-
attachments: array(AttachmentSchema).optional(),
|
|
43179
|
-
clickUrl: string().optional(),
|
|
43180
|
-
actions: array(NotificationActionSchema).optional(),
|
|
43181
|
-
sound: string().optional(),
|
|
43182
|
-
ttl: number().optional(),
|
|
43183
|
-
tag: string().optional(),
|
|
43184
|
-
deviceId: number().optional(),
|
|
43185
|
-
eventId: string().optional(),
|
|
43186
|
-
metadata: record(string(), unknown()).optional(),
|
|
43372
|
+
});
|
|
43373
|
+
var AutomationStepTraceSchema = object({
|
|
43374
|
+
/** `actions.<i>` */
|
|
43375
|
+
path: string().min(1),
|
|
43376
|
+
kind: string().min(1),
|
|
43377
|
+
cap: string().optional(),
|
|
43378
|
+
method: string().optional(),
|
|
43379
|
+
/** The vocabulary action the step performs (`cover.open`), when it names one. */
|
|
43380
|
+
actionId: string().optional(),
|
|
43381
|
+
deviceId: number().int().optional(),
|
|
43382
|
+
/** What happened per device, when the step drove several (or a domain). */
|
|
43383
|
+
devices: array(StepDeviceTraceSchema).optional(),
|
|
43384
|
+
startedAt: number().optional(),
|
|
43385
|
+
endedAt: number().optional(),
|
|
43187
43386
|
/**
|
|
43188
|
-
*
|
|
43189
|
-
*
|
|
43190
|
-
*
|
|
43387
|
+
* The parameters the step SENT, after `{{vars.x}}` substitution — what the
|
|
43388
|
+
* device was actually asked. Bounded like trigger `vars`; a secret parameter
|
|
43389
|
+
* is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
|
|
43191
43390
|
*/
|
|
43192
|
-
|
|
43193
|
-
|
|
43194
|
-
|
|
43195
|
-
|
|
43196
|
-
|
|
43197
|
-
|
|
43198
|
-
|
|
43199
|
-
|
|
43200
|
-
|
|
43201
|
-
critical: boolean().optional(),
|
|
43202
|
-
silent: boolean().optional(),
|
|
43203
|
-
noPush: boolean().optional()
|
|
43204
|
-
}).optional(),
|
|
43205
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
43206
|
-
requires: array(string()).optional(),
|
|
43207
|
-
description: string().optional()
|
|
43391
|
+
args: record(string(), TraceValueSchema).optional(),
|
|
43392
|
+
/** `empty`: the target resolved to no device, so nothing was done — never silently `ok`. */
|
|
43393
|
+
outcome: _enum([
|
|
43394
|
+
"ok",
|
|
43395
|
+
"error",
|
|
43396
|
+
"not-run",
|
|
43397
|
+
"empty"
|
|
43398
|
+
]),
|
|
43399
|
+
message: string().optional()
|
|
43208
43400
|
});
|
|
43209
|
-
|
|
43210
|
-
|
|
43211
|
-
|
|
43212
|
-
|
|
43213
|
-
|
|
43214
|
-
|
|
43215
|
-
|
|
43216
|
-
|
|
43217
|
-
|
|
43218
|
-
|
|
43219
|
-
|
|
43220
|
-
|
|
43221
|
-
|
|
43222
|
-
actions: number().int().nonnegative(),
|
|
43401
|
+
var AutomationRunTraceSchema = object({
|
|
43402
|
+
runId: string().min(1),
|
|
43403
|
+
startedAt: number(),
|
|
43404
|
+
endedAt: number(),
|
|
43405
|
+
trigger: AutomationTriggerTraceSchema,
|
|
43406
|
+
conditions: array(AutomationConditionTraceNodeSchema),
|
|
43407
|
+
verdict: AutomationRunVerdictSchema.optional(),
|
|
43408
|
+
steps: array(AutomationStepTraceSchema),
|
|
43409
|
+
outcome: AutomationRunOutcomeSchema,
|
|
43410
|
+
/** How many identical consecutive skips this trace stands for (>= 1). */
|
|
43411
|
+
repeat: number().int().min(1),
|
|
43412
|
+
/** When the latest of those repeats happened. */
|
|
43413
|
+
lastAt: number(),
|
|
43223
43414
|
/**
|
|
43224
|
-
*
|
|
43225
|
-
*
|
|
43226
|
-
*
|
|
43227
|
-
* run on the addon cap path — three production failures in one day taught
|
|
43228
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
43229
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
43230
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
43415
|
+
* A scheduler point's own outcome (D983). `outcome` carries the closest
|
|
43416
|
+
* automation outcome so a reader that does not know schedulers still reads
|
|
43417
|
+
* the trace; this field says exactly what happened.
|
|
43231
43418
|
*/
|
|
43232
|
-
|
|
43233
|
-
levels: array(TargetKindLevelSchema),
|
|
43234
|
-
format: array(NotificationFormatSchema),
|
|
43235
|
-
clickUrl: boolean(),
|
|
43236
|
-
sound: boolean(),
|
|
43237
|
-
ttl: boolean(),
|
|
43238
|
-
bodyMaxLen: number().int().positive()
|
|
43419
|
+
scheduler: SchedulerOutcomeSchema.optional()
|
|
43239
43420
|
});
|
|
43240
43421
|
/**
|
|
43241
|
-
*
|
|
43242
|
-
*
|
|
43243
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
43244
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
43245
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
43422
|
+
* What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
|
|
43423
|
+
* `action:<actionId>`): the cooldown stamp and the trace ring.
|
|
43246
43424
|
*/
|
|
43247
|
-
var
|
|
43248
|
-
|
|
43249
|
-
|
|
43250
|
-
|
|
43251
|
-
icon: string(),
|
|
43252
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
43253
|
-
addonId: string(),
|
|
43254
|
-
/**
|
|
43255
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
43256
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
43257
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
43258
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
43259
|
-
*
|
|
43260
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
43261
|
-
* client, and a native client joins it onto its own hub base.
|
|
43262
|
-
*
|
|
43263
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
43264
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
43265
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
43266
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
43267
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
43268
|
-
*/
|
|
43269
|
-
iconUrl: string().optional(),
|
|
43270
|
-
/**
|
|
43271
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
43272
|
-
*
|
|
43273
|
-
* The server knows this and therefore says it, because the client cannot
|
|
43274
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
43275
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
43276
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
43277
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
43278
|
-
*
|
|
43279
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
43280
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
43281
|
-
* its raster path, which is the safe default for an unknown image.
|
|
43282
|
-
*/
|
|
43283
|
-
iconMediaType: string().optional(),
|
|
43284
|
-
configSchema: ConfigSchemaPassthrough$4,
|
|
43285
|
-
/**
|
|
43286
|
-
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
43287
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
43288
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
43289
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
43290
|
-
* on every edit.
|
|
43291
|
-
*
|
|
43292
|
-
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
43293
|
-
* kinds) and from a provider older than this field — an editor that finds
|
|
43294
|
-
* it absent must assume ANY edit may need the secret.
|
|
43295
|
-
*/
|
|
43296
|
-
destinationKeys: array(string()).optional(),
|
|
43297
|
-
supportsDiscovery: boolean(),
|
|
43298
|
-
/**
|
|
43299
|
-
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
43300
|
-
* Absent = none; the rule editor then offers nothing.
|
|
43301
|
-
*/
|
|
43302
|
-
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
43303
|
-
caps: TargetKindCapsSchema
|
|
43425
|
+
var AutomationStateDocSchema = object({
|
|
43426
|
+
version: literal(1),
|
|
43427
|
+
lastRunStartedAt: number().nullable(),
|
|
43428
|
+
traces: array(AutomationRunTraceSchema)
|
|
43304
43429
|
});
|
|
43305
43430
|
/**
|
|
43306
|
-
*
|
|
43307
|
-
*
|
|
43308
|
-
*
|
|
43431
|
+
* Actions — named, runnable things an operator (or an automation) can start,
|
|
43432
|
+
* declared as LIST ENTRIES, never as devices.
|
|
43433
|
+
*
|
|
43434
|
+
* An entry is either **internal** (a short list of cap/wait steps kept in the
|
|
43435
|
+
* hub's `actions` record kind) or declared by an addon through the
|
|
43436
|
+
* `action-source` collection (a Home Assistant script, a scene …). The one
|
|
43437
|
+
* surface the UI and automations talk to is the `actions` cap; it keeps no
|
|
43438
|
+
* catalog of its own (D3) — every `list` asks the providers live.
|
|
43439
|
+
*
|
|
43440
|
+
* This file is the data half: the entry schema, the variables an entry may
|
|
43441
|
+
* declare, and the pure resolution of the values a run was given. It holds no
|
|
43442
|
+
* runtime.
|
|
43309
43443
|
*/
|
|
43310
|
-
|
|
43311
|
-
|
|
43312
|
-
|
|
43313
|
-
|
|
43314
|
-
|
|
43315
|
-
|
|
43316
|
-
|
|
43444
|
+
/** What a variable's value is. `select` takes one of `options`. */
|
|
43445
|
+
var ActionVariableTypeSchema = _enum([
|
|
43446
|
+
"string",
|
|
43447
|
+
"number",
|
|
43448
|
+
"boolean",
|
|
43449
|
+
"select"
|
|
43450
|
+
]);
|
|
43451
|
+
var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
43452
|
+
var ActionVariableValueSchema = union([
|
|
43453
|
+
string(),
|
|
43454
|
+
number(),
|
|
43455
|
+
boolean()
|
|
43456
|
+
]);
|
|
43457
|
+
/**
|
|
43458
|
+
* One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
|
|
43459
|
+
* value) or `{{vars.name}}` (inside a string) in their args.
|
|
43460
|
+
*/
|
|
43461
|
+
var ActionVariableSchema = object({
|
|
43462
|
+
name: string().regex(ACTION_VARIABLE_NAME_RE),
|
|
43463
|
+
label: string().min(1).max(120).optional(),
|
|
43464
|
+
type: ActionVariableTypeSchema,
|
|
43465
|
+
required: boolean().optional(),
|
|
43466
|
+
default: ActionVariableValueSchema.optional(),
|
|
43467
|
+
/** The allowed values of a `select`. */
|
|
43468
|
+
options: array(string().min(1)).min(1).optional()
|
|
43469
|
+
}).superRefine((v, ctx) => {
|
|
43470
|
+
if (v.type === "select" && v.options === void 0) ctx.addIssue({
|
|
43471
|
+
code: "custom",
|
|
43472
|
+
message: "a select variable needs options",
|
|
43473
|
+
path: ["options"]
|
|
43474
|
+
});
|
|
43475
|
+
if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
|
|
43476
|
+
code: "custom",
|
|
43477
|
+
message: variableValueError(v, v.default) ?? "invalid default",
|
|
43478
|
+
path: ["default"]
|
|
43479
|
+
});
|
|
43317
43480
|
});
|
|
43318
|
-
/**
|
|
43319
|
-
|
|
43320
|
-
|
|
43321
|
-
|
|
43322
|
-
|
|
43481
|
+
/** Why `value` is not a legal value for the variable, or null when it is. */
|
|
43482
|
+
function variableValueError(variable, value) {
|
|
43483
|
+
switch (variable.type) {
|
|
43484
|
+
case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
|
|
43485
|
+
case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
|
|
43486
|
+
case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
|
|
43487
|
+
case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
|
|
43488
|
+
}
|
|
43489
|
+
}
|
|
43490
|
+
var ActionSourceKindSchema = _enum(["internal", "addon"]);
|
|
43491
|
+
function isInternalActionId(actionId) {
|
|
43492
|
+
return actionId.startsWith("internal:") && actionId.length > 9;
|
|
43493
|
+
}
|
|
43494
|
+
/** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
|
|
43495
|
+
function addonIdOfActionId(actionId) {
|
|
43496
|
+
if (isInternalActionId(actionId)) return null;
|
|
43497
|
+
const at = actionId.indexOf(":");
|
|
43498
|
+
return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
|
|
43499
|
+
}
|
|
43500
|
+
var ActionStatusSchema = object({
|
|
43501
|
+
isRunning: boolean(),
|
|
43502
|
+
/** Ms epoch the latest run STARTED; null when it never ran. */
|
|
43503
|
+
lastRunAt: number().nullable(),
|
|
43504
|
+
/** The latest run's failure; null on success or when it never ran. */
|
|
43505
|
+
lastError: string().nullable()
|
|
43323
43506
|
});
|
|
43324
|
-
|
|
43325
|
-
|
|
43326
|
-
|
|
43327
|
-
|
|
43328
|
-
|
|
43329
|
-
|
|
43330
|
-
|
|
43331
|
-
|
|
43507
|
+
var ActionEntrySchema = object({
|
|
43508
|
+
/** `internal:<uuid>` or `<addonId>:<localId>`. */
|
|
43509
|
+
id: string().min(3),
|
|
43510
|
+
name: string().min(1).max(120),
|
|
43511
|
+
/** The addon that declared it; `core-blocks` for an internal one. */
|
|
43512
|
+
addonId: string().min(1),
|
|
43513
|
+
source: ActionSourceKindSchema,
|
|
43514
|
+
/** An addon-declared entry is edited where it was declared. */
|
|
43515
|
+
readOnly: boolean(),
|
|
43516
|
+
/** Internal only: cap and wait steps. No action-in-action, so no cycles. */
|
|
43517
|
+
steps: array(ActionStepSchema).min(1).max(50).optional(),
|
|
43518
|
+
variables: array(ActionVariableSchema).max(20).optional(),
|
|
43519
|
+
status: ActionStatusSchema
|
|
43520
|
+
}).superRefine((entry, ctx) => {
|
|
43521
|
+
if (entry.source === "internal") {
|
|
43522
|
+
if (!isInternalActionId(entry.id)) ctx.addIssue({
|
|
43523
|
+
code: "custom",
|
|
43524
|
+
message: "an internal action id is internal:<uuid>",
|
|
43525
|
+
path: ["id"]
|
|
43526
|
+
});
|
|
43527
|
+
return;
|
|
43528
|
+
}
|
|
43529
|
+
if (entry.steps !== void 0) ctx.addIssue({
|
|
43530
|
+
code: "custom",
|
|
43531
|
+
message: "only an internal action carries steps",
|
|
43532
|
+
path: ["steps"]
|
|
43533
|
+
});
|
|
43534
|
+
if (!entry.readOnly) ctx.addIssue({
|
|
43535
|
+
code: "custom",
|
|
43536
|
+
message: "an addon action is read-only here",
|
|
43537
|
+
path: ["readOnly"]
|
|
43538
|
+
});
|
|
43539
|
+
if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
|
|
43540
|
+
code: "custom",
|
|
43541
|
+
message: "an addon action id is <addonId>:<localId>",
|
|
43542
|
+
path: ["id"]
|
|
43543
|
+
});
|
|
43332
43544
|
});
|
|
43333
|
-
|
|
43334
|
-
|
|
43335
|
-
|
|
43336
|
-
|
|
43545
|
+
/** What an operator writes for an internal action. */
|
|
43546
|
+
var ActionInputSchema = object({
|
|
43547
|
+
name: string().min(1).max(120),
|
|
43548
|
+
steps: array(ActionStepSchema).min(1).max(50),
|
|
43549
|
+
variables: array(ActionVariableSchema).max(20).optional()
|
|
43337
43550
|
});
|
|
43338
|
-
/**
|
|
43339
|
-
var
|
|
43551
|
+
/** Who started a run. Absent = the operator pressed it. */
|
|
43552
|
+
var ActionRunOriginSchema = object({
|
|
43553
|
+
/** The automation block whose step started it. */
|
|
43554
|
+
blockId: string().min(1).optional() });
|
|
43340
43555
|
/**
|
|
43341
|
-
*
|
|
43342
|
-
* `
|
|
43556
|
+
* `started` = the caller did not ask to wait. `timed-out` = the run reached the
|
|
43557
|
+
* ceiling (`ACTION_RUN_MAX_MS`) and was stopped; it is never a `ran`.
|
|
43343
43558
|
*/
|
|
43344
|
-
var
|
|
43345
|
-
|
|
43346
|
-
|
|
43347
|
-
|
|
43348
|
-
|
|
43349
|
-
|
|
43559
|
+
var ActionRunOutcomeSchema = union([
|
|
43560
|
+
AutomationRunOutcomeSchema,
|
|
43561
|
+
literal("started"),
|
|
43562
|
+
literal("timed-out")
|
|
43563
|
+
]);
|
|
43564
|
+
var ActionRunResultSchema = object({
|
|
43565
|
+
runId: string().min(1),
|
|
43566
|
+
outcome: ActionRunOutcomeSchema,
|
|
43567
|
+
/** Why it did not run, or what failed. */
|
|
43568
|
+
message: string().optional()
|
|
43350
43569
|
});
|
|
43351
|
-
|
|
43352
|
-
|
|
43353
|
-
|
|
43354
|
-
|
|
43355
|
-
|
|
43356
|
-
|
|
43357
|
-
|
|
43358
|
-
|
|
43359
|
-
|
|
43360
|
-
|
|
43361
|
-
|
|
43362
|
-
|
|
43363
|
-
/**
|
|
43364
|
-
|
|
43365
|
-
|
|
43366
|
-
|
|
43367
|
-
|
|
43368
|
-
|
|
43369
|
-
|
|
43370
|
-
|
|
43371
|
-
|
|
43372
|
-
|
|
43373
|
-
|
|
43374
|
-
|
|
43375
|
-
|
|
43376
|
-
|
|
43377
|
-
}), TestResultSchema, {
|
|
43378
|
-
kind: "mutation",
|
|
43379
|
-
auth: "admin"
|
|
43380
|
-
}), method(object({
|
|
43381
|
-
target: TargetSchema,
|
|
43382
|
-
/**
|
|
43383
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
43384
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
43385
|
-
* password fields are restored from this source (D634 still applies if
|
|
43386
|
-
* destinations diverge without a re-typed secret).
|
|
43387
|
-
*/
|
|
43388
|
-
cloneSecretsFrom: string().optional()
|
|
43389
|
-
}), TargetSchema, {
|
|
43390
|
-
kind: "mutation",
|
|
43391
|
-
auth: "admin"
|
|
43392
|
-
}), method(object({ targetId: string() }), _void(), {
|
|
43393
|
-
kind: "mutation",
|
|
43394
|
-
auth: "admin"
|
|
43395
|
-
}), method(object({
|
|
43396
|
-
targetId: string(),
|
|
43397
|
-
enabled: boolean()
|
|
43398
|
-
}), _void(), {
|
|
43399
|
-
kind: "mutation",
|
|
43400
|
-
auth: "admin"
|
|
43570
|
+
_enum([
|
|
43571
|
+
"not-found",
|
|
43572
|
+
"read-only",
|
|
43573
|
+
"invalid-variables",
|
|
43574
|
+
"invalid-steps",
|
|
43575
|
+
"not-linked",
|
|
43576
|
+
"busy"
|
|
43577
|
+
]);
|
|
43578
|
+
object({
|
|
43579
|
+
actionId: string(),
|
|
43580
|
+
runId: string(),
|
|
43581
|
+
isRunning: boolean(),
|
|
43582
|
+
/** Set when the run ended. */
|
|
43583
|
+
outcome: AutomationRunOutcomeSchema.optional(),
|
|
43584
|
+
lastRunAt: number().nullable(),
|
|
43585
|
+
lastError: string().nullable()
|
|
43586
|
+
});
|
|
43587
|
+
/** A bare number accepted as a base unit — the save succeeded, say so. */
|
|
43588
|
+
var UnitWarningSchema = object({
|
|
43589
|
+
/** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
|
|
43590
|
+
path: string(),
|
|
43591
|
+
deviceId: number().int().optional(),
|
|
43592
|
+
field: string(),
|
|
43593
|
+
value: number(),
|
|
43594
|
+
assumedUnit: string(),
|
|
43595
|
+
message: string()
|
|
43401
43596
|
});
|
|
43402
43597
|
_enum([
|
|
43403
43598
|
"automation",
|
|
@@ -43466,6 +43661,8 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
|
43466
43661
|
kind: "mutation",
|
|
43467
43662
|
auth: "admin"
|
|
43468
43663
|
});
|
|
43664
|
+
/** `timeoutMs` of `actions.run`: a waited call outlives any legal run. */
|
|
43665
|
+
var ACTION_RUN_TIMEOUT_MS = 363e4;
|
|
43469
43666
|
/**
|
|
43470
43667
|
* actions — the ONE surface for runnable actions, for the UI and for
|
|
43471
43668
|
* automations. Provided by the core-blocks builtin on the hub.
|
|
@@ -43484,6 +43681,12 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
|
43484
43681
|
* An admin, and an in-process caller (an automation block), may run any action.
|
|
43485
43682
|
* Every refusal is typed (`[not-linked] …`) and logged.
|
|
43486
43683
|
*
|
|
43684
|
+
* **A run lasts at most `ACTION_RUN_MAX_MS` (60 min).** The provider stops a
|
|
43685
|
+
* run that reaches it and answers `timed-out` (never `ran`); an action whose
|
|
43686
|
+
* `wait` steps alone add up to more is refused at create/update
|
|
43687
|
+
* (`[invalid-steps]`). `run` declares a `timeoutMs` above that ceiling, so a
|
|
43688
|
+
* waited call (an automation step) is never cut by the 60 s transport default.
|
|
43689
|
+
*
|
|
43487
43690
|
* A second `run` of an action that is already running is REFUSED — traced and
|
|
43488
43691
|
* logged — never queued.
|
|
43489
43692
|
*/
|
|
@@ -43510,7 +43713,8 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
|
43510
43713
|
role: "viewer",
|
|
43511
43714
|
auth: "protected",
|
|
43512
43715
|
kind: "mutation",
|
|
43513
|
-
caller: "required"
|
|
43716
|
+
caller: "required",
|
|
43717
|
+
timeoutMs: ACTION_RUN_TIMEOUT_MS
|
|
43514
43718
|
}), method(ActionIdInput, _void(), {
|
|
43515
43719
|
kind: "mutation",
|
|
43516
43720
|
auth: "admin"
|
|
@@ -47239,37 +47443,6 @@ method(object({
|
|
|
47239
47443
|
kind: "mutation",
|
|
47240
47444
|
auth: "admin"
|
|
47241
47445
|
}), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
|
|
47242
|
-
var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
|
|
47243
|
-
object({
|
|
47244
|
-
outcome: literal("validated"),
|
|
47245
|
-
/** Round-trip of the sign-in, when the provider measured it. */
|
|
47246
|
-
latencyMs: number().nonnegative().optional(),
|
|
47247
|
-
/** Optional human detail worth showing next to the tick
|
|
47248
|
-
* ("3 devices visible on this account"). */
|
|
47249
|
-
detail: string().optional()
|
|
47250
|
-
}).strict(),
|
|
47251
|
-
object({
|
|
47252
|
-
outcome: literal("rejected"),
|
|
47253
|
-
error: string()
|
|
47254
|
-
}).strict(),
|
|
47255
|
-
object({
|
|
47256
|
-
outcome: literal("inconclusive"),
|
|
47257
|
-
error: string()
|
|
47258
|
-
}).strict()
|
|
47259
|
-
]);
|
|
47260
|
-
var ConnectionTestInputSchema = object({
|
|
47261
|
-
/** Candidate integration settings, exactly as the create form collected them. */
|
|
47262
|
-
settings: record(string(), unknown()) });
|
|
47263
|
-
/**
|
|
47264
|
-
* What the provider's test actually DOES, so the UI can say it in words before
|
|
47265
|
-
* the operator presses the button ("Signs in to the Dreo cloud"). Purely
|
|
47266
|
-
* descriptive — it never changes routing.
|
|
47267
|
-
*/
|
|
47268
|
-
var ConnectionTestDescriptorSchema = object({ label: string() });
|
|
47269
|
-
method(ConnectionTestInputSchema, ConnectionTestOutcomeSchema, {
|
|
47270
|
-
kind: "mutation",
|
|
47271
|
-
auth: "admin"
|
|
47272
|
-
}), method(_void(), ConnectionTestDescriptorSchema, { auth: "admin" });
|
|
47273
47446
|
object({
|
|
47274
47447
|
/** True when the upstream system considers the entity connected. */
|
|
47275
47448
|
connected: boolean(),
|
|
@@ -47527,6 +47700,9 @@ var MediaPlayerStatusSchema = object({
|
|
|
47527
47700
|
/** Repeat mode. Null when no repeat surface.
|
|
47528
47701
|
* Pair with `DeviceFeature.MediaPlayerRepeat`. */
|
|
47529
47702
|
repeat: MediaPlayerRepeatSchema.nullable(),
|
|
47703
|
+
/** Ms epoch when `position` was read from the device. Null when the driver
|
|
47704
|
+
* does not say; a UI that advances the position while playing counts from it. */
|
|
47705
|
+
positionUpdatedAt: number().nullable().default(null),
|
|
47530
47706
|
/** Ms epoch when the slice was last updated. */
|
|
47531
47707
|
lastChangedAt: number()
|
|
47532
47708
|
});
|
|
@@ -53061,6 +53237,7 @@ var IntegrationWithStateSchema = object({
|
|
|
53061
53237
|
id: string(),
|
|
53062
53238
|
addonId: string(),
|
|
53063
53239
|
name: string(),
|
|
53240
|
+
/** A projection of the instance body's switch (`setInstanceEnabled` is the only writer). */
|
|
53064
53241
|
enabled: boolean(),
|
|
53065
53242
|
info: record(string(), unknown()).readonly(),
|
|
53066
53243
|
/** The discovery candidate this integration was set up from (D984). */
|
|
@@ -53073,6 +53250,7 @@ var IntegrationLiteSchema = object({
|
|
|
53073
53250
|
id: string(),
|
|
53074
53251
|
addonId: string(),
|
|
53075
53252
|
name: string(),
|
|
53253
|
+
/** A projection of the instance body's switch (`setInstanceEnabled` is the only writer). */
|
|
53076
53254
|
enabled: boolean(),
|
|
53077
53255
|
info: record(string(), unknown()).readonly(),
|
|
53078
53256
|
/** The discovery candidate this integration was set up from (D984). */
|
|
@@ -53118,10 +53296,9 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
53118
53296
|
* locations" checkbox. Provider-declared in the addon manifest. */
|
|
53119
53297
|
supportsLocationImport: boolean(),
|
|
53120
53298
|
/**
|
|
53121
|
-
* True when this integration
|
|
53122
|
-
*
|
|
53123
|
-
*
|
|
53124
|
-
* rather than offering a button that always answers the same nonsense.
|
|
53299
|
+
* True when this integration's instances declare a Test (`integration-instances.describe().canTest`,
|
|
53300
|
+
* D1016). An integration that cannot be tested must say so up front rather than offering a
|
|
53301
|
+
* button that always answers the same nonsense.
|
|
53125
53302
|
*/
|
|
53126
53303
|
canTest: boolean(),
|
|
53127
53304
|
existingInstances: array(object({
|
|
@@ -53172,15 +53349,21 @@ var TestConnectionResultSchema = object({
|
|
|
53172
53349
|
var CreateIntegrationInputSchema = object({
|
|
53173
53350
|
addonId: string(),
|
|
53174
53351
|
name: string(),
|
|
53175
|
-
enabled: boolean().optional(),
|
|
53176
53352
|
info: record(string(), unknown()).optional(),
|
|
53177
53353
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
53178
53354
|
originStableId: string().optional(),
|
|
53179
|
-
settings: record(string(), unknown()).optional()
|
|
53355
|
+
settings: record(string(), unknown()).optional(),
|
|
53356
|
+
/** The instance kind being created, for a provider with several; omitted = its only testable kind. */
|
|
53357
|
+
kind: string().min(1).optional()
|
|
53180
53358
|
});
|
|
53181
53359
|
var UpdateIntegrationInputSchema = object({
|
|
53182
53360
|
id: string(),
|
|
53183
53361
|
name: string().optional(),
|
|
53362
|
+
/**
|
|
53363
|
+
* Accepted for a FIXED builtin only (`info.fixed`: alarm panel, camera grid, core blocks,
|
|
53364
|
+
* terminal), which keeps no instance record: the registry flag is its one store. Any other
|
|
53365
|
+
* integration is switched through `integration-instances.setInstanceEnabled`.
|
|
53366
|
+
*/
|
|
53184
53367
|
enabled: boolean().optional(),
|
|
53185
53368
|
info: record(string(), unknown()).optional()
|
|
53186
53369
|
});
|
|
@@ -53197,13 +53380,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
53197
53380
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
53198
53381
|
kind: "mutation",
|
|
53199
53382
|
auth: "admin"
|
|
53200
|
-
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" })
|
|
53201
|
-
addonId: string(),
|
|
53202
|
-
settings: record(string(), unknown())
|
|
53203
|
-
}), TestConnectionResultSchema, {
|
|
53204
|
-
kind: "mutation",
|
|
53205
|
-
auth: "admin"
|
|
53206
|
-
});
|
|
53383
|
+
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" });
|
|
53207
53384
|
/**
|
|
53208
53385
|
* `integration-instances` - the connection of an addon that holds ONE or SEVERAL
|
|
53209
53386
|
* (an account, a relay, a destination) as typed records (D985, D986). The addon's
|
|
@@ -53228,7 +53405,7 @@ var IntegrationInstanceIdSchema = string().min(1);
|
|
|
53228
53405
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
53229
53406
|
/**
|
|
53230
53407
|
* What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
|
|
53231
|
-
* instance is an integration that brings devices (
|
|
53408
|
+
* instance is an integration that brings devices (its record body owns the on/off switch);
|
|
53232
53409
|
* `services`: a connection configured from the addon's own function page, never listed on
|
|
53233
53410
|
* Integrations.
|
|
53234
53411
|
* It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
|
|
@@ -53345,8 +53522,8 @@ var IntegrationInstanceSchema = object({
|
|
|
53345
53522
|
configured: boolean(),
|
|
53346
53523
|
/**
|
|
53347
53524
|
* The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
|
|
53348
|
-
* so a row written before the field existed is on).
|
|
53349
|
-
*
|
|
53525
|
+
* so a row written before the field existed is on). The same for every role, `devices` included:
|
|
53526
|
+
* the body is the one store, `setInstanceEnabled` the one writer (epic #440, step 3).
|
|
53350
53527
|
*/
|
|
53351
53528
|
enabled: boolean().optional(),
|
|
53352
53529
|
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
@@ -56182,6 +56359,8 @@ var MigrationIntegrationSchema = object({
|
|
|
56182
56359
|
addonId: string(),
|
|
56183
56360
|
name: string(),
|
|
56184
56361
|
info: Rows,
|
|
56362
|
+
/** The registry's stored switch: read by the one-switch migration, nothing else. */
|
|
56363
|
+
enabled: boolean().optional(),
|
|
56185
56364
|
originStableId: string().optional()
|
|
56186
56365
|
});
|
|
56187
56366
|
var MigrationDeviceSchema = object({
|
|
@@ -61418,13 +61597,6 @@ var AddBrokerInputSchema = object({
|
|
|
61418
61597
|
});
|
|
61419
61598
|
var AddBrokerResultSchema = object({ id: string() });
|
|
61420
61599
|
var IdInputSchema = object({ id: string() });
|
|
61421
|
-
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
61422
|
-
ok: literal(true),
|
|
61423
|
-
latencyMs: number()
|
|
61424
|
-
}), object({
|
|
61425
|
-
ok: literal(false),
|
|
61426
|
-
error: string()
|
|
61427
|
-
})]);
|
|
61428
61600
|
var StartEmbeddedInputSchema = object({
|
|
61429
61601
|
port: number().int().min(1).max(65535).default(1883),
|
|
61430
61602
|
/** Allow anonymous connect (no username/password). Default: false. */
|
|
@@ -61447,9 +61619,6 @@ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnection
|
|
|
61447
61619
|
}), method(IdInputSchema, _void(), {
|
|
61448
61620
|
kind: "mutation",
|
|
61449
61621
|
auth: "admin"
|
|
61450
|
-
}), method(IdInputSchema, TestResultSchema$1, {
|
|
61451
|
-
kind: "mutation",
|
|
61452
|
-
auth: "admin"
|
|
61453
61622
|
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
61454
61623
|
kind: "mutation",
|
|
61455
61624
|
auth: "admin"
|
|
@@ -65700,14 +65869,6 @@ object({
|
|
|
65700
65869
|
empty: boolean().nullable(),
|
|
65701
65870
|
/** True while the pump is running. Null while unknown. */
|
|
65702
65871
|
pumpRunning: boolean().nullable(),
|
|
65703
|
-
/** Water drunk in the last drink, in litres. */
|
|
65704
|
-
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65705
|
-
/** Water drunk in the current hour, in litres. */
|
|
65706
|
-
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65707
|
-
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
65708
|
-
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65709
|
-
/** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
|
|
65710
|
-
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65711
65872
|
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
65712
65873
|
error: string().nullable(),
|
|
65713
65874
|
/** Ms epoch when the slice was last refreshed. */
|
|
@@ -65720,9 +65881,6 @@ DeviceType.PetFountain, method(object({
|
|
|
65720
65881
|
}), _void(), {
|
|
65721
65882
|
kind: "mutation",
|
|
65722
65883
|
auth: "protected"
|
|
65723
|
-
}), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
65724
|
-
kind: "mutation",
|
|
65725
|
-
auth: "protected"
|
|
65726
65884
|
});
|
|
65727
65885
|
/**
|
|
65728
65886
|
* The wire shapes of the Auto tune plan and apply (D745), used by the
|
|
@@ -73072,7 +73230,7 @@ var TrainingProviderTypeSchema = object({
|
|
|
73072
73230
|
label: string(),
|
|
73073
73231
|
/** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
|
|
73074
73232
|
iconAsset: string(),
|
|
73075
|
-
/** Owning `training-provider` addon
|
|
73233
|
+
/** Owning `training-provider` addon. */
|
|
73076
73234
|
addonId: string(),
|
|
73077
73235
|
/**
|
|
73078
73236
|
* Fields for one entry of this type (credentials + defaults). Absent on a
|
|
@@ -73117,36 +73275,6 @@ var TrainingProviderDescriptorSchema = object({
|
|
|
73117
73275
|
*/
|
|
73118
73276
|
formValues: record(string(), unknown()).optional()
|
|
73119
73277
|
});
|
|
73120
|
-
/**
|
|
73121
|
-
* Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
|
|
73122
|
-
* An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
|
|
73123
|
-
* `{ $secretClear: true }` clears it.
|
|
73124
|
-
*/
|
|
73125
|
-
var TrainingProviderUpdateEntryInputSchema = object({
|
|
73126
|
-
entryId: string().min(1),
|
|
73127
|
-
name: string().min(1),
|
|
73128
|
-
tokenId: string().optional(),
|
|
73129
|
-
tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
|
|
73130
|
-
environment: string().optional(),
|
|
73131
|
-
gpuType: string().optional(),
|
|
73132
|
-
region: string().optional(),
|
|
73133
|
-
costCapUsd: number().nonnegative().optional(),
|
|
73134
|
-
maxRuntimeSec: number().int().positive().optional(),
|
|
73135
|
-
failedDatasetGraceHours: number().nonnegative().optional()
|
|
73136
|
-
});
|
|
73137
|
-
var TrainingCredentialCheckSchema = object({
|
|
73138
|
-
ok: boolean(),
|
|
73139
|
-
/** Workspace / account name, when the provider says. */
|
|
73140
|
-
account: string().optional(),
|
|
73141
|
-
reason: _enum([
|
|
73142
|
-
"missing",
|
|
73143
|
-
"invalid",
|
|
73144
|
-
"forbidden",
|
|
73145
|
-
"network",
|
|
73146
|
-
"quota"
|
|
73147
|
-
]).optional(),
|
|
73148
|
-
detail: string().optional()
|
|
73149
|
-
});
|
|
73150
73278
|
var ProviderJobStateSchema = _enum([
|
|
73151
73279
|
"pending",
|
|
73152
73280
|
"starting",
|
|
@@ -73194,22 +73322,6 @@ var TrainingLogLineSchema = object({
|
|
|
73194
73322
|
text: string()
|
|
73195
73323
|
});
|
|
73196
73324
|
method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderTypeSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderEntrySchema).readonly(), { auth: "admin" }), method(object({
|
|
73197
|
-
typeId: string().min(1),
|
|
73198
|
-
name: string().min(1)
|
|
73199
|
-
}), object({ entryId: string() }), {
|
|
73200
|
-
kind: "mutation",
|
|
73201
|
-
auth: "admin"
|
|
73202
|
-
}), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
|
|
73203
|
-
kind: "mutation",
|
|
73204
|
-
auth: "admin"
|
|
73205
|
-
}), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
|
|
73206
|
-
kind: "mutation",
|
|
73207
|
-
auth: "admin"
|
|
73208
|
-
}), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
|
|
73209
|
-
kind: "mutation",
|
|
73210
|
-
auth: "admin",
|
|
73211
|
-
timeoutMs: 3e4
|
|
73212
|
-
}), method(object({
|
|
73213
73325
|
entryId: string().min(1),
|
|
73214
73326
|
gpuType: string(),
|
|
73215
73327
|
region: string(),
|
|
@@ -76595,20 +76707,6 @@ Object.freeze({
|
|
|
76595
76707
|
access: "view",
|
|
76596
76708
|
role: "admin"
|
|
76597
76709
|
},
|
|
76598
|
-
"connectionTest.describeTest": {
|
|
76599
|
-
capName: "connection-test",
|
|
76600
|
-
capScope: "system",
|
|
76601
|
-
addonId: null,
|
|
76602
|
-
access: "view",
|
|
76603
|
-
role: "admin"
|
|
76604
|
-
},
|
|
76605
|
-
"connectionTest.testSettings": {
|
|
76606
|
-
capName: "connection-test",
|
|
76607
|
-
capScope: "system",
|
|
76608
|
-
addonId: null,
|
|
76609
|
-
access: "create",
|
|
76610
|
-
role: "admin"
|
|
76611
|
-
},
|
|
76612
76710
|
"connectivity.getStatus": {
|
|
76613
76711
|
capName: "connectivity",
|
|
76614
76712
|
capScope: "device",
|
|
@@ -78975,13 +79073,6 @@ Object.freeze({
|
|
|
78975
79073
|
access: "view",
|
|
78976
79074
|
role: "admin"
|
|
78977
79075
|
},
|
|
78978
|
-
"integrations.testConnection": {
|
|
78979
|
-
capName: "integrations",
|
|
78980
|
-
capScope: "system",
|
|
78981
|
-
addonId: null,
|
|
78982
|
-
access: "create",
|
|
78983
|
-
role: "admin"
|
|
78984
|
-
},
|
|
78985
79076
|
"integrations.update": {
|
|
78986
79077
|
capName: "integrations",
|
|
78987
79078
|
capScope: "system",
|
|
@@ -80214,13 +80305,6 @@ Object.freeze({
|
|
|
80214
80305
|
access: "create",
|
|
80215
80306
|
role: "admin"
|
|
80216
80307
|
},
|
|
80217
|
-
"mqttBroker.testConnection": {
|
|
80218
|
-
capName: "mqtt-broker",
|
|
80219
|
-
capScope: "system",
|
|
80220
|
-
addonId: null,
|
|
80221
|
-
access: "create",
|
|
80222
|
-
role: "admin"
|
|
80223
|
-
},
|
|
80224
80308
|
"nativeObjectDetection.getOptions": {
|
|
80225
80309
|
capName: "native-object-detection",
|
|
80226
80310
|
capScope: "device",
|
|
@@ -81096,13 +81180,6 @@ Object.freeze({
|
|
|
81096
81180
|
access: "view",
|
|
81097
81181
|
role: "viewer"
|
|
81098
81182
|
},
|
|
81099
|
-
"petFountain.resetDailyConsumption": {
|
|
81100
|
-
capName: "pet-fountain",
|
|
81101
|
-
capScope: "device",
|
|
81102
|
-
addonId: null,
|
|
81103
|
-
access: "delete",
|
|
81104
|
-
role: "viewer"
|
|
81105
|
-
},
|
|
81106
81183
|
"petFountain.setPump": {
|
|
81107
81184
|
capName: "pet-fountain",
|
|
81108
81185
|
capScope: "device",
|
|
@@ -85058,20 +85135,6 @@ Object.freeze({
|
|
|
85058
85135
|
access: "create",
|
|
85059
85136
|
role: "admin"
|
|
85060
85137
|
},
|
|
85061
|
-
"trainingProvider.createEntry": {
|
|
85062
|
-
capName: "training-provider",
|
|
85063
|
-
capScope: "system",
|
|
85064
|
-
addonId: null,
|
|
85065
|
-
access: "create",
|
|
85066
|
-
role: "admin"
|
|
85067
|
-
},
|
|
85068
|
-
"trainingProvider.deleteEntry": {
|
|
85069
|
-
capName: "training-provider",
|
|
85070
|
-
capScope: "system",
|
|
85071
|
-
addonId: null,
|
|
85072
|
-
access: "delete",
|
|
85073
|
-
role: "admin"
|
|
85074
|
-
},
|
|
85075
85138
|
"trainingProvider.describe": {
|
|
85076
85139
|
capName: "training-provider",
|
|
85077
85140
|
capScope: "system",
|
|
@@ -85142,13 +85205,6 @@ Object.freeze({
|
|
|
85142
85205
|
access: "create",
|
|
85143
85206
|
role: "admin"
|
|
85144
85207
|
},
|
|
85145
|
-
"trainingProvider.updateEntry": {
|
|
85146
|
-
capName: "training-provider",
|
|
85147
|
-
capScope: "system",
|
|
85148
|
-
addonId: null,
|
|
85149
|
-
access: "create",
|
|
85150
|
-
role: "admin"
|
|
85151
|
-
},
|
|
85152
85208
|
"trainingProvider.uploadDataset": {
|
|
85153
85209
|
capName: "training-provider",
|
|
85154
85210
|
capScope: "system",
|
|
@@ -85156,13 +85212,6 @@ Object.freeze({
|
|
|
85156
85212
|
access: "create",
|
|
85157
85213
|
role: "admin"
|
|
85158
85214
|
},
|
|
85159
|
-
"trainingProvider.validateCredentials": {
|
|
85160
|
-
capName: "training-provider",
|
|
85161
|
-
capScope: "system",
|
|
85162
|
-
addonId: null,
|
|
85163
|
-
access: "create",
|
|
85164
|
-
role: "admin"
|
|
85165
|
-
},
|
|
85166
85215
|
"turnProvider.getTurnServers": {
|
|
85167
85216
|
capName: "turn-provider",
|
|
85168
85217
|
capScope: "system",
|
|
@@ -87499,11 +87548,6 @@ Object.freeze({
|
|
|
87499
87548
|
form: "single",
|
|
87500
87549
|
optional: false
|
|
87501
87550
|
}],
|
|
87502
|
-
"petFountain.resetDailyConsumption": [{
|
|
87503
|
-
name: "deviceId",
|
|
87504
|
-
form: "single",
|
|
87505
|
-
optional: false
|
|
87506
|
-
}],
|
|
87507
87551
|
"petFountain.setPump": [{
|
|
87508
87552
|
name: "deviceId",
|
|
87509
87553
|
form: "single",
|
|
@@ -90247,7 +90291,6 @@ object({
|
|
|
90247
90291
|
delay: number().nonnegative().optional(),
|
|
90248
90292
|
commands: record(string(), unknown())
|
|
90249
90293
|
});
|
|
90250
|
-
entryBody({ brokerId: string() });
|
|
90251
90294
|
//#endregion
|
|
90252
90295
|
//#region src/addon.ts
|
|
90253
90296
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -90269,7 +90312,7 @@ var AgentUIAddon = class extends BaseAddon {
|
|
|
90269
90312
|
capability: adminUiCapability,
|
|
90270
90313
|
provider: {
|
|
90271
90314
|
getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
|
|
90272
|
-
getVersion: async () => ({ version: "1.2.
|
|
90315
|
+
getVersion: async () => ({ version: "1.2.335" })
|
|
90273
90316
|
}
|
|
90274
90317
|
}];
|
|
90275
90318
|
}
|