@camstack/addon-smtp-nodemailer 1.2.150 → 1.2.152
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/smtp.addon.js +917 -1126
- package/dist/smtp.addon.mjs +917 -1126
- package/package.json +2 -2
package/dist/smtp.addon.js
CHANGED
|
@@ -38,7 +38,7 @@ var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["modu
|
|
|
38
38
|
//#endregion
|
|
39
39
|
let node_crypto = require("node:crypto");
|
|
40
40
|
let node_net = require("node:net");
|
|
41
|
-
//#region ../types/dist/event-category-
|
|
41
|
+
//#region ../types/dist/event-category-78tcBemo.mjs
|
|
42
42
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
43
43
|
EventCategory["SystemBoot"] = "system.boot";
|
|
44
44
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -159,6 +159,23 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
159
159
|
*/
|
|
160
160
|
EventCategory["AddonLoadRecovered"] = "addon.load-recovered";
|
|
161
161
|
/**
|
|
162
|
+
* An addon is RUNNING although its installed package is below what the
|
|
163
|
+
* running server requires (`camstack.requires.addons` of the server, or the
|
|
164
|
+
* addon's own `requires.server`). Raised once per streak by the unsupported-
|
|
165
|
+
* addon tracker, on the hub and on agents; AlertCenter turns it into one
|
|
166
|
+
* operator alert per node and package. The addon is never held back for it.
|
|
167
|
+
*
|
|
168
|
+
* Payload: `{ packageName, addonId, nodeId, installedVersion, requiredMinimum, serverVersion, unmet }`.
|
|
169
|
+
*/
|
|
170
|
+
EventCategory["AddonUnsupportedVersion"] = "addon.unsupported-version";
|
|
171
|
+
/**
|
|
172
|
+
* The package of an {@link AddonUnsupportedVersion} alert reached a supported
|
|
173
|
+
* version, or is no longer installed on the node. AlertCenter dismisses the matching alert.
|
|
174
|
+
*
|
|
175
|
+
* Payload: `{ packageName, addonId, nodeId }`.
|
|
176
|
+
*/
|
|
177
|
+
EventCategory["AddonUnsupportedResolved"] = "addon.unsupported-resolved";
|
|
178
|
+
/**
|
|
162
179
|
* Monitor scheduled the next retry for a failed addon. Transient —
|
|
163
180
|
* surfaced to the UI for live-updating the "next retry in Ns"
|
|
164
181
|
* countdown on the Addons page row, NOT persisted as an alert.
|
|
@@ -269,7 +286,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
269
286
|
EventCategory["ProcessRestarted"] = "process.restarted";
|
|
270
287
|
/**
|
|
271
288
|
* The SET of storage locations changed — one was created, edited, enabled,
|
|
272
|
-
* disabled or deleted through
|
|
289
|
+
* disabled or deleted through the location instances or the forced-delete action.
|
|
273
290
|
*
|
|
274
291
|
* Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
|
|
275
292
|
* it must also converge on its own periodic path, because a dropped event
|
|
@@ -6020,7 +6037,7 @@ var ZodIssueCode = {
|
|
|
6020
6037
|
var ZodFirstPartyTypeKind;
|
|
6021
6038
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6022
6039
|
//#endregion
|
|
6023
|
-
//#region ../types/dist/sleep-
|
|
6040
|
+
//#region ../types/dist/sleep-CQP-REJp.mjs
|
|
6024
6041
|
/**
|
|
6025
6042
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6026
6043
|
* window to float samples (D455).
|
|
@@ -6384,6 +6401,59 @@ Object.fromEntries([
|
|
|
6384
6401
|
function isValuelessField(field) {
|
|
6385
6402
|
return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button" || field.type === "account-sign-in" || field.type === "secret-presence";
|
|
6386
6403
|
}
|
|
6404
|
+
/**
|
|
6405
|
+
* Base collector: flatten a hydrated `ConfigUISchemaWithValues` into
|
|
6406
|
+
* `key → { value, default }`, recursing through `group` and `sub-tabs`
|
|
6407
|
+
* layout fields. Keeping `value` and `default` SEPARATE (rather than
|
|
6408
|
+
* pre-resolving) lets callers both read the effective value AND detect
|
|
6409
|
+
* overrides (`value` differs from `default`). Structural over `unknown`;
|
|
6410
|
+
* malformed input yields an empty map, never throws.
|
|
6411
|
+
*/
|
|
6412
|
+
function collectHydratedFieldEntries(schema) {
|
|
6413
|
+
const out = /* @__PURE__ */ new Map();
|
|
6414
|
+
if (typeof schema !== "object" || schema === null) return out;
|
|
6415
|
+
const sections = schema.sections;
|
|
6416
|
+
if (!Array.isArray(sections)) return out;
|
|
6417
|
+
for (const section of sections) if (typeof section === "object" && section !== null) collectFieldEntriesInto(section.fields, out);
|
|
6418
|
+
return out;
|
|
6419
|
+
}
|
|
6420
|
+
/** Recursive field walker for {@link collectHydratedFieldEntries}. */
|
|
6421
|
+
function collectFieldEntriesInto(fields, into) {
|
|
6422
|
+
if (!Array.isArray(fields)) return;
|
|
6423
|
+
for (const field of fields) {
|
|
6424
|
+
if (typeof field !== "object" || field === null) continue;
|
|
6425
|
+
const type = field.type;
|
|
6426
|
+
if (type === "group") {
|
|
6427
|
+
collectFieldEntriesInto(field.fields, into);
|
|
6428
|
+
continue;
|
|
6429
|
+
}
|
|
6430
|
+
if (type === "sub-tabs") {
|
|
6431
|
+
const tabs = field.tabs;
|
|
6432
|
+
if (Array.isArray(tabs)) {
|
|
6433
|
+
for (const tab of tabs) if (typeof tab === "object" && tab !== null) collectFieldEntriesInto(tab.fields, into);
|
|
6434
|
+
}
|
|
6435
|
+
continue;
|
|
6436
|
+
}
|
|
6437
|
+
const key = field.key;
|
|
6438
|
+
if (typeof key !== "string" || key.length === 0) continue;
|
|
6439
|
+
if (!("value" in field)) continue;
|
|
6440
|
+
into.set(key, {
|
|
6441
|
+
value: field.value,
|
|
6442
|
+
default: field.default
|
|
6443
|
+
});
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
/**
|
|
6447
|
+
* Flatten a hydrated settings payload into `key → EFFECTIVE value`
|
|
6448
|
+
* (`value ?? default`). The read-side inverse of `hydrateSchema` for the
|
|
6449
|
+
* common "just give me the resolved values" case. Built on
|
|
6450
|
+
* {@link collectHydratedFieldEntries}.
|
|
6451
|
+
*/
|
|
6452
|
+
function collectHydratedFieldValues(schema) {
|
|
6453
|
+
const out = /* @__PURE__ */ new Map();
|
|
6454
|
+
for (const [key, entry] of collectHydratedFieldEntries(schema)) out.set(key, entry.value ?? entry.default);
|
|
6455
|
+
return out;
|
|
6456
|
+
}
|
|
6387
6457
|
function hydrateSchema(schema, values, options = {}) {
|
|
6388
6458
|
return {
|
|
6389
6459
|
...schema.tabs ? { tabs: [...schema.tabs] } : {},
|
|
@@ -6729,10 +6799,33 @@ function integrationRowsApiOver(api) {
|
|
|
6729
6799
|
};
|
|
6730
6800
|
}
|
|
6731
6801
|
var PREFIX = "records/";
|
|
6802
|
+
/**
|
|
6803
|
+
* Cross-realm brand. An addon bundles its own copy of this module (D15) while
|
|
6804
|
+
* `ctx.records` is the HOST's door, so an error thrown by the host is an
|
|
6805
|
+
* instance of the host's classes, not of the addon's. The brand plus the code
|
|
6806
|
+
* make `instanceof` answer by identity of the failure, not of the copy.
|
|
6807
|
+
*/
|
|
6808
|
+
var RECORDS_ERROR_BRAND = Symbol.for("camstack.records-error");
|
|
6809
|
+
function carriesBrand(value) {
|
|
6810
|
+
return typeof value === "object" && value !== null && Reflect.get(value, RECORDS_ERROR_BRAND) === true;
|
|
6811
|
+
}
|
|
6732
6812
|
/** The base of every records failure. `detail` is the human part of the message. */
|
|
6733
6813
|
var RecordsError = class extends Error {
|
|
6814
|
+
/** The code a subclass stands for; the base stands for every code. */
|
|
6815
|
+
static code = void 0;
|
|
6816
|
+
/**
|
|
6817
|
+
* `instanceof` across copies of this module: any branded error is a
|
|
6818
|
+
* `RecordsError`, and is an instance of a subclass only when its code is the
|
|
6819
|
+
* subclass's code. Same-copy answers are unchanged.
|
|
6820
|
+
*/
|
|
6821
|
+
static [Symbol.hasInstance](value) {
|
|
6822
|
+
if (Function.prototype[Symbol.hasInstance].call(this, value)) return true;
|
|
6823
|
+
if (!carriesBrand(value)) return false;
|
|
6824
|
+
return this.code === void 0 || value.code === this.code;
|
|
6825
|
+
}
|
|
6734
6826
|
code;
|
|
6735
6827
|
detail;
|
|
6828
|
+
[RECORDS_ERROR_BRAND] = true;
|
|
6736
6829
|
constructor(code, detail) {
|
|
6737
6830
|
super(`${PREFIX}${code}: ${detail}`);
|
|
6738
6831
|
this.name = "RecordsError";
|
|
@@ -6741,29 +6834,33 @@ var RecordsError = class extends Error {
|
|
|
6741
6834
|
}
|
|
6742
6835
|
};
|
|
6743
6836
|
/** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
|
|
6744
|
-
var RevConflictError = class extends RecordsError {
|
|
6837
|
+
var RevConflictError = class RevConflictError extends RecordsError {
|
|
6838
|
+
static code = "rev-conflict";
|
|
6745
6839
|
constructor(detail) {
|
|
6746
|
-
super(
|
|
6840
|
+
super(RevConflictError.code, detail);
|
|
6747
6841
|
this.name = "RevConflictError";
|
|
6748
6842
|
}
|
|
6749
6843
|
};
|
|
6750
6844
|
/** The keychain could not store or serve a value: explicit, never an empty string. */
|
|
6751
|
-
var RecordSecretUnavailableError = class extends RecordsError {
|
|
6845
|
+
var RecordSecretUnavailableError = class RecordSecretUnavailableError extends RecordsError {
|
|
6846
|
+
static code = "secret-unavailable";
|
|
6752
6847
|
constructor(detail) {
|
|
6753
|
-
super(
|
|
6848
|
+
super(RecordSecretUnavailableError.code, detail);
|
|
6754
6849
|
this.name = "RecordSecretUnavailableError";
|
|
6755
6850
|
}
|
|
6756
6851
|
};
|
|
6757
|
-
var InvalidRecordError = class extends RecordsError {
|
|
6852
|
+
var InvalidRecordError = class InvalidRecordError extends RecordsError {
|
|
6853
|
+
static code = "invalid-record";
|
|
6758
6854
|
constructor(detail) {
|
|
6759
|
-
super(
|
|
6855
|
+
super(InvalidRecordError.code, detail);
|
|
6760
6856
|
this.name = "InvalidRecordError";
|
|
6761
6857
|
}
|
|
6762
6858
|
};
|
|
6763
6859
|
/** A kind declaration the engine refuses (non-additive change, bad shape, wrong file). */
|
|
6764
|
-
var DeclarationRefusedError = class extends RecordsError {
|
|
6860
|
+
var DeclarationRefusedError = class DeclarationRefusedError extends RecordsError {
|
|
6861
|
+
static code = "declaration-refused";
|
|
6765
6862
|
constructor(detail) {
|
|
6766
|
-
super(
|
|
6863
|
+
super(DeclarationRefusedError.code, detail);
|
|
6767
6864
|
this.name = "DeclarationRefusedError";
|
|
6768
6865
|
}
|
|
6769
6866
|
};
|
|
@@ -7241,17 +7338,35 @@ var RecordIntegrationInstances = class {
|
|
|
7241
7338
|
...kind !== void 0 ? { kind } : {}
|
|
7242
7339
|
});
|
|
7243
7340
|
if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
|
|
7341
|
+
const initial = this.hooks.initialBody(name, kind);
|
|
7342
|
+
const patch = this.patchOf(values ?? {});
|
|
7343
|
+
if (Object.keys(patch).length > 0) Object.assign(patch, await this.hooks.validateWrite?.({
|
|
7344
|
+
kind,
|
|
7345
|
+
body: {
|
|
7346
|
+
...initial,
|
|
7347
|
+
...patch
|
|
7348
|
+
}
|
|
7349
|
+
}));
|
|
7244
7350
|
await this.handle.put({
|
|
7245
7351
|
id: minted,
|
|
7246
|
-
data:
|
|
7352
|
+
data: initial
|
|
7247
7353
|
}, { ifRev: 0 });
|
|
7248
|
-
const patch = this.patchOf(values ?? {});
|
|
7249
7354
|
if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
|
|
7250
7355
|
await this.hooks.onChanged?.(minted, "created");
|
|
7251
7356
|
return { id: minted };
|
|
7252
7357
|
};
|
|
7253
7358
|
updateInstance = async ({ id, name, values }) => {
|
|
7254
7359
|
const patch = this.patchOf(values);
|
|
7360
|
+
if (this.hooks.validateWrite !== void 0 && Object.keys(patch).length > 0) {
|
|
7361
|
+
const stored = await this.handle.get(id);
|
|
7362
|
+
if (stored !== null) Object.assign(patch, await this.hooks.validateWrite({
|
|
7363
|
+
kind: void 0,
|
|
7364
|
+
body: {
|
|
7365
|
+
...stored.data,
|
|
7366
|
+
...patch
|
|
7367
|
+
}
|
|
7368
|
+
}));
|
|
7369
|
+
}
|
|
7255
7370
|
if (name !== void 0) patch[this.nameField] = name;
|
|
7256
7371
|
await this.handle.patch(id, patch);
|
|
7257
7372
|
await this.hooks.onChanged?.(id, "updated");
|
|
@@ -7562,6 +7677,21 @@ var SCOPED_SETTINGS_SCOPES = [
|
|
|
7562
7677
|
"device-defaults",
|
|
7563
7678
|
"node"
|
|
7564
7679
|
];
|
|
7680
|
+
/** The one refusal of a key a scope did not declare. */
|
|
7681
|
+
function assertDeclaredKeys(keys, declared) {
|
|
7682
|
+
const undeclared = keys.filter((key) => !declared.has(key));
|
|
7683
|
+
if (undeclared.length > 0) throw new Error(`scoped settings: [${undeclared.join(", ")}] not declared for this scope (declared: [${[...declared].join(", ")}])`);
|
|
7684
|
+
}
|
|
7685
|
+
/** Build the handle over an addon's scoped read, write and unset. */
|
|
7686
|
+
function createScopedSettings(deps) {
|
|
7687
|
+
return {
|
|
7688
|
+
async get() {
|
|
7689
|
+
return deps.schema.parse(await deps.read());
|
|
7690
|
+
},
|
|
7691
|
+
patch: (patch) => deps.write(Object.fromEntries(Object.entries(patch))),
|
|
7692
|
+
unset: (keys) => deps.unset(keys)
|
|
7693
|
+
};
|
|
7694
|
+
}
|
|
7565
7695
|
/** A write to a secret path could not be stored in the keychain, so it was refused. */
|
|
7566
7696
|
var KeychainWriteRefusedError = class extends Error {
|
|
7567
7697
|
fieldPath;
|
|
@@ -8137,7 +8267,16 @@ function createHandle(raw, opts) {
|
|
|
8137
8267
|
return blob;
|
|
8138
8268
|
}
|
|
8139
8269
|
},
|
|
8140
|
-
writeAddonStore: writeAddon
|
|
8270
|
+
writeAddonStore: writeAddon,
|
|
8271
|
+
removeClearedKeys: (keys) => serialize("addon", async () => {
|
|
8272
|
+
const stored = await readStoredAddonBlob(raw);
|
|
8273
|
+
index.seen("addon", stored);
|
|
8274
|
+
const removable = keys.filter((key) => collectSecretMarkerRefs(stored[key]).length === 0);
|
|
8275
|
+
if (removable.length === 0) return;
|
|
8276
|
+
const patch = Object.fromEntries(removable.map((key) => [key, void 0]));
|
|
8277
|
+
await raw.writeAddonStore(patch);
|
|
8278
|
+
index.wrote("addon", patch);
|
|
8279
|
+
})
|
|
8141
8280
|
};
|
|
8142
8281
|
}
|
|
8143
8282
|
/** The context the addon sees, plus the private raw view and write outcome. */
|
|
@@ -8682,6 +8821,151 @@ var BaseAddon = class {
|
|
|
8682
8821
|
}
|
|
8683
8822
|
}
|
|
8684
8823
|
/**
|
|
8824
|
+
* Hook: values layered over the stored ones when the FORM of one scope is served
|
|
8825
|
+
* ({@link getScopedSettings}) — a preview that derives dependent options without writing.
|
|
8826
|
+
* Never applied to {@link ScopedSettings.get} or to a write. `null` ⇒ no overlay.
|
|
8827
|
+
*/
|
|
8828
|
+
async scopedSettingsOverlay(_scope, _cap, _nodeId) {
|
|
8829
|
+
return null;
|
|
8830
|
+
}
|
|
8831
|
+
/**
|
|
8832
|
+
* Hook: a write of one scope changed stored values (not called when the store reports no change,
|
|
8833
|
+
* e.g. the write-back of a stored secret). `keys` are the bare field keys of the write, node keys included.
|
|
8834
|
+
*/
|
|
8835
|
+
async onScopedSettingsChanged(_scope, _cap, _keys) {}
|
|
8836
|
+
/** Typed handle over the CLUSTER settings `cap` declares (`clusterSettingsSchema`). */
|
|
8837
|
+
clusterSettings(cap, schema) {
|
|
8838
|
+
return this.scopedHandle("cluster", cap, schema);
|
|
8839
|
+
}
|
|
8840
|
+
/** Typed handle over the fleet defaults of the per-device settings `cap` declares. */
|
|
8841
|
+
deviceDefaults(cap, schema) {
|
|
8842
|
+
return this.scopedHandle("device-defaults", cap, schema);
|
|
8843
|
+
}
|
|
8844
|
+
/**
|
|
8845
|
+
* Typed handle over the settings `cap` owns PER NODE. `nodeId` omitted ⇒ the local node. A node
|
|
8846
|
+
* with no row yields the schema defaults — never a bare-key fallback.
|
|
8847
|
+
*/
|
|
8848
|
+
nodeSettings(cap, schema, nodeId) {
|
|
8849
|
+
return this.scopedHandle("node", cap, schema, nodeId);
|
|
8850
|
+
}
|
|
8851
|
+
/**
|
|
8852
|
+
* {@link state} for a value that is kept per node: stored under `<key>@<nodeId>`, and the
|
|
8853
|
+
* settings store carries `nodeId` in the row data (D986 §1), so a forgotten node's rows go in one
|
|
8854
|
+
* central delete. `nodeId` omitted ⇒ the local node.
|
|
8855
|
+
*/
|
|
8856
|
+
nodeState(key, schema, fallback, nodeId) {
|
|
8857
|
+
this._declaredStateKeys.add(key);
|
|
8858
|
+
return createDurableState({
|
|
8859
|
+
key: nodeScopedKey(key, nodeId ?? this._ctx?.kernel?.localNodeId ?? ""),
|
|
8860
|
+
schema,
|
|
8861
|
+
fallback,
|
|
8862
|
+
read: async () => this.resolveStoredSecrets(await this.readAddonStoreWithRetry()),
|
|
8863
|
+
write: async (patch) => {
|
|
8864
|
+
await this._ctx?.settings?.writeAddonStore(patch);
|
|
8865
|
+
},
|
|
8866
|
+
onParseError: (k, e) => this._ctx?.logger?.warn?.(`durable-state: stored "${k}" failed validation — using fallback`, { meta: {
|
|
8867
|
+
addonId: this._ctx?.id,
|
|
8868
|
+
key: k,
|
|
8869
|
+
error: String(e)
|
|
8870
|
+
} })
|
|
8871
|
+
});
|
|
8872
|
+
}
|
|
8873
|
+
/** The loaded schema `cap` declares for `scope`, or `null`. */
|
|
8874
|
+
async declaredScopedSchema(scope, cap) {
|
|
8875
|
+
await this.loadScopedSchemas(cap);
|
|
8876
|
+
return this.loadedScopedSchemas(cap).find((entry) => entry.scope === scope)?.schema ?? null;
|
|
8877
|
+
}
|
|
8878
|
+
async requireScopedSchema(scope, cap) {
|
|
8879
|
+
const schema = await this.declaredScopedSchema(scope, cap);
|
|
8880
|
+
if (schema === null) throw new Error(`${this.constructor.name}: cap "${cap}" declares no ${scope} settings`);
|
|
8881
|
+
return schema;
|
|
8882
|
+
}
|
|
8883
|
+
scopedKeys(schema) {
|
|
8884
|
+
return new Set(schema.sections.flatMap((section) => collectValueFieldKeys(section.fields)));
|
|
8885
|
+
}
|
|
8886
|
+
/** `store` narrowed to the keys `schema` declares. */
|
|
8887
|
+
narrowToScope(store, schema) {
|
|
8888
|
+
const keys = this.scopedKeys(schema);
|
|
8889
|
+
return Object.fromEntries(Object.entries(store).filter(([key]) => keys.has(key)));
|
|
8890
|
+
}
|
|
8891
|
+
scopedHandle(scope, cap, schema, nodeId) {
|
|
8892
|
+
return createScopedSettings({
|
|
8893
|
+
schema,
|
|
8894
|
+
read: async () => {
|
|
8895
|
+
const declared = await this.requireScopedSchema(scope, cap);
|
|
8896
|
+
const stored = this.narrowToScope(await this.resolveGlobalStore(nodeId, cap), declared);
|
|
8897
|
+
const effective = collectHydratedFieldValues(hydrateSchema(declared, stored));
|
|
8898
|
+
return {
|
|
8899
|
+
...Object.fromEntries([...effective].filter(([, value]) => value !== void 0)),
|
|
8900
|
+
...stored
|
|
8901
|
+
};
|
|
8902
|
+
},
|
|
8903
|
+
write: (patch) => this.writeScoped(scope, cap, patch, nodeId),
|
|
8904
|
+
unset: (keys) => this.unsetScoped(scope, cap, keys, nodeId)
|
|
8905
|
+
});
|
|
8906
|
+
}
|
|
8907
|
+
/** The one write path of the scoped API: declared keys only, then the global patch. */
|
|
8908
|
+
async writeScoped(scope, cap, patch, nodeId) {
|
|
8909
|
+
assertDeclaredKeys(Object.keys(patch), this.scopedKeys(await this.requireScopedSchema(scope, cap)));
|
|
8910
|
+
if (await this.applyGlobalPatch(patch, scope === "node" ? nodeId : void 0, cap)) await this.onScopedSettingsChanged(scope, cap, Object.keys(patch));
|
|
8911
|
+
}
|
|
8912
|
+
/**
|
|
8913
|
+
* Remove declared keys of one scope. A secret goes through {@link clearSecrets} — its keychain
|
|
8914
|
+
* value is released exactly as there — and the emptied row is then removed; every other key is
|
|
8915
|
+
* removed by an `undefined` patch (which a secret transform would read as "keep").
|
|
8916
|
+
*/
|
|
8917
|
+
async unsetScoped(scope, cap, keys, nodeId) {
|
|
8918
|
+
assertDeclaredKeys(keys, this.scopedKeys(await this.requireScopedSchema(scope, cap)));
|
|
8919
|
+
const target = scope === "node" ? nodeId : void 0;
|
|
8920
|
+
const tree = this.addonSecretTree();
|
|
8921
|
+
const secretKeys = keys.filter((key) => tree.has(key));
|
|
8922
|
+
const plainKeys = keys.filter((key) => !tree.has(key));
|
|
8923
|
+
let changed = false;
|
|
8924
|
+
if (plainKeys.length > 0) {
|
|
8925
|
+
const patch = Object.fromEntries(plainKeys.map((key) => [key, void 0]));
|
|
8926
|
+
changed = await this.applyGlobalPatch(patch, target, cap) || changed;
|
|
8927
|
+
}
|
|
8928
|
+
if (secretKeys.length > 0) {
|
|
8929
|
+
await this.clearSecretKeys(secretKeys, target, cap);
|
|
8930
|
+
const node = normalizeNodeId(target ?? this._ctx?.kernel?.localNodeId);
|
|
8931
|
+
const rows = secretKeys.map((key) => scope === "node" ? nodeScopedKey(key, node) : key);
|
|
8932
|
+
if (this._secretSettings !== null) await this._secretSettings.removeClearedKeys(rows);
|
|
8933
|
+
else await this._ctx?.settings?.writeAddonStore(Object.fromEntries(rows.map((row) => [row, void 0])));
|
|
8934
|
+
changed = true;
|
|
8935
|
+
}
|
|
8936
|
+
if (changed) await this.onScopedSettingsChanged(scope, cap, [...keys]);
|
|
8937
|
+
}
|
|
8938
|
+
/**
|
|
8939
|
+
* The hydrated form of one scope of one capability's settings (any
|
|
8940
|
+
* {@link scopedSettingsOverlay} applied), or `null` when the capability declares nothing there.
|
|
8941
|
+
*/
|
|
8942
|
+
async getScopedSettings(scope, cap, nodeId) {
|
|
8943
|
+
const schema = await this.declaredScopedSchema(scope, cap);
|
|
8944
|
+
if (schema === null) return null;
|
|
8945
|
+
const stored = this.narrowToScope(await this.readRawProjectedStore(nodeId, cap), schema);
|
|
8946
|
+
const overlay = await this.scopedSettingsOverlay(scope, cap, nodeId);
|
|
8947
|
+
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(stored, overlay) : stored, { inlineSecretsAsViews: true });
|
|
8948
|
+
}
|
|
8949
|
+
/** The OPERATOR's write of declared keys of one scope (D787 secret semantics). */
|
|
8950
|
+
async updateScopedSettings(scope, cap, patch, nodeId) {
|
|
8951
|
+
this._operatorWriteDepth++;
|
|
8952
|
+
try {
|
|
8953
|
+
await this.writeScoped(scope, cap, patch, nodeId);
|
|
8954
|
+
} finally {
|
|
8955
|
+
this._operatorWriteDepth--;
|
|
8956
|
+
}
|
|
8957
|
+
}
|
|
8958
|
+
/**
|
|
8959
|
+
* The effective values (`stored ?? default`) of one scope, a secret as its `set` view — never
|
|
8960
|
+
* its value. `null` when the capability declares nothing for the scope.
|
|
8961
|
+
*/
|
|
8962
|
+
async readScopedValues(scope, cap, nodeId) {
|
|
8963
|
+
const schema = await this.declaredScopedSchema(scope, cap);
|
|
8964
|
+
if (schema === null) return null;
|
|
8965
|
+
const form = hydrateSchema(schema, this.narrowToScope(await this.readRawProjectedStore(nodeId, cap), schema), { inlineSecretsAsViews: true });
|
|
8966
|
+
return Object.fromEntries([...collectHydratedFieldValues(form)].filter(([, value]) => value !== void 0));
|
|
8967
|
+
}
|
|
8968
|
+
/**
|
|
8685
8969
|
* What the create wizard and the integration edit page render for one instance: the addon's
|
|
8686
8970
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
8687
8971
|
*/
|
|
@@ -8833,6 +9117,9 @@ var BaseAddon = class {
|
|
|
8833
9117
|
* a keychain (an older kernel) it writes `''`, as such code always did.
|
|
8834
9118
|
*/
|
|
8835
9119
|
async clearSecrets(keys, nodeId) {
|
|
9120
|
+
await this.clearSecretKeys(keys, nodeId);
|
|
9121
|
+
}
|
|
9122
|
+
async clearSecretKeys(keys, nodeId, cap) {
|
|
8836
9123
|
const tree = this.addonSecretTree();
|
|
8837
9124
|
const keychain = this._ctx?.secrets !== void 0;
|
|
8838
9125
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
@@ -8845,25 +9132,27 @@ var BaseAddon = class {
|
|
|
8845
9132
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8846
9133
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8847
9134
|
}
|
|
8848
|
-
await this.applyGlobalPatch(patch, nodeId);
|
|
9135
|
+
await this.applyGlobalPatch(patch, nodeId, cap);
|
|
8849
9136
|
}
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
9137
|
+
/** Resolves `true` when the write changed stored values. */
|
|
9138
|
+
async applyGlobalPatch(barePatch, nodeId, cap) {
|
|
9139
|
+
await this.loadScopedSchemas(cap);
|
|
9140
|
+
const keys = this.perNodeKeys(cap);
|
|
8853
9141
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8854
9142
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
8855
9143
|
const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
|
|
8856
9144
|
const handle = this._secretSettings;
|
|
8857
9145
|
if (handle !== null) {
|
|
8858
9146
|
const { changed } = await handle.writeAddonStore(scoped);
|
|
8859
|
-
if (!changed) return;
|
|
9147
|
+
if (!changed) return false;
|
|
8860
9148
|
} else await this._ctx?.settings?.writeAddonStore(scoped);
|
|
8861
|
-
if (target !== localNode) return;
|
|
9149
|
+
if (target !== localNode) return true;
|
|
8862
9150
|
await this.resolveConfig();
|
|
8863
9151
|
await this.onConfigChanged();
|
|
8864
9152
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8865
9153
|
const legacy = this.globalSettingsSchema();
|
|
8866
9154
|
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
9155
|
+
return true;
|
|
8867
9156
|
}
|
|
8868
9157
|
/**
|
|
8869
9158
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -9558,30 +9847,94 @@ function normalizeAddonInitResult(result) {
|
|
|
9558
9847
|
if (Array.isArray(result)) return { providers: result };
|
|
9559
9848
|
return result;
|
|
9560
9849
|
}
|
|
9850
|
+
object({
|
|
9851
|
+
field: string().min(1),
|
|
9852
|
+
on: literal("advance")
|
|
9853
|
+
});
|
|
9561
9854
|
/**
|
|
9562
|
-
*
|
|
9563
|
-
*
|
|
9564
|
-
*
|
|
9855
|
+
* Output schema shared by the contribution + live methods.
|
|
9856
|
+
*
|
|
9857
|
+
* Mirrors the `ConfigUISchemaWithValues` shape (sections[] + optional
|
|
9858
|
+
* tabs[]) without importing from `../interfaces/config-ui.js` — a
|
|
9859
|
+
* concrete-but-lenient Zod object keeps tRPC output inference happy
|
|
9860
|
+
* (using `z.unknown()` here collapses unrelated router branches to
|
|
9861
|
+
* `unknown` when the generator re-inlines the huge AppRouter type).
|
|
9862
|
+
*
|
|
9863
|
+
* `.passthrough()` on sections/fields accepts whatever FormBuilder
|
|
9864
|
+
* extensions the caller adds (showWhen, displayScale, …) without
|
|
9865
|
+
* rebuilding every time a new field kind is introduced.
|
|
9565
9866
|
*/
|
|
9566
|
-
var
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9867
|
+
var ContributionSectionSchema = object({
|
|
9868
|
+
id: string(),
|
|
9869
|
+
title: string(),
|
|
9870
|
+
description: string().optional(),
|
|
9871
|
+
style: _enum(["card", "accordion"]).optional(),
|
|
9872
|
+
defaultCollapsed: boolean().optional(),
|
|
9873
|
+
columns: union([
|
|
9874
|
+
literal(1),
|
|
9875
|
+
literal(2),
|
|
9876
|
+
literal(3),
|
|
9877
|
+
literal(4)
|
|
9878
|
+
]).optional(),
|
|
9879
|
+
tab: string().optional(),
|
|
9880
|
+
location: _enum([
|
|
9881
|
+
"settings",
|
|
9882
|
+
"top-tab",
|
|
9883
|
+
"hero"
|
|
9884
|
+
]).optional(),
|
|
9885
|
+
order: number().optional(),
|
|
9886
|
+
fields: array(any())
|
|
9584
9887
|
});
|
|
9888
|
+
object({
|
|
9889
|
+
tabs: array(object({
|
|
9890
|
+
id: string(),
|
|
9891
|
+
label: string(),
|
|
9892
|
+
icon: string(),
|
|
9893
|
+
order: number().optional()
|
|
9894
|
+
})).optional(),
|
|
9895
|
+
sections: array(ContributionSectionSchema)
|
|
9896
|
+
}).nullable();
|
|
9897
|
+
object({ deviceId: number() }), object({ deviceId: number() }), object({
|
|
9898
|
+
deviceId: number(),
|
|
9899
|
+
patch: record(string(), unknown())
|
|
9900
|
+
}), object({ success: literal(true) });
|
|
9901
|
+
object({ deviceId: number() }), unknown().nullable();
|
|
9902
|
+
function method(input, output, options) {
|
|
9903
|
+
return {
|
|
9904
|
+
input,
|
|
9905
|
+
output,
|
|
9906
|
+
kind: options?.kind ?? "query",
|
|
9907
|
+
...options?.auth !== void 0 ? { auth: options.auth } : {},
|
|
9908
|
+
...options?.access !== void 0 ? { access: options.access } : {},
|
|
9909
|
+
...options?.role !== void 0 ? { role: options.role } : {},
|
|
9910
|
+
...options?.caller !== void 0 ? { caller: options.caller } : {},
|
|
9911
|
+
timeoutMs: options?.timeoutMs
|
|
9912
|
+
};
|
|
9913
|
+
}
|
|
9914
|
+
/**
|
|
9915
|
+
* A wrapper/system-only method: served exclusively by the cap's system-level
|
|
9916
|
+
* provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
|
|
9917
|
+
* driver natives don't stub out a wrapper concern (e.g. a cross-device cache
|
|
9918
|
+
* overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
|
|
9919
|
+
*/
|
|
9920
|
+
function systemMethod(input, output, options) {
|
|
9921
|
+
return {
|
|
9922
|
+
...method(input, output, options),
|
|
9923
|
+
systemOnly: true
|
|
9924
|
+
};
|
|
9925
|
+
}
|
|
9926
|
+
/**
|
|
9927
|
+
* A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
|
|
9928
|
+
* on `InferProvider`. The `providerOptional: true` literal is what
|
|
9929
|
+
* `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
|
|
9930
|
+
* for when this is the honest answer and when it is a soft stub.
|
|
9931
|
+
*/
|
|
9932
|
+
function optionalMethod(input, output, options) {
|
|
9933
|
+
return {
|
|
9934
|
+
...method(input, output, options),
|
|
9935
|
+
providerOptional: true
|
|
9936
|
+
};
|
|
9937
|
+
}
|
|
9585
9938
|
function defOf(schema) {
|
|
9586
9939
|
const zodBag = Reflect.get(schema, "_zod");
|
|
9587
9940
|
if (typeof zodBag !== "object" || zodBag === null) return {};
|
|
@@ -9838,94 +10191,30 @@ function defineEntryKind(name, spec) {
|
|
|
9838
10191
|
if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
|
|
9839
10192
|
return defined;
|
|
9840
10193
|
}
|
|
9841
|
-
object({
|
|
9842
|
-
field: string().min(1),
|
|
9843
|
-
on: literal("advance")
|
|
9844
|
-
});
|
|
9845
10194
|
/**
|
|
9846
|
-
*
|
|
9847
|
-
*
|
|
9848
|
-
*
|
|
9849
|
-
* tabs[]) without importing from `../interfaces/config-ui.js` — a
|
|
9850
|
-
* concrete-but-lenient Zod object keeps tRPC output inference happy
|
|
9851
|
-
* (using `z.unknown()` here collapses unrelated router branches to
|
|
9852
|
-
* `unknown` when the generator re-inlines the huge AppRouter type).
|
|
9853
|
-
*
|
|
9854
|
-
* `.passthrough()` on sections/fields accepts whatever FormBuilder
|
|
9855
|
-
* extensions the caller adds (showWhen, displayScale, …) without
|
|
9856
|
-
* rebuilding every time a new field kind is introduced.
|
|
10195
|
+
* The share-view scope kinds a share-link token can carry. The hub mints
|
|
10196
|
+
* and validates them (`ShareViewGrantSchema` builds its
|
|
10197
|
+
* enum from this list); a data plane names the kinds it ACCEPTS in `share`.
|
|
9857
10198
|
*/
|
|
9858
|
-
var
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
order: number().optional(),
|
|
9877
|
-
fields: array(any())
|
|
10199
|
+
var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
|
|
10200
|
+
/** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
|
|
10201
|
+
var PeerBytesTicketSchema = object({
|
|
10202
|
+
/** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
|
|
10203
|
+
url: string().min(1),
|
|
10204
|
+
/**
|
|
10205
|
+
* The HOST node this URL means something on — the hub or a named agent,
|
|
10206
|
+
* never a runner. {@link AddonPeerBytes.open} compares it to its own and
|
|
10207
|
+
* refuses `cross-node` by name when they differ, without dialling.
|
|
10208
|
+
*/
|
|
10209
|
+
hostNodeId: string().min(1),
|
|
10210
|
+
expiresAtMs: number().int().nonnegative(),
|
|
10211
|
+
/**
|
|
10212
|
+
* What the producer DECLARED the body to be, when it knows — `null` when it
|
|
10213
|
+
* does not. Never `0` for unknown (D393): a consumer sizing a bound off this
|
|
10214
|
+
* must be able to tell "the producer did not say" from "the body is empty".
|
|
10215
|
+
*/
|
|
10216
|
+
declaredBytes: number().int().nonnegative().nullable()
|
|
9878
10217
|
});
|
|
9879
|
-
object({
|
|
9880
|
-
tabs: array(object({
|
|
9881
|
-
id: string(),
|
|
9882
|
-
label: string(),
|
|
9883
|
-
icon: string(),
|
|
9884
|
-
order: number().optional()
|
|
9885
|
-
})).optional(),
|
|
9886
|
-
sections: array(ContributionSectionSchema)
|
|
9887
|
-
}).nullable();
|
|
9888
|
-
object({ deviceId: number() }), object({ deviceId: number() }), object({
|
|
9889
|
-
deviceId: number(),
|
|
9890
|
-
patch: record(string(), unknown())
|
|
9891
|
-
}), object({ success: literal(true) });
|
|
9892
|
-
object({ deviceId: number() }), unknown().nullable();
|
|
9893
|
-
function method(input, output, options) {
|
|
9894
|
-
return {
|
|
9895
|
-
input,
|
|
9896
|
-
output,
|
|
9897
|
-
kind: options?.kind ?? "query",
|
|
9898
|
-
...options?.auth !== void 0 ? { auth: options.auth } : {},
|
|
9899
|
-
...options?.access !== void 0 ? { access: options.access } : {},
|
|
9900
|
-
...options?.role !== void 0 ? { role: options.role } : {},
|
|
9901
|
-
...options?.caller !== void 0 ? { caller: options.caller } : {},
|
|
9902
|
-
timeoutMs: options?.timeoutMs
|
|
9903
|
-
};
|
|
9904
|
-
}
|
|
9905
|
-
/**
|
|
9906
|
-
* A wrapper/system-only method: served exclusively by the cap's system-level
|
|
9907
|
-
* provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
|
|
9908
|
-
* driver natives don't stub out a wrapper concern (e.g. a cross-device cache
|
|
9909
|
-
* overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
|
|
9910
|
-
*/
|
|
9911
|
-
function systemMethod(input, output, options) {
|
|
9912
|
-
return {
|
|
9913
|
-
...method(input, output, options),
|
|
9914
|
-
systemOnly: true
|
|
9915
|
-
};
|
|
9916
|
-
}
|
|
9917
|
-
/**
|
|
9918
|
-
* A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
|
|
9919
|
-
* on `InferProvider`. The `providerOptional: true` literal is what
|
|
9920
|
-
* `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
|
|
9921
|
-
* for when this is the honest answer and when it is a soft stub.
|
|
9922
|
-
*/
|
|
9923
|
-
function optionalMethod(input, output, options) {
|
|
9924
|
-
return {
|
|
9925
|
-
...method(input, output, options),
|
|
9926
|
-
providerOptional: true
|
|
9927
|
-
};
|
|
9928
|
-
}
|
|
9929
10218
|
var READING_QUANTITIES = [
|
|
9930
10219
|
"temperature",
|
|
9931
10220
|
"pressure",
|
|
@@ -10504,7 +10793,6 @@ var CAP_NAMES = Object.freeze([
|
|
|
10504
10793
|
"climate-control",
|
|
10505
10794
|
"color",
|
|
10506
10795
|
"composer",
|
|
10507
|
-
"connection-test",
|
|
10508
10796
|
"connectivity",
|
|
10509
10797
|
"consumables",
|
|
10510
10798
|
"contact",
|
|
@@ -10594,7 +10882,6 @@ var CAP_NAMES = Object.freeze([
|
|
|
10594
10882
|
"notifier",
|
|
10595
10883
|
"numeric-sensor",
|
|
10596
10884
|
"oauth-integration",
|
|
10597
|
-
"oidc-connections",
|
|
10598
10885
|
"osd",
|
|
10599
10886
|
"osd-manager",
|
|
10600
10887
|
"pet-feeder",
|
|
@@ -12486,8 +12773,7 @@ Object.freeze({
|
|
|
12486
12773
|
"device-provider": { "reloadDevice": 18e4 },
|
|
12487
12774
|
"llm": {
|
|
12488
12775
|
"generate": 6e5,
|
|
12489
|
-
"generateVision": 6e5
|
|
12490
|
-
"testProfile": 6e5
|
|
12776
|
+
"generateVision": 6e5
|
|
12491
12777
|
},
|
|
12492
12778
|
"llm-runtime": {
|
|
12493
12779
|
"complete": 6e5,
|
|
@@ -12524,8 +12810,7 @@ Object.freeze({
|
|
|
12524
12810
|
"offerArtifact": 3e5,
|
|
12525
12811
|
"readResult": 3e5,
|
|
12526
12812
|
"submitJob": 18e5,
|
|
12527
|
-
"uploadDataset": 72e5
|
|
12528
|
-
"validateCredentials": 3e4
|
|
12813
|
+
"uploadDataset": 72e5
|
|
12529
12814
|
}
|
|
12530
12815
|
});
|
|
12531
12816
|
object({
|
|
@@ -13940,6 +14225,17 @@ function composeInstanceKinds(parts, base) {
|
|
|
13940
14225
|
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13941
14226
|
return part;
|
|
13942
14227
|
};
|
|
14228
|
+
/**
|
|
14229
|
+
* The part a DRAFT test runs on. A named kind is that kind; an unnamed one is the provider's only
|
|
14230
|
+
* testable kind. Several testable kinds and none named is a typed refusal that lists them, never a throw.
|
|
14231
|
+
*/
|
|
14232
|
+
const draftPart = (kindId) => {
|
|
14233
|
+
if (kindId !== void 0) return parts.find((candidate) => candidate.kind.id === kindId) ?? { error: `Unknown instance kind '${kindId}'` };
|
|
14234
|
+
const testable = parts.filter((part) => part.kind.canTest !== false);
|
|
14235
|
+
const only = testable.length === 1 ? testable[0] : parts.length === 1 ? parts[0] : void 0;
|
|
14236
|
+
if (only !== void 0) return only;
|
|
14237
|
+
return { error: `This provider has several kinds: name one of ${(testable.length > 0 ? testable : parts).map((part) => part.kind.id).join(", ")}` };
|
|
14238
|
+
};
|
|
13943
14239
|
const holderOf = async (id) => {
|
|
13944
14240
|
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13945
14241
|
throw new UnknownInstanceError(id);
|
|
@@ -13975,7 +14271,13 @@ function composeInstanceKinds(parts, base) {
|
|
|
13975
14271
|
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13976
14272
|
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13977
14273
|
testInstance: async (input) => {
|
|
13978
|
-
const
|
|
14274
|
+
const resolved = input.id === null ? draftPart(input.kind) : await holderOf(input.id);
|
|
14275
|
+
if ("error" in resolved) return {
|
|
14276
|
+
success: false,
|
|
14277
|
+
status: "inconclusive",
|
|
14278
|
+
error: resolved.error
|
|
14279
|
+
};
|
|
14280
|
+
const test = resolved.provider.testInstance;
|
|
13979
14281
|
if (test === void 0) return {
|
|
13980
14282
|
success: false,
|
|
13981
14283
|
status: "inconclusive",
|
|
@@ -13985,6 +14287,414 @@ function composeInstanceKinds(parts, base) {
|
|
|
13985
14287
|
}
|
|
13986
14288
|
};
|
|
13987
14289
|
}
|
|
14290
|
+
/**
|
|
14291
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
14292
|
+
*
|
|
14293
|
+
* Apprise-derived model (see
|
|
14294
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
14295
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
14296
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
14297
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
14298
|
+
* message to what the kind supports — callers never special-case a service.
|
|
14299
|
+
*
|
|
14300
|
+
* DESIGN DECISIONS (locked):
|
|
14301
|
+
* - A target is an INSTANCE of this function cap (epic #440, step 6): the
|
|
14302
|
+
* providers serve their targets as `integration-instances` records, one
|
|
14303
|
+
* record kind per target kind, and the admin lists, adds, edits, switches
|
|
14304
|
+
* and deletes them through that cap. This cap keeps what an instance cannot
|
|
14305
|
+
* say: the kind catalogue (`listTargetKinds`: capabilities, delivery
|
|
14306
|
+
* parameters, brand icon), dispatch (`send`, `testTarget`) and the form
|
|
14307
|
+
* helpers (`discoverTargets`, `probeTargetConfig`).
|
|
14308
|
+
* - `listTargetKinds` / `discoverTargets` return arrays → the generated
|
|
14309
|
+
* cap-mount auto-`concatCollection`-fans them across every registered
|
|
14310
|
+
* provider so one catalog is routable. `send` / `testTarget` /
|
|
14311
|
+
* `probeTargetConfig` route to ONE provider by the `addonId` the generated
|
|
14312
|
+
* collection router extracts from the call input.
|
|
14313
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
14314
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
14315
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
14316
|
+
* base64 fallback needed.
|
|
14317
|
+
*
|
|
14318
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
14319
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
14320
|
+
* admin "Integrations" page.
|
|
14321
|
+
*/
|
|
14322
|
+
/**
|
|
14323
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
14324
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
14325
|
+
*/
|
|
14326
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
14327
|
+
"image",
|
|
14328
|
+
"video",
|
|
14329
|
+
"gif",
|
|
14330
|
+
"audio",
|
|
14331
|
+
"icon"
|
|
14332
|
+
]);
|
|
14333
|
+
/**
|
|
14334
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
14335
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
14336
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
14337
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
14338
|
+
*/
|
|
14339
|
+
var AttachmentSchema = object({
|
|
14340
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
14341
|
+
url: string().optional(),
|
|
14342
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
14343
|
+
mime: string().optional(),
|
|
14344
|
+
name: string().optional()
|
|
14345
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
14346
|
+
var NotificationFormatSchema = _enum([
|
|
14347
|
+
"text",
|
|
14348
|
+
"markdown",
|
|
14349
|
+
"html"
|
|
14350
|
+
]);
|
|
14351
|
+
/**
|
|
14352
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
14353
|
+
*
|
|
14354
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
14355
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
14356
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
14357
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
14358
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
14359
|
+
*
|
|
14360
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
14361
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
14362
|
+
* differently.
|
|
14363
|
+
*
|
|
14364
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
14365
|
+
*
|
|
14366
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
14367
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
14368
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
14369
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
14370
|
+
* decides its glyph, which is the only place that decision can be made
|
|
14371
|
+
* honestly.
|
|
14372
|
+
*
|
|
14373
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
14374
|
+
* `disarm` straight through; iOS feeds that string to
|
|
14375
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
14376
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
14377
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
14378
|
+
*
|
|
14379
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
14380
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
14381
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
14382
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
14383
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
14384
|
+
*/
|
|
14385
|
+
var NotificationActionIconSchema = _enum([
|
|
14386
|
+
"acknowledge",
|
|
14387
|
+
"dismiss",
|
|
14388
|
+
"silence",
|
|
14389
|
+
"snooze",
|
|
14390
|
+
"view",
|
|
14391
|
+
"play",
|
|
14392
|
+
"open",
|
|
14393
|
+
"close",
|
|
14394
|
+
"lock",
|
|
14395
|
+
"unlock",
|
|
14396
|
+
"arm",
|
|
14397
|
+
"arm-home",
|
|
14398
|
+
"arm-away",
|
|
14399
|
+
"arm-night",
|
|
14400
|
+
"disarm",
|
|
14401
|
+
"light",
|
|
14402
|
+
"alert",
|
|
14403
|
+
"camera"
|
|
14404
|
+
]);
|
|
14405
|
+
/** A single tap-through action button. */
|
|
14406
|
+
var NotificationActionSchema = object({
|
|
14407
|
+
id: string(),
|
|
14408
|
+
label: string(),
|
|
14409
|
+
url: string().optional(),
|
|
14410
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
14411
|
+
icon: NotificationActionIconSchema.optional(),
|
|
14412
|
+
/**
|
|
14413
|
+
* Renders in a warning style where the notifier supports it.
|
|
14414
|
+
*
|
|
14415
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
14416
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
14417
|
+
* does not buy.
|
|
14418
|
+
*/
|
|
14419
|
+
destructive: boolean().optional(),
|
|
14420
|
+
/**
|
|
14421
|
+
* How the tap should REACH the url.
|
|
14422
|
+
*
|
|
14423
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
14424
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
14425
|
+
* That is right for a button whose answer the operator wants to read.
|
|
14426
|
+
*
|
|
14427
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
14428
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
14429
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
14430
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
14431
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
14432
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
14433
|
+
* a requirement.
|
|
14434
|
+
*/
|
|
14435
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
14436
|
+
});
|
|
14437
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
14438
|
+
var DeliveryParamValueSchema = union([
|
|
14439
|
+
string(),
|
|
14440
|
+
number(),
|
|
14441
|
+
boolean()
|
|
14442
|
+
]);
|
|
14443
|
+
/**
|
|
14444
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
14445
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
14446
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
14447
|
+
* no editor knows a service by name.
|
|
14448
|
+
*
|
|
14449
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
14450
|
+
* run on the addon cap path).
|
|
14451
|
+
*/
|
|
14452
|
+
var DeliveryParamSchema = object({
|
|
14453
|
+
key: string().min(1),
|
|
14454
|
+
label: string(),
|
|
14455
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
14456
|
+
labelKey: string().optional(),
|
|
14457
|
+
type: _enum([
|
|
14458
|
+
"enum",
|
|
14459
|
+
"number",
|
|
14460
|
+
"boolean",
|
|
14461
|
+
"string"
|
|
14462
|
+
]),
|
|
14463
|
+
/** `enum` only: the closed set of accepted values. */
|
|
14464
|
+
options: array(object({
|
|
14465
|
+
value: string(),
|
|
14466
|
+
label: string()
|
|
14467
|
+
})).optional(),
|
|
14468
|
+
/** `number` only. */
|
|
14469
|
+
min: number().optional(),
|
|
14470
|
+
max: number().optional(),
|
|
14471
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
14472
|
+
default: DeliveryParamValueSchema.optional()
|
|
14473
|
+
});
|
|
14474
|
+
/**
|
|
14475
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
14476
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
14477
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
14478
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
14479
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
14480
|
+
* `priority` for that one target.
|
|
14481
|
+
*/
|
|
14482
|
+
var NotificationSchema = object({
|
|
14483
|
+
body: string(),
|
|
14484
|
+
title: string().optional(),
|
|
14485
|
+
format: NotificationFormatSchema.default("text"),
|
|
14486
|
+
priority: number().int().min(1).max(5).default(3),
|
|
14487
|
+
level: string().optional(),
|
|
14488
|
+
attachments: array(AttachmentSchema).optional(),
|
|
14489
|
+
clickUrl: string().optional(),
|
|
14490
|
+
actions: array(NotificationActionSchema).optional(),
|
|
14491
|
+
sound: string().optional(),
|
|
14492
|
+
ttl: number().optional(),
|
|
14493
|
+
tag: string().optional(),
|
|
14494
|
+
deviceId: number().optional(),
|
|
14495
|
+
eventId: string().optional(),
|
|
14496
|
+
metadata: record(string(), unknown()).optional(),
|
|
14497
|
+
/**
|
|
14498
|
+
* Values for the parameters the target's kind DECLARES in
|
|
14499
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
14500
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
14501
|
+
*/
|
|
14502
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
14503
|
+
});
|
|
14504
|
+
/** One declared native severity/priority level for a kind. */
|
|
14505
|
+
var TargetKindLevelSchema = object({
|
|
14506
|
+
id: string(),
|
|
14507
|
+
label: string(),
|
|
14508
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
14509
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
14510
|
+
flags: object({
|
|
14511
|
+
critical: boolean().optional(),
|
|
14512
|
+
silent: boolean().optional(),
|
|
14513
|
+
noPush: boolean().optional()
|
|
14514
|
+
}).optional(),
|
|
14515
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
14516
|
+
requires: array(string()).optional(),
|
|
14517
|
+
description: string().optional()
|
|
14518
|
+
});
|
|
14519
|
+
/** The full capability block consulted before dispatch. */
|
|
14520
|
+
var TargetKindCapsSchema = object({
|
|
14521
|
+
attachments: object({
|
|
14522
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
14523
|
+
mode: _enum([
|
|
14524
|
+
"url",
|
|
14525
|
+
"bytes",
|
|
14526
|
+
"both"
|
|
14527
|
+
]),
|
|
14528
|
+
max: number().int().nonnegative(),
|
|
14529
|
+
maxBytes: number().int().positive().optional()
|
|
14530
|
+
}),
|
|
14531
|
+
/** Max action buttons (0 = none). */
|
|
14532
|
+
actions: number().int().nonnegative(),
|
|
14533
|
+
/**
|
|
14534
|
+
* Whether this kind renders a per-action ICON.
|
|
14535
|
+
*
|
|
14536
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
14537
|
+
* run on the addon cap path — three production failures in one day taught
|
|
14538
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
14539
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
14540
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
14541
|
+
*/
|
|
14542
|
+
actionIcons: boolean().optional(),
|
|
14543
|
+
levels: array(TargetKindLevelSchema),
|
|
14544
|
+
format: array(NotificationFormatSchema),
|
|
14545
|
+
clickUrl: boolean(),
|
|
14546
|
+
sound: boolean(),
|
|
14547
|
+
ttl: boolean(),
|
|
14548
|
+
bodyMaxLen: number().int().positive()
|
|
14549
|
+
});
|
|
14550
|
+
/**
|
|
14551
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
14552
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
14553
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
14554
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
14555
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
14556
|
+
*/
|
|
14557
|
+
var ConfigSchemaPassthrough$3 = unknown();
|
|
14558
|
+
var TargetKindSchema = object({
|
|
14559
|
+
kind: string(),
|
|
14560
|
+
label: string(),
|
|
14561
|
+
icon: string(),
|
|
14562
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
14563
|
+
addonId: string(),
|
|
14564
|
+
/**
|
|
14565
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
14566
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
14567
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
14568
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
14569
|
+
*
|
|
14570
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
14571
|
+
* client, and a native client joins it onto its own hub base.
|
|
14572
|
+
*
|
|
14573
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
14574
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
14575
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
14576
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
14577
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
14578
|
+
*/
|
|
14579
|
+
iconUrl: string().optional(),
|
|
14580
|
+
/**
|
|
14581
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
14582
|
+
*
|
|
14583
|
+
* The server knows this and therefore says it, because the client cannot
|
|
14584
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
14585
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
14586
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
14587
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
14588
|
+
*
|
|
14589
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
14590
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
14591
|
+
* its raster path, which is the safe default for an unknown image.
|
|
14592
|
+
*/
|
|
14593
|
+
iconMediaType: string().optional(),
|
|
14594
|
+
configSchema: ConfigSchemaPassthrough$3,
|
|
14595
|
+
/**
|
|
14596
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
14597
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
14598
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
14599
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
14600
|
+
* on every edit.
|
|
14601
|
+
*
|
|
14602
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
14603
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
14604
|
+
* it absent must assume ANY edit may need the secret.
|
|
14605
|
+
*/
|
|
14606
|
+
destinationKeys: array(string()).optional(),
|
|
14607
|
+
supportsDiscovery: boolean(),
|
|
14608
|
+
/**
|
|
14609
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
14610
|
+
* Absent = none; the rule editor then offers nothing.
|
|
14611
|
+
*/
|
|
14612
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
14613
|
+
caps: TargetKindCapsSchema
|
|
14614
|
+
});
|
|
14615
|
+
/**
|
|
14616
|
+
* A target as a provider's own code sees it (the notification-center bridge, the
|
|
14617
|
+
* send path): the flat record body split into the name, the switch and the
|
|
14618
|
+
* kind's fields. `config` holds secrets; a reader that serves it to a client
|
|
14619
|
+
* REDACTS secret fields (a presence marker only) — never round-trip a stored
|
|
14620
|
+
* secret to the UI.
|
|
14621
|
+
*/
|
|
14622
|
+
var TargetSchema = object({
|
|
14623
|
+
id: string(),
|
|
14624
|
+
name: string(),
|
|
14625
|
+
kind: string(),
|
|
14626
|
+
addonId: string(),
|
|
14627
|
+
enabled: boolean(),
|
|
14628
|
+
config: record(string(), unknown())
|
|
14629
|
+
});
|
|
14630
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
14631
|
+
var DiscoveredTargetSchema = object({
|
|
14632
|
+
kind: string(),
|
|
14633
|
+
suggestedName: string(),
|
|
14634
|
+
config: record(string(), unknown())
|
|
14635
|
+
});
|
|
14636
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
14637
|
+
var RenderedAsSchema = object({
|
|
14638
|
+
level: string(),
|
|
14639
|
+
format: NotificationFormatSchema,
|
|
14640
|
+
attachmentsSent: number().int().nonnegative(),
|
|
14641
|
+
actionsSent: number().int().nonnegative(),
|
|
14642
|
+
truncated: boolean(),
|
|
14643
|
+
dropped: array(string())
|
|
14644
|
+
});
|
|
14645
|
+
var SendResultSchema = object({
|
|
14646
|
+
success: boolean(),
|
|
14647
|
+
error: string().optional(),
|
|
14648
|
+
renderedAs: RenderedAsSchema.optional()
|
|
14649
|
+
});
|
|
14650
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
14651
|
+
var TestResultSchema = SendResultSchema;
|
|
14652
|
+
/**
|
|
14653
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
14654
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
14655
|
+
*/
|
|
14656
|
+
var ProbeTargetConfigResultSchema = object({
|
|
14657
|
+
ok: boolean(),
|
|
14658
|
+
resolvedUrl: string(),
|
|
14659
|
+
httpStatus: number().int().optional(),
|
|
14660
|
+
stateInstance: string().optional(),
|
|
14661
|
+
error: string().optional()
|
|
14662
|
+
});
|
|
14663
|
+
method(object({}), array(TargetKindSchema)), method(object({
|
|
14664
|
+
kind: string(),
|
|
14665
|
+
config: record(string(), unknown()).optional(),
|
|
14666
|
+
/**
|
|
14667
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
14668
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
14669
|
+
* secrets server-side before dialling.
|
|
14670
|
+
*/
|
|
14671
|
+
targetId: string().optional()
|
|
14672
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
14673
|
+
kind: string(),
|
|
14674
|
+
config: record(string(), unknown()),
|
|
14675
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
14676
|
+
targetId: string().optional()
|
|
14677
|
+
}), ProbeTargetConfigResultSchema, {
|
|
14678
|
+
kind: "mutation",
|
|
14679
|
+
auth: "admin"
|
|
14680
|
+
}), method(object({
|
|
14681
|
+
targetId: string(),
|
|
14682
|
+
notification: NotificationSchema
|
|
14683
|
+
}), SendResultSchema, {
|
|
14684
|
+
kind: "mutation",
|
|
14685
|
+
auth: "admin"
|
|
14686
|
+
}), method(object({
|
|
14687
|
+
targetId: string(),
|
|
14688
|
+
sample: NotificationSchema.optional()
|
|
14689
|
+
}), TestResultSchema, {
|
|
14690
|
+
kind: "mutation",
|
|
14691
|
+
auth: "admin"
|
|
14692
|
+
});
|
|
14693
|
+
looseObject({
|
|
14694
|
+
name: string().min(1),
|
|
14695
|
+
enabled: boolean().optional()
|
|
14696
|
+
});
|
|
14697
|
+
TargetSchema.omit({ id: true });
|
|
13988
14698
|
var EncodeProfileSchema = object({
|
|
13989
14699
|
video: object({
|
|
13990
14700
|
codec: _enum([
|
|
@@ -22202,7 +22912,6 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
22202
22912
|
"calendar-source": system(INTERNAL),
|
|
22203
22913
|
calendars: system("calendars feed schedules and conditions, not device actions"),
|
|
22204
22914
|
composer: system("device composition — an authoring tool, not an action"),
|
|
22205
|
-
"connection-test": system(INTERNAL),
|
|
22206
22915
|
"core-blocks": system(INTERNAL),
|
|
22207
22916
|
"custom-model-registry": system(ML),
|
|
22208
22917
|
"data-store-provider": system(INTERNAL),
|
|
@@ -22247,7 +22956,6 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
22247
22956
|
"notification-output": system("notification delivery — reached through notifier devices and rules"),
|
|
22248
22957
|
"notification-rules": system("notification rules — authored on their own page"),
|
|
22249
22958
|
"oauth-integration": system(IDENTITY),
|
|
22250
|
-
"oidc-connections": system(IDENTITY),
|
|
22251
22959
|
"osd-manager": system(PIPELINE),
|
|
22252
22960
|
"pipeline-executor": system(PIPELINE),
|
|
22253
22961
|
"pipeline-orchestrator": system(PIPELINE),
|
|
@@ -44119,427 +44827,6 @@ var UnitWarningSchema = object({
|
|
|
44119
44827
|
assumedUnit: string(),
|
|
44120
44828
|
message: string()
|
|
44121
44829
|
});
|
|
44122
|
-
/**
|
|
44123
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
44124
|
-
*
|
|
44125
|
-
* Apprise-derived model (see
|
|
44126
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
44127
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
44128
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
44129
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
44130
|
-
* message to what the kind supports — callers never special-case a service.
|
|
44131
|
-
*
|
|
44132
|
-
* DESIGN DECISIONS (locked):
|
|
44133
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
44134
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
44135
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
44136
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
44137
|
-
* alternative would fork the UI per addon and cannot host the
|
|
44138
|
-
* discovery→adopt flow.
|
|
44139
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
44140
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
44141
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
44142
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
44143
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
44144
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
44145
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
44146
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
44147
|
-
* base64 fallback needed.
|
|
44148
|
-
*
|
|
44149
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
44150
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
44151
|
-
* admin "Integrations" page.
|
|
44152
|
-
*/
|
|
44153
|
-
/**
|
|
44154
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
44155
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
44156
|
-
*/
|
|
44157
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
44158
|
-
"image",
|
|
44159
|
-
"video",
|
|
44160
|
-
"gif",
|
|
44161
|
-
"audio",
|
|
44162
|
-
"icon"
|
|
44163
|
-
]);
|
|
44164
|
-
/**
|
|
44165
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
44166
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
44167
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
44168
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
44169
|
-
*/
|
|
44170
|
-
var AttachmentSchema = object({
|
|
44171
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
44172
|
-
url: string().optional(),
|
|
44173
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
44174
|
-
mime: string().optional(),
|
|
44175
|
-
name: string().optional()
|
|
44176
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
44177
|
-
var NotificationFormatSchema = _enum([
|
|
44178
|
-
"text",
|
|
44179
|
-
"markdown",
|
|
44180
|
-
"html"
|
|
44181
|
-
]);
|
|
44182
|
-
/**
|
|
44183
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
44184
|
-
*
|
|
44185
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
44186
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
44187
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
44188
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
44189
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
44190
|
-
*
|
|
44191
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
44192
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
44193
|
-
* differently.
|
|
44194
|
-
*
|
|
44195
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
44196
|
-
*
|
|
44197
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
44198
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
44199
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
44200
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
44201
|
-
* decides its glyph, which is the only place that decision can be made
|
|
44202
|
-
* honestly.
|
|
44203
|
-
*
|
|
44204
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
44205
|
-
* `disarm` straight through; iOS feeds that string to
|
|
44206
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
44207
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
44208
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
44209
|
-
*
|
|
44210
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
44211
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
44212
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
44213
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
44214
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
44215
|
-
*/
|
|
44216
|
-
var NotificationActionIconSchema = _enum([
|
|
44217
|
-
"acknowledge",
|
|
44218
|
-
"dismiss",
|
|
44219
|
-
"silence",
|
|
44220
|
-
"snooze",
|
|
44221
|
-
"view",
|
|
44222
|
-
"play",
|
|
44223
|
-
"open",
|
|
44224
|
-
"close",
|
|
44225
|
-
"lock",
|
|
44226
|
-
"unlock",
|
|
44227
|
-
"arm",
|
|
44228
|
-
"arm-home",
|
|
44229
|
-
"arm-away",
|
|
44230
|
-
"arm-night",
|
|
44231
|
-
"disarm",
|
|
44232
|
-
"light",
|
|
44233
|
-
"alert",
|
|
44234
|
-
"camera"
|
|
44235
|
-
]);
|
|
44236
|
-
/** A single tap-through action button. */
|
|
44237
|
-
var NotificationActionSchema = object({
|
|
44238
|
-
id: string(),
|
|
44239
|
-
label: string(),
|
|
44240
|
-
url: string().optional(),
|
|
44241
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
44242
|
-
icon: NotificationActionIconSchema.optional(),
|
|
44243
|
-
/**
|
|
44244
|
-
* Renders in a warning style where the notifier supports it.
|
|
44245
|
-
*
|
|
44246
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
44247
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
44248
|
-
* does not buy.
|
|
44249
|
-
*/
|
|
44250
|
-
destructive: boolean().optional(),
|
|
44251
|
-
/**
|
|
44252
|
-
* How the tap should REACH the url.
|
|
44253
|
-
*
|
|
44254
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
44255
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
44256
|
-
* That is right for a button whose answer the operator wants to read.
|
|
44257
|
-
*
|
|
44258
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
44259
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
44260
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
44261
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
44262
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
44263
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
44264
|
-
* a requirement.
|
|
44265
|
-
*/
|
|
44266
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
44267
|
-
});
|
|
44268
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
44269
|
-
var DeliveryParamValueSchema = union([
|
|
44270
|
-
string(),
|
|
44271
|
-
number(),
|
|
44272
|
-
boolean()
|
|
44273
|
-
]);
|
|
44274
|
-
/**
|
|
44275
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
44276
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
44277
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
44278
|
-
* no editor knows a service by name.
|
|
44279
|
-
*
|
|
44280
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
44281
|
-
* run on the addon cap path).
|
|
44282
|
-
*/
|
|
44283
|
-
var DeliveryParamSchema = object({
|
|
44284
|
-
key: string().min(1),
|
|
44285
|
-
label: string(),
|
|
44286
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
44287
|
-
labelKey: string().optional(),
|
|
44288
|
-
type: _enum([
|
|
44289
|
-
"enum",
|
|
44290
|
-
"number",
|
|
44291
|
-
"boolean",
|
|
44292
|
-
"string"
|
|
44293
|
-
]),
|
|
44294
|
-
/** `enum` only: the closed set of accepted values. */
|
|
44295
|
-
options: array(object({
|
|
44296
|
-
value: string(),
|
|
44297
|
-
label: string()
|
|
44298
|
-
})).optional(),
|
|
44299
|
-
/** `number` only. */
|
|
44300
|
-
min: number().optional(),
|
|
44301
|
-
max: number().optional(),
|
|
44302
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
44303
|
-
default: DeliveryParamValueSchema.optional()
|
|
44304
|
-
});
|
|
44305
|
-
/**
|
|
44306
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
44307
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
44308
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
44309
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
44310
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
44311
|
-
* `priority` for that one target.
|
|
44312
|
-
*/
|
|
44313
|
-
var NotificationSchema = object({
|
|
44314
|
-
body: string(),
|
|
44315
|
-
title: string().optional(),
|
|
44316
|
-
format: NotificationFormatSchema.default("text"),
|
|
44317
|
-
priority: number().int().min(1).max(5).default(3),
|
|
44318
|
-
level: string().optional(),
|
|
44319
|
-
attachments: array(AttachmentSchema).optional(),
|
|
44320
|
-
clickUrl: string().optional(),
|
|
44321
|
-
actions: array(NotificationActionSchema).optional(),
|
|
44322
|
-
sound: string().optional(),
|
|
44323
|
-
ttl: number().optional(),
|
|
44324
|
-
tag: string().optional(),
|
|
44325
|
-
deviceId: number().optional(),
|
|
44326
|
-
eventId: string().optional(),
|
|
44327
|
-
metadata: record(string(), unknown()).optional(),
|
|
44328
|
-
/**
|
|
44329
|
-
* Values for the parameters the target's kind DECLARES in
|
|
44330
|
-
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
44331
|
-
* declaration by the sender; a notifier reads only the keys it declared.
|
|
44332
|
-
*/
|
|
44333
|
-
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
44334
|
-
});
|
|
44335
|
-
/** One declared native severity/priority level for a kind. */
|
|
44336
|
-
var TargetKindLevelSchema = object({
|
|
44337
|
-
id: string(),
|
|
44338
|
-
label: string(),
|
|
44339
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
44340
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
44341
|
-
flags: object({
|
|
44342
|
-
critical: boolean().optional(),
|
|
44343
|
-
silent: boolean().optional(),
|
|
44344
|
-
noPush: boolean().optional()
|
|
44345
|
-
}).optional(),
|
|
44346
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
44347
|
-
requires: array(string()).optional(),
|
|
44348
|
-
description: string().optional()
|
|
44349
|
-
});
|
|
44350
|
-
/** The full capability block consulted before dispatch. */
|
|
44351
|
-
var TargetKindCapsSchema = object({
|
|
44352
|
-
attachments: object({
|
|
44353
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
44354
|
-
mode: _enum([
|
|
44355
|
-
"url",
|
|
44356
|
-
"bytes",
|
|
44357
|
-
"both"
|
|
44358
|
-
]),
|
|
44359
|
-
max: number().int().nonnegative(),
|
|
44360
|
-
maxBytes: number().int().positive().optional()
|
|
44361
|
-
}),
|
|
44362
|
-
/** Max action buttons (0 = none). */
|
|
44363
|
-
actions: number().int().nonnegative(),
|
|
44364
|
-
/**
|
|
44365
|
-
* Whether this kind renders a per-action ICON.
|
|
44366
|
-
*
|
|
44367
|
-
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
44368
|
-
* run on the addon cap path — three production failures in one day taught
|
|
44369
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
44370
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
44371
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
44372
|
-
*/
|
|
44373
|
-
actionIcons: boolean().optional(),
|
|
44374
|
-
levels: array(TargetKindLevelSchema),
|
|
44375
|
-
format: array(NotificationFormatSchema),
|
|
44376
|
-
clickUrl: boolean(),
|
|
44377
|
-
sound: boolean(),
|
|
44378
|
-
ttl: boolean(),
|
|
44379
|
-
bodyMaxLen: number().int().positive()
|
|
44380
|
-
});
|
|
44381
|
-
/**
|
|
44382
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
44383
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
44384
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
44385
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
44386
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
44387
|
-
*/
|
|
44388
|
-
var ConfigSchemaPassthrough$4 = unknown();
|
|
44389
|
-
var TargetKindSchema = object({
|
|
44390
|
-
kind: string(),
|
|
44391
|
-
label: string(),
|
|
44392
|
-
icon: string(),
|
|
44393
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
44394
|
-
addonId: string(),
|
|
44395
|
-
/**
|
|
44396
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
44397
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
44398
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
44399
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
44400
|
-
*
|
|
44401
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
44402
|
-
* client, and a native client joins it onto its own hub base.
|
|
44403
|
-
*
|
|
44404
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
44405
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
44406
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
44407
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
44408
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
44409
|
-
*/
|
|
44410
|
-
iconUrl: string().optional(),
|
|
44411
|
-
/**
|
|
44412
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
44413
|
-
*
|
|
44414
|
-
* The server knows this and therefore says it, because the client cannot
|
|
44415
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
44416
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
44417
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
44418
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
44419
|
-
*
|
|
44420
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
44421
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
44422
|
-
* its raster path, which is the safe default for an unknown image.
|
|
44423
|
-
*/
|
|
44424
|
-
iconMediaType: string().optional(),
|
|
44425
|
-
configSchema: ConfigSchemaPassthrough$4,
|
|
44426
|
-
/**
|
|
44427
|
-
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
44428
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
44429
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
44430
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
44431
|
-
* on every edit.
|
|
44432
|
-
*
|
|
44433
|
-
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
44434
|
-
* kinds) and from a provider older than this field — an editor that finds
|
|
44435
|
-
* it absent must assume ANY edit may need the secret.
|
|
44436
|
-
*/
|
|
44437
|
-
destinationKeys: array(string()).optional(),
|
|
44438
|
-
supportsDiscovery: boolean(),
|
|
44439
|
-
/**
|
|
44440
|
-
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
44441
|
-
* Absent = none; the rule editor then offers nothing.
|
|
44442
|
-
*/
|
|
44443
|
-
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
44444
|
-
caps: TargetKindCapsSchema
|
|
44445
|
-
});
|
|
44446
|
-
/**
|
|
44447
|
-
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
44448
|
-
* (return a presence marker only) when serving `listTargets` — never
|
|
44449
|
-
* round-trip a stored secret to the UI.
|
|
44450
|
-
*/
|
|
44451
|
-
var TargetSchema = object({
|
|
44452
|
-
id: string(),
|
|
44453
|
-
name: string(),
|
|
44454
|
-
kind: string(),
|
|
44455
|
-
addonId: string(),
|
|
44456
|
-
enabled: boolean(),
|
|
44457
|
-
config: record(string(), unknown())
|
|
44458
|
-
});
|
|
44459
|
-
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
44460
|
-
var DiscoveredTargetSchema = object({
|
|
44461
|
-
kind: string(),
|
|
44462
|
-
suggestedName: string(),
|
|
44463
|
-
config: record(string(), unknown())
|
|
44464
|
-
});
|
|
44465
|
-
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
44466
|
-
var RenderedAsSchema = object({
|
|
44467
|
-
level: string(),
|
|
44468
|
-
format: NotificationFormatSchema,
|
|
44469
|
-
attachmentsSent: number().int().nonnegative(),
|
|
44470
|
-
actionsSent: number().int().nonnegative(),
|
|
44471
|
-
truncated: boolean(),
|
|
44472
|
-
dropped: array(string())
|
|
44473
|
-
});
|
|
44474
|
-
var SendResultSchema = object({
|
|
44475
|
-
success: boolean(),
|
|
44476
|
-
error: string().optional(),
|
|
44477
|
-
renderedAs: RenderedAsSchema.optional()
|
|
44478
|
-
});
|
|
44479
|
-
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
44480
|
-
var TestResultSchema = SendResultSchema;
|
|
44481
|
-
/**
|
|
44482
|
-
* Pre-save credential probe against a partial (unsaved) target config.
|
|
44483
|
-
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
44484
|
-
*/
|
|
44485
|
-
var ProbeTargetConfigResultSchema = object({
|
|
44486
|
-
ok: boolean(),
|
|
44487
|
-
resolvedUrl: string(),
|
|
44488
|
-
httpStatus: number().int().optional(),
|
|
44489
|
-
stateInstance: string().optional(),
|
|
44490
|
-
error: string().optional()
|
|
44491
|
-
});
|
|
44492
|
-
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
44493
|
-
kind: string(),
|
|
44494
|
-
config: record(string(), unknown()).optional(),
|
|
44495
|
-
/**
|
|
44496
|
-
* When editing / duplicating, the blank password field never round-trips
|
|
44497
|
-
* the stored token. Pass the source target id so the provider can restore
|
|
44498
|
-
* secrets server-side before dialling (same merge as upsert).
|
|
44499
|
-
*/
|
|
44500
|
-
targetId: string().optional()
|
|
44501
|
-
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
44502
|
-
kind: string(),
|
|
44503
|
-
config: record(string(), unknown()),
|
|
44504
|
-
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
44505
|
-
targetId: string().optional()
|
|
44506
|
-
}), ProbeTargetConfigResultSchema, {
|
|
44507
|
-
kind: "mutation",
|
|
44508
|
-
auth: "admin"
|
|
44509
|
-
}), method(object({
|
|
44510
|
-
targetId: string(),
|
|
44511
|
-
notification: NotificationSchema
|
|
44512
|
-
}), SendResultSchema, {
|
|
44513
|
-
kind: "mutation",
|
|
44514
|
-
auth: "admin"
|
|
44515
|
-
}), method(object({
|
|
44516
|
-
targetId: string(),
|
|
44517
|
-
sample: NotificationSchema.optional()
|
|
44518
|
-
}), TestResultSchema, {
|
|
44519
|
-
kind: "mutation",
|
|
44520
|
-
auth: "admin"
|
|
44521
|
-
}), method(object({
|
|
44522
|
-
target: TargetSchema,
|
|
44523
|
-
/**
|
|
44524
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
44525
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
44526
|
-
* password fields are restored from this source (D634 still applies if
|
|
44527
|
-
* destinations diverge without a re-typed secret).
|
|
44528
|
-
*/
|
|
44529
|
-
cloneSecretsFrom: string().optional()
|
|
44530
|
-
}), TargetSchema, {
|
|
44531
|
-
kind: "mutation",
|
|
44532
|
-
auth: "admin"
|
|
44533
|
-
}), method(object({ targetId: string() }), _void(), {
|
|
44534
|
-
kind: "mutation",
|
|
44535
|
-
auth: "admin"
|
|
44536
|
-
}), method(object({
|
|
44537
|
-
targetId: string(),
|
|
44538
|
-
enabled: boolean()
|
|
44539
|
-
}), _void(), {
|
|
44540
|
-
kind: "mutation",
|
|
44541
|
-
auth: "admin"
|
|
44542
|
-
});
|
|
44543
44830
|
_enum([
|
|
44544
44831
|
"automation",
|
|
44545
44832
|
"action",
|
|
@@ -44810,6 +45097,8 @@ var SettingsSchemaWithValuesSchema = object({
|
|
|
44810
45097
|
});
|
|
44811
45098
|
/** Patch object — keys are field names, values are the new field values. */
|
|
44812
45099
|
var SettingsPatchSchema = record(string(), unknown());
|
|
45100
|
+
/** The scope of a scoped-settings call (D986): where the capability declared the values. */
|
|
45101
|
+
var ScopedSettingsScopeSchema = _enum(SCOPED_SETTINGS_SCOPES);
|
|
44813
45102
|
/** Standard success response for update operations. */
|
|
44814
45103
|
var SettingsUpdateResultSchema = object({ success: literal(true) });
|
|
44815
45104
|
method(object({
|
|
@@ -44825,6 +45114,25 @@ method(object({
|
|
|
44825
45114
|
kind: "mutation",
|
|
44826
45115
|
auth: "admin"
|
|
44827
45116
|
}), method(object({
|
|
45117
|
+
addonId: string(),
|
|
45118
|
+
cap: string(),
|
|
45119
|
+
scope: ScopedSettingsScopeSchema,
|
|
45120
|
+
nodeId: string().optional()
|
|
45121
|
+
}), SettingsSchemaWithValuesSchema.nullable()), method(object({
|
|
45122
|
+
addonId: string(),
|
|
45123
|
+
cap: string(),
|
|
45124
|
+
scope: ScopedSettingsScopeSchema,
|
|
45125
|
+
nodeId: string().optional(),
|
|
45126
|
+
patch: SettingsPatchSchema
|
|
45127
|
+
}), SettingsUpdateResultSchema, {
|
|
45128
|
+
kind: "mutation",
|
|
45129
|
+
auth: "admin"
|
|
45130
|
+
}), method(object({
|
|
45131
|
+
addonId: string(),
|
|
45132
|
+
cap: string(),
|
|
45133
|
+
scope: ScopedSettingsScopeSchema,
|
|
45134
|
+
nodeId: string().optional()
|
|
45135
|
+
}), record(string(), unknown()).nullable()), method(object({
|
|
44828
45136
|
addonId: string(),
|
|
44829
45137
|
nodeId: string().optional()
|
|
44830
45138
|
}), SettingsSchemaWithValuesSchema.nullable()), method(object({
|
|
@@ -48417,37 +48725,6 @@ method(object({
|
|
|
48417
48725
|
kind: "mutation",
|
|
48418
48726
|
auth: "admin"
|
|
48419
48727
|
}), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
|
|
48420
|
-
var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
|
|
48421
|
-
object({
|
|
48422
|
-
outcome: literal("validated"),
|
|
48423
|
-
/** Round-trip of the sign-in, when the provider measured it. */
|
|
48424
|
-
latencyMs: number().nonnegative().optional(),
|
|
48425
|
-
/** Optional human detail worth showing next to the tick
|
|
48426
|
-
* ("3 devices visible on this account"). */
|
|
48427
|
-
detail: string().optional()
|
|
48428
|
-
}).strict(),
|
|
48429
|
-
object({
|
|
48430
|
-
outcome: literal("rejected"),
|
|
48431
|
-
error: string()
|
|
48432
|
-
}).strict(),
|
|
48433
|
-
object({
|
|
48434
|
-
outcome: literal("inconclusive"),
|
|
48435
|
-
error: string()
|
|
48436
|
-
}).strict()
|
|
48437
|
-
]);
|
|
48438
|
-
var ConnectionTestInputSchema = object({
|
|
48439
|
-
/** Candidate integration settings, exactly as the create form collected them. */
|
|
48440
|
-
settings: record(string(), unknown()) });
|
|
48441
|
-
/**
|
|
48442
|
-
* What the provider's test actually DOES, so the UI can say it in words before
|
|
48443
|
-
* the operator presses the button ("Signs in to the Dreo cloud"). Purely
|
|
48444
|
-
* descriptive — it never changes routing.
|
|
48445
|
-
*/
|
|
48446
|
-
var ConnectionTestDescriptorSchema = object({ label: string() });
|
|
48447
|
-
method(ConnectionTestInputSchema, ConnectionTestOutcomeSchema, {
|
|
48448
|
-
kind: "mutation",
|
|
48449
|
-
auth: "admin"
|
|
48450
|
-
}), method(_void(), ConnectionTestDescriptorSchema, { auth: "admin" });
|
|
48451
48728
|
object({
|
|
48452
48729
|
/** True when the upstream system considers the entity connected. */
|
|
48453
48730
|
connected: boolean(),
|
|
@@ -54301,10 +54578,9 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
54301
54578
|
* locations" checkbox. Provider-declared in the addon manifest. */
|
|
54302
54579
|
supportsLocationImport: boolean(),
|
|
54303
54580
|
/**
|
|
54304
|
-
* True when this integration
|
|
54305
|
-
*
|
|
54306
|
-
*
|
|
54307
|
-
* rather than offering a button that always answers the same nonsense.
|
|
54581
|
+
* True when this integration's instances declare a Test (`integration-instances.describe().canTest`,
|
|
54582
|
+
* D1016). An integration that cannot be tested must say so up front rather than offering a
|
|
54583
|
+
* button that always answers the same nonsense.
|
|
54308
54584
|
*/
|
|
54309
54585
|
canTest: boolean(),
|
|
54310
54586
|
existingInstances: array(object({
|
|
@@ -54358,7 +54634,9 @@ var CreateIntegrationInputSchema = object({
|
|
|
54358
54634
|
info: record(string(), unknown()).optional(),
|
|
54359
54635
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
54360
54636
|
originStableId: string().optional(),
|
|
54361
|
-
settings: record(string(), unknown()).optional()
|
|
54637
|
+
settings: record(string(), unknown()).optional(),
|
|
54638
|
+
/** The instance kind being created, for a provider with several; omitted = its only testable kind. */
|
|
54639
|
+
kind: string().min(1).optional()
|
|
54362
54640
|
});
|
|
54363
54641
|
var UpdateIntegrationInputSchema = object({
|
|
54364
54642
|
id: string(),
|
|
@@ -54384,13 +54662,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
54384
54662
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
54385
54663
|
kind: "mutation",
|
|
54386
54664
|
auth: "admin"
|
|
54387
|
-
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" })
|
|
54388
|
-
addonId: string(),
|
|
54389
|
-
settings: record(string(), unknown())
|
|
54390
|
-
}), TestConnectionResultSchema, {
|
|
54391
|
-
kind: "mutation",
|
|
54392
|
-
auth: "admin"
|
|
54393
|
-
});
|
|
54665
|
+
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" });
|
|
54394
54666
|
/**
|
|
54395
54667
|
* `integration-instances` - the connection of an addon that holds ONE or SEVERAL
|
|
54396
54668
|
* (an account, a relay, a destination) as typed records (D985, D986). The addon's
|
|
@@ -54410,7 +54682,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
54410
54682
|
* or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
|
|
54411
54683
|
* clears it.
|
|
54412
54684
|
*/
|
|
54413
|
-
var ConfigSchemaPassthrough$
|
|
54685
|
+
var ConfigSchemaPassthrough$2 = custom();
|
|
54414
54686
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
54415
54687
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
54416
54688
|
/**
|
|
@@ -54456,7 +54728,16 @@ var IntegrationInstanceKindSchema = object({
|
|
|
54456
54728
|
/** Relative hub path to start an interactive account link of an instance of this kind. */
|
|
54457
54729
|
connectPath: string().min(1).optional(),
|
|
54458
54730
|
/** Relative hub path to disconnect that account. */
|
|
54459
|
-
disconnectPath: string().min(1).optional()
|
|
54731
|
+
disconnectPath: string().min(1).optional(),
|
|
54732
|
+
/**
|
|
54733
|
+
* Parts of the edit dialog the flat form cannot draw (a node picker, a model source), named by a
|
|
54734
|
+
* GENERIC widget id the client maps to a component; a client that knows none of them shows none.
|
|
54735
|
+
* `connection` widgets write connection values, `model` widgets own the model choice.
|
|
54736
|
+
*/
|
|
54737
|
+
widgets: array(object({
|
|
54738
|
+
id: string().min(1),
|
|
54739
|
+
placement: _enum(["connection", "model"])
|
|
54740
|
+
})).optional()
|
|
54460
54741
|
});
|
|
54461
54742
|
/**
|
|
54462
54743
|
* How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
|
|
@@ -54561,7 +54842,7 @@ var integrationInstancesCapability = {
|
|
|
54561
54842
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
54562
54843
|
/** The kind of the create form (`id: null`) of a provider with several. */
|
|
54563
54844
|
kind: string().min(1).optional()
|
|
54564
|
-
}), ConfigSchemaPassthrough$
|
|
54845
|
+
}), ConfigSchemaPassthrough$2, { auth: "admin" }),
|
|
54565
54846
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
54566
54847
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
54567
54848
|
/**
|
|
@@ -55512,12 +55793,12 @@ var LlmModelInfoSchema = object({
|
|
|
55512
55793
|
});
|
|
55513
55794
|
/**
|
|
55514
55795
|
* The provider-specific part of a profile, validated by the provider that owns
|
|
55515
|
-
* the kind (
|
|
55796
|
+
* the kind (saving an instance refuses what it does not accept). A field its
|
|
55516
55797
|
* `connectionSchema` declares `type: 'password'` is a secret: it is held in the
|
|
55517
55798
|
* keychain, never in the row, and reads back as the redaction marker.
|
|
55518
55799
|
*/
|
|
55519
55800
|
var LlmProviderConfigSchema = record(string(), unknown());
|
|
55520
|
-
|
|
55801
|
+
object({
|
|
55521
55802
|
id: string(),
|
|
55522
55803
|
name: string(),
|
|
55523
55804
|
/** The provider-declared kind (`LlmProfileKindDescriptor.kind`). */
|
|
@@ -55611,8 +55892,8 @@ var LlmProviderWidgetSchema = object({
|
|
|
55611
55892
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
55612
55893
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
55613
55894
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
55614
|
-
var ConfigSchemaPassthrough$
|
|
55615
|
-
|
|
55895
|
+
var ConfigSchemaPassthrough$1 = unknown();
|
|
55896
|
+
object({
|
|
55616
55897
|
kind: LlmProfileKindSchema,
|
|
55617
55898
|
label: string(),
|
|
55618
55899
|
/** A generic icon name (`cloud`, `plug`, `cpu`) — never a glyph or a logo. */
|
|
@@ -55620,9 +55901,9 @@ var LlmProfileKindDescriptorSchema = object({
|
|
|
55620
55901
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
55621
55902
|
addonId: string(),
|
|
55622
55903
|
/** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
|
|
55623
|
-
connectionSchema: ConfigSchemaPassthrough$
|
|
55904
|
+
connectionSchema: ConfigSchemaPassthrough$1,
|
|
55624
55905
|
/** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
|
|
55625
|
-
advancedSchema: ConfigSchemaPassthrough$
|
|
55906
|
+
advancedSchema: ConfigSchemaPassthrough$1.optional(),
|
|
55626
55907
|
/** The kind's defaults; a model from `listModels` may override any of them. */
|
|
55627
55908
|
features: LlmModelFeaturesSchema,
|
|
55628
55909
|
/** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
|
|
@@ -55751,20 +56032,7 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
|
|
|
55751
56032
|
}), method(object({
|
|
55752
56033
|
addonId: string().optional(),
|
|
55753
56034
|
requestId: string()
|
|
55754
|
-
}), _void(), { kind: "mutation" }), method(
|
|
55755
|
-
kind: "mutation",
|
|
55756
|
-
auth: "admin"
|
|
55757
|
-
}), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
|
|
55758
|
-
kind: "mutation",
|
|
55759
|
-
auth: "admin"
|
|
55760
|
-
}), method(ProfileRefInputSchema, _void(), {
|
|
55761
|
-
kind: "mutation",
|
|
55762
|
-
auth: "admin"
|
|
55763
|
-
}), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
|
|
55764
|
-
kind: "mutation",
|
|
55765
|
-
auth: "admin",
|
|
55766
|
-
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
55767
|
-
}), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
|
|
56035
|
+
}), _void(), { kind: "mutation" }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
|
|
55768
56036
|
kind: "mutation",
|
|
55769
56037
|
auth: "admin"
|
|
55770
56038
|
}), method(object({}), array(LlmDefaultSchema)), method(object({
|
|
@@ -56759,20 +57027,7 @@ method(_void(), MeshStatusSchema), method(object({
|
|
|
56759
57027
|
/** Optional hostname override the host should advertise once joined. */
|
|
56760
57028
|
hostname: string().optional() }), object({
|
|
56761
57029
|
/** Authentication URL the operator should open in a browser. */
|
|
56762
|
-
loginUrl: string() }), { kind: "mutation" }), method(_void(), object({ left: literal(true) }), { kind: "mutation" }), method(_void(), object({ loggedOut: literal(true) }), { kind: "mutation" }), method(_void(), object({ peers: array(MeshPeerSchema).readonly() }))
|
|
56763
|
-
/** Optional auth key — when provided, probes the key validity
|
|
56764
|
-
* against the provider's API. Omit when already joined to
|
|
56765
|
-
* just ping the daemon. */
|
|
56766
|
-
authKey: string().optional() }), object({
|
|
56767
|
-
ok: boolean(),
|
|
56768
|
-
/** Provider-side identifier resolved by the probe (tailnet
|
|
56769
|
-
* name for Tailscale, network id for ZeroTier, etc.). */
|
|
56770
|
-
tenant: string().optional(),
|
|
56771
|
-
/** Daemon binary version, when reachable. */
|
|
56772
|
-
daemonVersion: string().optional(),
|
|
56773
|
-
/** Human-readable error when `ok: false`. */
|
|
56774
|
-
error: string().optional()
|
|
56775
|
-
}), { kind: "mutation" });
|
|
57030
|
+
loginUrl: string() }), { kind: "mutation" }), method(_void(), object({ left: literal(true) }), { kind: "mutation" }), method(_void(), object({ loggedOut: literal(true) }), { kind: "mutation" }), method(_void(), object({ peers: array(MeshPeerSchema).readonly() }));
|
|
56776
57031
|
object({
|
|
56777
57032
|
value: number().nullable().meta(readingMeta("device", { label: "Total" })),
|
|
56778
57033
|
lastFetchedAt: number()
|
|
@@ -62633,13 +62888,6 @@ var AddBrokerInputSchema = object({
|
|
|
62633
62888
|
});
|
|
62634
62889
|
var AddBrokerResultSchema = object({ id: string() });
|
|
62635
62890
|
var IdInputSchema = object({ id: string() });
|
|
62636
|
-
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
62637
|
-
ok: literal(true),
|
|
62638
|
-
latencyMs: number()
|
|
62639
|
-
}), object({
|
|
62640
|
-
ok: literal(false),
|
|
62641
|
-
error: string()
|
|
62642
|
-
})]);
|
|
62643
62891
|
var StartEmbeddedInputSchema = object({
|
|
62644
62892
|
port: number().int().min(1).max(65535).default(1883),
|
|
62645
62893
|
/** Allow anonymous connect (no username/password). Default: false. */
|
|
@@ -62662,9 +62910,6 @@ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnection
|
|
|
62662
62910
|
}), method(IdInputSchema, _void(), {
|
|
62663
62911
|
kind: "mutation",
|
|
62664
62912
|
auth: "admin"
|
|
62665
|
-
}), method(IdInputSchema, TestResultSchema$1, {
|
|
62666
|
-
kind: "mutation",
|
|
62667
|
-
auth: "admin"
|
|
62668
62913
|
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
62669
62914
|
kind: "mutation",
|
|
62670
62915
|
auth: "admin"
|
|
@@ -66440,61 +66685,6 @@ var OauthIntegrationDescriptorSchema = object({
|
|
|
66440
66685
|
});
|
|
66441
66686
|
method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
|
|
66442
66687
|
/**
|
|
66443
|
-
* `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
|
|
66444
|
-
*
|
|
66445
|
-
* One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
|
|
66446
|
-
* ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
|
|
66447
|
-
* Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
|
|
66448
|
-
* immutable after create.
|
|
66449
|
-
*/
|
|
66450
|
-
var ConfigSchemaPassthrough$1 = unknown();
|
|
66451
|
-
var OidcConnectionSchema = object({
|
|
66452
|
-
id: string().min(1),
|
|
66453
|
-
displayName: string(),
|
|
66454
|
-
icon: string(),
|
|
66455
|
-
issuerUrl: string(),
|
|
66456
|
-
clientId: string(),
|
|
66457
|
-
/** Client id + secret present — never the secret value. */
|
|
66458
|
-
configured: boolean(),
|
|
66459
|
-
/**
|
|
66460
|
-
* Values for the connection form schema. `clientSecret` is a keychain
|
|
66461
|
-
* {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
|
|
66462
|
-
*/
|
|
66463
|
-
formValues: record(string(), unknown()).optional()
|
|
66464
|
-
});
|
|
66465
|
-
/**
|
|
66466
|
-
* Patch one connection. Omitted or empty `clientSecret` keeps the stored
|
|
66467
|
-
* secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
|
|
66468
|
-
*/
|
|
66469
|
-
var OidcConnectionUpdateInputSchema = object({
|
|
66470
|
-
id: string().min(1),
|
|
66471
|
-
displayName: string().min(1),
|
|
66472
|
-
icon: string().optional(),
|
|
66473
|
-
issuerUrl: string().optional(),
|
|
66474
|
-
clientId: string().optional(),
|
|
66475
|
-
clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
|
|
66476
|
-
redirectUri: string().optional(),
|
|
66477
|
-
scopes: string().optional(),
|
|
66478
|
-
usernameClaim: _enum([
|
|
66479
|
-
"preferred_username",
|
|
66480
|
-
"email",
|
|
66481
|
-
"sub"
|
|
66482
|
-
]).optional()
|
|
66483
|
-
});
|
|
66484
|
-
method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
|
|
66485
|
-
id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
|
|
66486
|
-
displayName: string().min(1)
|
|
66487
|
-
}), object({ id: string() }), {
|
|
66488
|
-
kind: "mutation",
|
|
66489
|
-
auth: "admin"
|
|
66490
|
-
}), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
|
|
66491
|
-
kind: "mutation",
|
|
66492
|
-
auth: "admin"
|
|
66493
|
-
}), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
|
|
66494
|
-
kind: "mutation",
|
|
66495
|
-
auth: "admin"
|
|
66496
|
-
});
|
|
66497
|
-
/**
|
|
66498
66688
|
* Generic on-screen-display (video overlay) cap. Each camera exposes
|
|
66499
66689
|
* a firmware-defined list of overlays (channel name, timestamp,
|
|
66500
66690
|
* watermark, logo, …); the cap models them as a homogeneous array of
|
|
@@ -72680,21 +72870,6 @@ method(object({
|
|
|
72680
72870
|
*/
|
|
72681
72871
|
ttlSec: union([number().int().positive(), literal("never")]).optional()
|
|
72682
72872
|
}), object({ token: string() }), { auth: "admin" }), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" });
|
|
72683
|
-
var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
|
|
72684
|
-
providerId: string().min(1),
|
|
72685
|
-
displayName: string().min(1),
|
|
72686
|
-
configSchema: unknown(),
|
|
72687
|
-
nodeLocal: boolean(),
|
|
72688
|
-
shouldSaveDiskSpace: literal(true),
|
|
72689
|
-
minFreePercent: number().min(0).max(100)
|
|
72690
|
-
}), object({
|
|
72691
|
-
providerId: string().min(1),
|
|
72692
|
-
displayName: string().min(1),
|
|
72693
|
-
configSchema: unknown(),
|
|
72694
|
-
nodeLocal: boolean(),
|
|
72695
|
-
shouldSaveDiskSpace: literal(false),
|
|
72696
|
-
minFreePercent: literal(null)
|
|
72697
|
-
})]);
|
|
72698
72873
|
method(object({
|
|
72699
72874
|
location: StorageLocationRefSchema,
|
|
72700
72875
|
relativePath: string()
|
|
@@ -72756,42 +72931,7 @@ method(object({
|
|
|
72756
72931
|
}), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
|
|
72757
72932
|
kind: "mutation",
|
|
72758
72933
|
auth: "admin"
|
|
72759
|
-
}), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(
|
|
72760
|
-
createdAt: true,
|
|
72761
|
-
updatedAt: true
|
|
72762
|
-
}), StorageLocationSchema, {
|
|
72763
|
-
kind: "mutation",
|
|
72764
|
-
auth: "admin"
|
|
72765
|
-
}), method(object({
|
|
72766
|
-
id: string(),
|
|
72767
|
-
force: boolean().optional()
|
|
72768
|
-
}), _void(), {
|
|
72769
|
-
kind: "mutation",
|
|
72770
|
-
auth: "admin"
|
|
72771
|
-
}), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
|
|
72772
|
-
ok: boolean(),
|
|
72773
|
-
error: string().optional()
|
|
72774
|
-
}), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
|
|
72775
|
-
providerId: string(),
|
|
72776
|
-
/**
|
|
72777
|
-
* The location this config is an UNSAVED edit of, when there is one.
|
|
72778
|
-
*
|
|
72779
|
-
* `listLocations` replaces every declared secret with the redaction
|
|
72780
|
-
* sentinel, so the edit modal's form state holds the sentinel for any
|
|
72781
|
-
* credential the operator did not retype — and posting that here
|
|
72782
|
-
* without a way to resolve it makes the provider try to authenticate
|
|
72783
|
-
* as `__camstack_redacted__` and report the operator's own working
|
|
72784
|
-
* password as wrong. Given this id, the orchestrator restores each
|
|
72785
|
-
* sentinel from the stored config (same rule as `upsertLocation`)
|
|
72786
|
-
* before dispatching. Omitted by the "Add location" wizard, where
|
|
72787
|
-
* every value was typed just now and nothing is stored yet.
|
|
72788
|
-
*/
|
|
72789
|
-
locationId: string().optional(),
|
|
72790
|
-
config: record(string(), unknown())
|
|
72791
|
-
}), object({
|
|
72792
|
-
ok: boolean(),
|
|
72793
|
-
error: string().optional()
|
|
72794
|
-
}), { auth: "admin" });
|
|
72934
|
+
}), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(_void(), array(StorageDrainProgressSchema).readonly());
|
|
72795
72935
|
/** One table of the settings database. */
|
|
72796
72936
|
var StorageTableSchema = object({
|
|
72797
72937
|
/** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
|
|
@@ -74022,7 +74162,7 @@ var TerminalInstanceInfoSchema = object({
|
|
|
74022
74162
|
/**
|
|
74023
74163
|
* False while the node the instance is bound to is not in the cluster
|
|
74024
74164
|
* topology (forgotten, renamed, re-paired). The instance is kept; the
|
|
74025
|
-
* operator re-points it
|
|
74165
|
+
* operator re-points it on the Terminal page.
|
|
74026
74166
|
*/
|
|
74027
74167
|
nodeInCluster: boolean(),
|
|
74028
74168
|
profileId: string(),
|
|
@@ -74060,41 +74200,7 @@ var TerminalOutputBatchSchema = object({
|
|
|
74060
74200
|
snapshot: string().optional(),
|
|
74061
74201
|
events: array(TerminalOutputEventSchema).readonly()
|
|
74062
74202
|
});
|
|
74063
|
-
method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(
|
|
74064
|
-
targetNodeId: string().min(1),
|
|
74065
|
-
profileId: string().min(1),
|
|
74066
|
-
name: string().trim().min(1).max(160).optional(),
|
|
74067
|
-
executable: string().max(1024).optional(),
|
|
74068
|
-
args: array(string().max(2048)).max(64).optional(),
|
|
74069
|
-
cwd: string().max(1024).optional(),
|
|
74070
|
-
environment: array(string().max(4096)).max(64).optional(),
|
|
74071
|
-
profileSettings: ProfileSettingsBagSchema.optional()
|
|
74072
|
-
}), TerminalInstanceInfoSchema, {
|
|
74073
|
-
kind: "mutation",
|
|
74074
|
-
auth: "admin"
|
|
74075
|
-
}), method(object({
|
|
74076
|
-
instanceId: string().min(1),
|
|
74077
|
-
/** Re-point the instance to another node; the profile must exist there. */
|
|
74078
|
-
targetNodeId: string().min(1).max(256).optional(),
|
|
74079
|
-
name: string().trim().min(1).max(160).optional(),
|
|
74080
|
-
executable: string().max(1024).optional(),
|
|
74081
|
-
args: array(string().max(2048)).max(64).optional(),
|
|
74082
|
-
cwd: string().max(1024).optional(),
|
|
74083
|
-
environment: array(string().max(4096)).max(64).optional(),
|
|
74084
|
-
profileSettings: ProfileSettingsBagSchema.optional()
|
|
74085
|
-
}), TerminalInstanceInfoSchema, {
|
|
74086
|
-
kind: "mutation",
|
|
74087
|
-
auth: "admin"
|
|
74088
|
-
}), method(object({ instanceId: string().min(1) }), _void(), {
|
|
74089
|
-
kind: "mutation",
|
|
74090
|
-
auth: "admin"
|
|
74091
|
-
}), method(object({
|
|
74092
|
-
instanceId: string().min(1),
|
|
74093
|
-
enabled: boolean()
|
|
74094
|
-
}), TerminalInstanceInfoSchema, {
|
|
74095
|
-
kind: "mutation",
|
|
74096
|
-
auth: "admin"
|
|
74097
|
-
}), method(_void(), array(TerminalLegacyCameraSchema).readonly(), { auth: "admin" }), method(object({
|
|
74203
|
+
method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalLegacyCameraSchema).readonly(), { auth: "admin" }), method(object({
|
|
74098
74204
|
stableId: string().min(1),
|
|
74099
74205
|
name: string().trim().min(1).max(160).optional()
|
|
74100
74206
|
}), TerminalInstanceInfoSchema, {
|
|
@@ -74289,7 +74395,7 @@ var TrainingProviderTypeSchema = object({
|
|
|
74289
74395
|
label: string(),
|
|
74290
74396
|
/** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
|
|
74291
74397
|
iconAsset: string(),
|
|
74292
|
-
/** Owning `training-provider` addon
|
|
74398
|
+
/** Owning `training-provider` addon. */
|
|
74293
74399
|
addonId: string(),
|
|
74294
74400
|
/**
|
|
74295
74401
|
* Fields for one entry of this type (credentials + defaults). Absent on a
|
|
@@ -74334,36 +74440,6 @@ var TrainingProviderDescriptorSchema = object({
|
|
|
74334
74440
|
*/
|
|
74335
74441
|
formValues: record(string(), unknown()).optional()
|
|
74336
74442
|
});
|
|
74337
|
-
/**
|
|
74338
|
-
* Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
|
|
74339
|
-
* An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
|
|
74340
|
-
* `{ $secretClear: true }` clears it.
|
|
74341
|
-
*/
|
|
74342
|
-
var TrainingProviderUpdateEntryInputSchema = object({
|
|
74343
|
-
entryId: string().min(1),
|
|
74344
|
-
name: string().min(1),
|
|
74345
|
-
tokenId: string().optional(),
|
|
74346
|
-
tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
|
|
74347
|
-
environment: string().optional(),
|
|
74348
|
-
gpuType: string().optional(),
|
|
74349
|
-
region: string().optional(),
|
|
74350
|
-
costCapUsd: number().nonnegative().optional(),
|
|
74351
|
-
maxRuntimeSec: number().int().positive().optional(),
|
|
74352
|
-
failedDatasetGraceHours: number().nonnegative().optional()
|
|
74353
|
-
});
|
|
74354
|
-
var TrainingCredentialCheckSchema = object({
|
|
74355
|
-
ok: boolean(),
|
|
74356
|
-
/** Workspace / account name, when the provider says. */
|
|
74357
|
-
account: string().optional(),
|
|
74358
|
-
reason: _enum([
|
|
74359
|
-
"missing",
|
|
74360
|
-
"invalid",
|
|
74361
|
-
"forbidden",
|
|
74362
|
-
"network",
|
|
74363
|
-
"quota"
|
|
74364
|
-
]).optional(),
|
|
74365
|
-
detail: string().optional()
|
|
74366
|
-
});
|
|
74367
74443
|
var ProviderJobStateSchema = _enum([
|
|
74368
74444
|
"pending",
|
|
74369
74445
|
"starting",
|
|
@@ -74411,22 +74487,6 @@ var TrainingLogLineSchema = object({
|
|
|
74411
74487
|
text: string()
|
|
74412
74488
|
});
|
|
74413
74489
|
method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderTypeSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderEntrySchema).readonly(), { auth: "admin" }), method(object({
|
|
74414
|
-
typeId: string().min(1),
|
|
74415
|
-
name: string().min(1)
|
|
74416
|
-
}), object({ entryId: string() }), {
|
|
74417
|
-
kind: "mutation",
|
|
74418
|
-
auth: "admin"
|
|
74419
|
-
}), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
|
|
74420
|
-
kind: "mutation",
|
|
74421
|
-
auth: "admin"
|
|
74422
|
-
}), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
|
|
74423
|
-
kind: "mutation",
|
|
74424
|
-
auth: "admin"
|
|
74425
|
-
}), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
|
|
74426
|
-
kind: "mutation",
|
|
74427
|
-
auth: "admin",
|
|
74428
|
-
timeoutMs: 3e4
|
|
74429
|
-
}), method(object({
|
|
74430
74490
|
entryId: string().min(1),
|
|
74431
74491
|
gpuType: string(),
|
|
74432
74492
|
region: string(),
|
|
@@ -76958,6 +77018,20 @@ Object.freeze({
|
|
|
76958
77018
|
access: "view",
|
|
76959
77019
|
role: "admin"
|
|
76960
77020
|
},
|
|
77021
|
+
"addonSettings.getScopedSettings": {
|
|
77022
|
+
capName: "addon-settings",
|
|
77023
|
+
capScope: "system",
|
|
77024
|
+
addonId: null,
|
|
77025
|
+
access: "view",
|
|
77026
|
+
role: "admin"
|
|
77027
|
+
},
|
|
77028
|
+
"addonSettings.readScopedValues": {
|
|
77029
|
+
capName: "addon-settings",
|
|
77030
|
+
capScope: "system",
|
|
77031
|
+
addonId: null,
|
|
77032
|
+
access: "view",
|
|
77033
|
+
role: "admin"
|
|
77034
|
+
},
|
|
76961
77035
|
"addonSettings.unsetStoredSettingsKeys": {
|
|
76962
77036
|
capName: "addon-settings",
|
|
76963
77037
|
capScope: "system",
|
|
@@ -76979,6 +77053,13 @@ Object.freeze({
|
|
|
76979
77053
|
access: "create",
|
|
76980
77054
|
role: "admin"
|
|
76981
77055
|
},
|
|
77056
|
+
"addonSettings.updateScopedSettings": {
|
|
77057
|
+
capName: "addon-settings",
|
|
77058
|
+
capScope: "system",
|
|
77059
|
+
addonId: null,
|
|
77060
|
+
access: "create",
|
|
77061
|
+
role: "admin"
|
|
77062
|
+
},
|
|
76982
77063
|
"addonWidgets.listWidgets": {
|
|
76983
77064
|
capName: "addon-widgets",
|
|
76984
77065
|
capScope: "system",
|
|
@@ -77812,20 +77893,6 @@ Object.freeze({
|
|
|
77812
77893
|
access: "view",
|
|
77813
77894
|
role: "admin"
|
|
77814
77895
|
},
|
|
77815
|
-
"connectionTest.describeTest": {
|
|
77816
|
-
capName: "connection-test",
|
|
77817
|
-
capScope: "system",
|
|
77818
|
-
addonId: null,
|
|
77819
|
-
access: "view",
|
|
77820
|
-
role: "admin"
|
|
77821
|
-
},
|
|
77822
|
-
"connectionTest.testSettings": {
|
|
77823
|
-
capName: "connection-test",
|
|
77824
|
-
capScope: "system",
|
|
77825
|
-
addonId: null,
|
|
77826
|
-
access: "create",
|
|
77827
|
-
role: "admin"
|
|
77828
|
-
},
|
|
77829
77896
|
"connectivity.getStatus": {
|
|
77830
77897
|
capName: "connectivity",
|
|
77831
77898
|
capScope: "device",
|
|
@@ -80192,13 +80259,6 @@ Object.freeze({
|
|
|
80192
80259
|
access: "view",
|
|
80193
80260
|
role: "admin"
|
|
80194
80261
|
},
|
|
80195
|
-
"integrations.testConnection": {
|
|
80196
|
-
capName: "integrations",
|
|
80197
|
-
capScope: "system",
|
|
80198
|
-
addonId: null,
|
|
80199
|
-
access: "create",
|
|
80200
|
-
role: "admin"
|
|
80201
|
-
},
|
|
80202
80262
|
"integrations.update": {
|
|
80203
80263
|
capName: "integrations",
|
|
80204
80264
|
capScope: "system",
|
|
@@ -80451,13 +80511,6 @@ Object.freeze({
|
|
|
80451
80511
|
access: "delete",
|
|
80452
80512
|
role: "admin"
|
|
80453
80513
|
},
|
|
80454
|
-
"llm.deleteProfile": {
|
|
80455
|
-
capName: "llm",
|
|
80456
|
-
capScope: "system",
|
|
80457
|
-
addonId: null,
|
|
80458
|
-
access: "delete",
|
|
80459
|
-
role: "admin"
|
|
80460
|
-
},
|
|
80461
80514
|
"llm.generate": {
|
|
80462
80515
|
capName: "llm",
|
|
80463
80516
|
capScope: "system",
|
|
@@ -80521,20 +80574,6 @@ Object.freeze({
|
|
|
80521
80574
|
access: "view",
|
|
80522
80575
|
role: "admin"
|
|
80523
80576
|
},
|
|
80524
|
-
"llm.listProfileKinds": {
|
|
80525
|
-
capName: "llm",
|
|
80526
|
-
capScope: "system",
|
|
80527
|
-
addonId: null,
|
|
80528
|
-
access: "view",
|
|
80529
|
-
role: "admin"
|
|
80530
|
-
},
|
|
80531
|
-
"llm.listProfiles": {
|
|
80532
|
-
capName: "llm",
|
|
80533
|
-
capScope: "system",
|
|
80534
|
-
addonId: null,
|
|
80535
|
-
access: "view",
|
|
80536
|
-
role: "admin"
|
|
80537
|
-
},
|
|
80538
80577
|
"llm.listRuntimeNodes": {
|
|
80539
80578
|
capName: "llm",
|
|
80540
80579
|
capScope: "system",
|
|
@@ -80542,13 +80581,6 @@ Object.freeze({
|
|
|
80542
80581
|
access: "view",
|
|
80543
80582
|
role: "admin"
|
|
80544
80583
|
},
|
|
80545
|
-
"llm.migrateRawSecrets": {
|
|
80546
|
-
capName: "llm",
|
|
80547
|
-
capScope: "system",
|
|
80548
|
-
addonId: null,
|
|
80549
|
-
access: "create",
|
|
80550
|
-
role: "admin"
|
|
80551
|
-
},
|
|
80552
80584
|
"llm.resolveModelRef": {
|
|
80553
80585
|
capName: "llm",
|
|
80554
80586
|
capScope: "system",
|
|
@@ -80577,20 +80609,6 @@ Object.freeze({
|
|
|
80577
80609
|
access: "create",
|
|
80578
80610
|
role: "admin"
|
|
80579
80611
|
},
|
|
80580
|
-
"llm.testProfile": {
|
|
80581
|
-
capName: "llm",
|
|
80582
|
-
capScope: "system",
|
|
80583
|
-
addonId: null,
|
|
80584
|
-
access: "create",
|
|
80585
|
-
role: "admin"
|
|
80586
|
-
},
|
|
80587
|
-
"llm.upsertProfile": {
|
|
80588
|
-
capName: "llm",
|
|
80589
|
-
capScope: "system",
|
|
80590
|
-
addonId: null,
|
|
80591
|
-
access: "create",
|
|
80592
|
-
role: "admin"
|
|
80593
|
-
},
|
|
80594
80612
|
"llmRuntime.appleAvailability": {
|
|
80595
80613
|
capName: "llm-runtime",
|
|
80596
80614
|
capScope: "system",
|
|
@@ -81095,13 +81113,6 @@ Object.freeze({
|
|
|
81095
81113
|
access: "create",
|
|
81096
81114
|
role: "admin"
|
|
81097
81115
|
},
|
|
81098
|
-
"meshNetwork.testConnection": {
|
|
81099
|
-
capName: "mesh-network",
|
|
81100
|
-
capScope: "system",
|
|
81101
|
-
addonId: null,
|
|
81102
|
-
access: "create",
|
|
81103
|
-
role: "admin"
|
|
81104
|
-
},
|
|
81105
81116
|
"meter.getStatus": {
|
|
81106
81117
|
capName: "meter",
|
|
81107
81118
|
capScope: "device",
|
|
@@ -81431,13 +81442,6 @@ Object.freeze({
|
|
|
81431
81442
|
access: "create",
|
|
81432
81443
|
role: "admin"
|
|
81433
81444
|
},
|
|
81434
|
-
"mqttBroker.testConnection": {
|
|
81435
|
-
capName: "mqtt-broker",
|
|
81436
|
-
capScope: "system",
|
|
81437
|
-
addonId: null,
|
|
81438
|
-
access: "create",
|
|
81439
|
-
role: "admin"
|
|
81440
|
-
},
|
|
81441
81445
|
"nativeObjectDetection.getOptions": {
|
|
81442
81446
|
capName: "native-object-detection",
|
|
81443
81447
|
capScope: "device",
|
|
@@ -81767,13 +81771,6 @@ Object.freeze({
|
|
|
81767
81771
|
access: "create",
|
|
81768
81772
|
role: "admin"
|
|
81769
81773
|
},
|
|
81770
|
-
"notificationOutput.deleteTarget": {
|
|
81771
|
-
capName: "notification-output",
|
|
81772
|
-
capScope: "system",
|
|
81773
|
-
addonId: null,
|
|
81774
|
-
access: "delete",
|
|
81775
|
-
role: "admin"
|
|
81776
|
-
},
|
|
81777
81774
|
"notificationOutput.discoverTargets": {
|
|
81778
81775
|
capName: "notification-output",
|
|
81779
81776
|
capScope: "system",
|
|
@@ -81781,13 +81778,6 @@ Object.freeze({
|
|
|
81781
81778
|
access: "view",
|
|
81782
81779
|
role: "admin"
|
|
81783
81780
|
},
|
|
81784
|
-
"notificationOutput.getTarget": {
|
|
81785
|
-
capName: "notification-output",
|
|
81786
|
-
capScope: "system",
|
|
81787
|
-
addonId: null,
|
|
81788
|
-
access: "view",
|
|
81789
|
-
role: "admin"
|
|
81790
|
-
},
|
|
81791
81781
|
"notificationOutput.listTargetKinds": {
|
|
81792
81782
|
capName: "notification-output",
|
|
81793
81783
|
capScope: "system",
|
|
@@ -81795,13 +81785,6 @@ Object.freeze({
|
|
|
81795
81785
|
access: "view",
|
|
81796
81786
|
role: "admin"
|
|
81797
81787
|
},
|
|
81798
|
-
"notificationOutput.listTargets": {
|
|
81799
|
-
capName: "notification-output",
|
|
81800
|
-
capScope: "system",
|
|
81801
|
-
addonId: null,
|
|
81802
|
-
access: "view",
|
|
81803
|
-
role: "admin"
|
|
81804
|
-
},
|
|
81805
81788
|
"notificationOutput.probeTargetConfig": {
|
|
81806
81789
|
capName: "notification-output",
|
|
81807
81790
|
capScope: "system",
|
|
@@ -81816,13 +81799,6 @@ Object.freeze({
|
|
|
81816
81799
|
access: "create",
|
|
81817
81800
|
role: "admin"
|
|
81818
81801
|
},
|
|
81819
|
-
"notificationOutput.setTargetEnabled": {
|
|
81820
|
-
capName: "notification-output",
|
|
81821
|
-
capScope: "system",
|
|
81822
|
-
addonId: null,
|
|
81823
|
-
access: "create",
|
|
81824
|
-
role: "admin"
|
|
81825
|
-
},
|
|
81826
81802
|
"notificationOutput.testTarget": {
|
|
81827
81803
|
capName: "notification-output",
|
|
81828
81804
|
capScope: "system",
|
|
@@ -81830,13 +81806,6 @@ Object.freeze({
|
|
|
81830
81806
|
access: "create",
|
|
81831
81807
|
role: "admin"
|
|
81832
81808
|
},
|
|
81833
|
-
"notificationOutput.upsertTarget": {
|
|
81834
|
-
capName: "notification-output",
|
|
81835
|
-
capScope: "system",
|
|
81836
|
-
addonId: null,
|
|
81837
|
-
access: "create",
|
|
81838
|
-
role: "admin"
|
|
81839
|
-
},
|
|
81840
81809
|
"notificationRules.activateAlarmProfile": {
|
|
81841
81810
|
capName: "notification-rules",
|
|
81842
81811
|
capScope: "system",
|
|
@@ -82096,41 +82065,6 @@ Object.freeze({
|
|
|
82096
82065
|
access: "view",
|
|
82097
82066
|
role: "admin"
|
|
82098
82067
|
},
|
|
82099
|
-
"oidcConnections.connectionFormSchema": {
|
|
82100
|
-
capName: "oidc-connections",
|
|
82101
|
-
capScope: "system",
|
|
82102
|
-
addonId: null,
|
|
82103
|
-
access: "view",
|
|
82104
|
-
role: "admin"
|
|
82105
|
-
},
|
|
82106
|
-
"oidcConnections.createConnection": {
|
|
82107
|
-
capName: "oidc-connections",
|
|
82108
|
-
capScope: "system",
|
|
82109
|
-
addonId: null,
|
|
82110
|
-
access: "create",
|
|
82111
|
-
role: "admin"
|
|
82112
|
-
},
|
|
82113
|
-
"oidcConnections.deleteConnection": {
|
|
82114
|
-
capName: "oidc-connections",
|
|
82115
|
-
capScope: "system",
|
|
82116
|
-
addonId: null,
|
|
82117
|
-
access: "delete",
|
|
82118
|
-
role: "admin"
|
|
82119
|
-
},
|
|
82120
|
-
"oidcConnections.listConnections": {
|
|
82121
|
-
capName: "oidc-connections",
|
|
82122
|
-
capScope: "system",
|
|
82123
|
-
addonId: null,
|
|
82124
|
-
access: "view",
|
|
82125
|
-
role: "admin"
|
|
82126
|
-
},
|
|
82127
|
-
"oidcConnections.updateConnection": {
|
|
82128
|
-
capName: "oidc-connections",
|
|
82129
|
-
capScope: "system",
|
|
82130
|
-
addonId: null,
|
|
82131
|
-
access: "create",
|
|
82132
|
-
role: "admin"
|
|
82133
|
-
},
|
|
82134
82068
|
"osd.getStatus": {
|
|
82135
82069
|
capName: "osd",
|
|
82136
82070
|
capScope: "device",
|
|
@@ -85162,13 +85096,6 @@ Object.freeze({
|
|
|
85162
85096
|
access: "delete",
|
|
85163
85097
|
role: "admin"
|
|
85164
85098
|
},
|
|
85165
|
-
"storage.deleteLocation": {
|
|
85166
|
-
capName: "storage",
|
|
85167
|
-
capScope: "system",
|
|
85168
|
-
addonId: null,
|
|
85169
|
-
access: "delete",
|
|
85170
|
-
role: "admin"
|
|
85171
|
-
},
|
|
85172
85099
|
"storage.endDownload": {
|
|
85173
85100
|
capName: "storage",
|
|
85174
85101
|
capScope: "system",
|
|
@@ -85225,13 +85152,6 @@ Object.freeze({
|
|
|
85225
85152
|
access: "view",
|
|
85226
85153
|
role: "admin"
|
|
85227
85154
|
},
|
|
85228
|
-
"storage.listProviders": {
|
|
85229
|
-
capName: "storage",
|
|
85230
|
-
capScope: "system",
|
|
85231
|
-
addonId: null,
|
|
85232
|
-
access: "view",
|
|
85233
|
-
role: "admin"
|
|
85234
|
-
},
|
|
85235
85155
|
"storage.read": {
|
|
85236
85156
|
capName: "storage",
|
|
85237
85157
|
capScope: "system",
|
|
@@ -85253,27 +85173,6 @@ Object.freeze({
|
|
|
85253
85173
|
access: "view",
|
|
85254
85174
|
role: "admin"
|
|
85255
85175
|
},
|
|
85256
|
-
"storage.testConfig": {
|
|
85257
|
-
capName: "storage",
|
|
85258
|
-
capScope: "system",
|
|
85259
|
-
addonId: null,
|
|
85260
|
-
access: "view",
|
|
85261
|
-
role: "admin"
|
|
85262
|
-
},
|
|
85263
|
-
"storage.testLocation": {
|
|
85264
|
-
capName: "storage",
|
|
85265
|
-
capScope: "system",
|
|
85266
|
-
addonId: null,
|
|
85267
|
-
access: "view",
|
|
85268
|
-
role: "admin"
|
|
85269
|
-
},
|
|
85270
|
-
"storage.upsertLocation": {
|
|
85271
|
-
capName: "storage",
|
|
85272
|
-
capScope: "system",
|
|
85273
|
-
addonId: null,
|
|
85274
|
-
access: "create",
|
|
85275
|
-
role: "admin"
|
|
85276
|
-
},
|
|
85277
85176
|
"storage.write": {
|
|
85278
85177
|
capName: "storage",
|
|
85279
85178
|
capScope: "system",
|
|
@@ -86051,27 +85950,6 @@ Object.freeze({
|
|
|
86051
85950
|
access: "create",
|
|
86052
85951
|
role: "admin"
|
|
86053
85952
|
},
|
|
86054
|
-
"terminalSession.createInstance": {
|
|
86055
|
-
capName: "terminal-session",
|
|
86056
|
-
capScope: "system",
|
|
86057
|
-
addonId: null,
|
|
86058
|
-
access: "create",
|
|
86059
|
-
role: "admin"
|
|
86060
|
-
},
|
|
86061
|
-
"terminalSession.deleteInstance": {
|
|
86062
|
-
capName: "terminal-session",
|
|
86063
|
-
capScope: "system",
|
|
86064
|
-
addonId: null,
|
|
86065
|
-
access: "delete",
|
|
86066
|
-
role: "admin"
|
|
86067
|
-
},
|
|
86068
|
-
"terminalSession.listInstances": {
|
|
86069
|
-
capName: "terminal-session",
|
|
86070
|
-
capScope: "system",
|
|
86071
|
-
addonId: null,
|
|
86072
|
-
access: "view",
|
|
86073
|
-
role: "admin"
|
|
86074
|
-
},
|
|
86075
85953
|
"terminalSession.listLegacyCameras": {
|
|
86076
85954
|
capName: "terminal-session",
|
|
86077
85955
|
capScope: "system",
|
|
@@ -86114,20 +85992,6 @@ Object.freeze({
|
|
|
86114
85992
|
access: "create",
|
|
86115
85993
|
role: "admin"
|
|
86116
85994
|
},
|
|
86117
|
-
"terminalSession.setInstanceEnabled": {
|
|
86118
|
-
capName: "terminal-session",
|
|
86119
|
-
capScope: "system",
|
|
86120
|
-
addonId: null,
|
|
86121
|
-
access: "create",
|
|
86122
|
-
role: "admin"
|
|
86123
|
-
},
|
|
86124
|
-
"terminalSession.updateInstance": {
|
|
86125
|
-
capName: "terminal-session",
|
|
86126
|
-
capScope: "system",
|
|
86127
|
-
addonId: null,
|
|
86128
|
-
access: "create",
|
|
86129
|
-
role: "admin"
|
|
86130
|
-
},
|
|
86131
85995
|
"terminalSession.writeInput": {
|
|
86132
85996
|
capName: "terminal-session",
|
|
86133
85997
|
capScope: "system",
|
|
@@ -86268,20 +86132,6 @@ Object.freeze({
|
|
|
86268
86132
|
access: "create",
|
|
86269
86133
|
role: "admin"
|
|
86270
86134
|
},
|
|
86271
|
-
"trainingProvider.createEntry": {
|
|
86272
|
-
capName: "training-provider",
|
|
86273
|
-
capScope: "system",
|
|
86274
|
-
addonId: null,
|
|
86275
|
-
access: "create",
|
|
86276
|
-
role: "admin"
|
|
86277
|
-
},
|
|
86278
|
-
"trainingProvider.deleteEntry": {
|
|
86279
|
-
capName: "training-provider",
|
|
86280
|
-
capScope: "system",
|
|
86281
|
-
addonId: null,
|
|
86282
|
-
access: "delete",
|
|
86283
|
-
role: "admin"
|
|
86284
|
-
},
|
|
86285
86135
|
"trainingProvider.describe": {
|
|
86286
86136
|
capName: "training-provider",
|
|
86287
86137
|
capScope: "system",
|
|
@@ -86352,13 +86202,6 @@ Object.freeze({
|
|
|
86352
86202
|
access: "create",
|
|
86353
86203
|
role: "admin"
|
|
86354
86204
|
},
|
|
86355
|
-
"trainingProvider.updateEntry": {
|
|
86356
|
-
capName: "training-provider",
|
|
86357
|
-
capScope: "system",
|
|
86358
|
-
addonId: null,
|
|
86359
|
-
access: "create",
|
|
86360
|
-
role: "admin"
|
|
86361
|
-
},
|
|
86362
86205
|
"trainingProvider.uploadDataset": {
|
|
86363
86206
|
capName: "training-provider",
|
|
86364
86207
|
capScope: "system",
|
|
@@ -86366,13 +86209,6 @@ Object.freeze({
|
|
|
86366
86209
|
access: "create",
|
|
86367
86210
|
role: "admin"
|
|
86368
86211
|
},
|
|
86369
|
-
"trainingProvider.validateCredentials": {
|
|
86370
|
-
capName: "training-provider",
|
|
86371
|
-
capScope: "system",
|
|
86372
|
-
addonId: null,
|
|
86373
|
-
access: "create",
|
|
86374
|
-
role: "admin"
|
|
86375
|
-
},
|
|
86376
86212
|
"turnProvider.getTurnServers": {
|
|
86377
86213
|
capName: "turn-provider",
|
|
86378
86214
|
capScope: "system",
|
|
@@ -146087,17 +145923,6 @@ function scrub(message, secret) {
|
|
|
146087
145923
|
function mintRelayId() {
|
|
146088
145924
|
return `relay-${(0, node_crypto.randomUUID)().slice(0, 8)}`;
|
|
146089
145925
|
}
|
|
146090
|
-
/** The keys the settings page edits: the relay's connection fields. */
|
|
146091
|
-
var RELAY_FORM_KEYS = new Set([
|
|
146092
|
-
"host",
|
|
146093
|
-
"port",
|
|
146094
|
-
"secure",
|
|
146095
|
-
"username",
|
|
146096
|
-
"password",
|
|
146097
|
-
"fromAddress",
|
|
146098
|
-
"fromName"
|
|
146099
|
-
]);
|
|
146100
|
-
var DEFAULT_RELAY_NAME = "Email (SMTP)";
|
|
146101
145926
|
var RELAY_LIST_MAX = 200;
|
|
146102
145927
|
function hasText(value) {
|
|
146103
145928
|
return typeof value === "string" && value.length > 0;
|
|
@@ -146350,44 +146175,10 @@ var SmtpNodemailerAddon = class extends BaseAddon {
|
|
|
146350
146175
|
const rows = (await records.kind(smtpRelaysKind).query({ limit: RELAY_LIST_MAX })).records;
|
|
146351
146176
|
return (rows.find((row) => row.data.host.length > 0) ?? rows[0])?.id;
|
|
146352
146177
|
}
|
|
146353
|
-
/** The relay form.
|
|
146178
|
+
/** The relay form. */
|
|
146354
146179
|
async integrationFormSchema(_instance) {
|
|
146355
146180
|
return this.relayForm();
|
|
146356
146181
|
}
|
|
146357
|
-
async getGlobalSettings() {
|
|
146358
|
-
const schema = this.relayForm();
|
|
146359
|
-
if (this.instances === null) return hydrateSchema(schema, {}, { inlineSecretsAsViews: true });
|
|
146360
|
-
const id = await this.firstRelayId();
|
|
146361
|
-
return hydrateSchema(schema, (id === void 0 ? null : await this.instances.readResolved(id))?.body ?? {}, { inlineSecretsAsViews: true });
|
|
146362
|
-
}
|
|
146363
|
-
async updateGlobalSettingsAsOperator(patch) {
|
|
146364
|
-
await this.writeDefaultRelay(patch);
|
|
146365
|
-
}
|
|
146366
|
-
async updateGlobalSettings(patch) {
|
|
146367
|
-
await this.writeDefaultRelay(patch);
|
|
146368
|
-
}
|
|
146369
|
-
/** Write the form's values onto the default relay, creating it on the first save. */
|
|
146370
|
-
async writeDefaultRelay(patch) {
|
|
146371
|
-
if (this.instances === null) throw new Error("smtp-nodemailer is not initialised");
|
|
146372
|
-
const values = Object.fromEntries(Object.entries(patch).filter(([key]) => RELAY_FORM_KEYS.has(key)));
|
|
146373
|
-
const existing = await this.firstRelayId();
|
|
146374
|
-
if (existing !== void 0) {
|
|
146375
|
-
await this.instances.updateInstance({
|
|
146376
|
-
id: existing,
|
|
146377
|
-
values
|
|
146378
|
-
});
|
|
146379
|
-
return;
|
|
146380
|
-
}
|
|
146381
|
-
const id = mintRelayId();
|
|
146382
|
-
await this.instances.createInstance({
|
|
146383
|
-
id,
|
|
146384
|
-
name: DEFAULT_RELAY_NAME
|
|
146385
|
-
});
|
|
146386
|
-
await this.instances.updateInstance({
|
|
146387
|
-
id,
|
|
146388
|
-
values
|
|
146389
|
-
});
|
|
146390
|
-
}
|
|
146391
146182
|
/** Probe a relay (EHLO + AUTH) with the form's values, falling back to the stored ones. */
|
|
146392
146183
|
async probe(id, values) {
|
|
146393
146184
|
const stored = id !== null && this.instances !== null ? await this.instances.getResolved(id) : null;
|