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