@camstack/addon-import-alexa 0.2.193 → 0.2.195

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/dist/addon.js CHANGED
@@ -48,7 +48,7 @@ node_fs = __toESM(node_fs, 1);
48
48
  let node_child_process = require("node:child_process");
49
49
  require("node:zlib");
50
50
  let node_url = require("node:url");
51
- //#region ../types/dist/event-category-BmPm98n0.mjs
51
+ //#region ../types/dist/event-category-78tcBemo.mjs
52
52
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
53
53
  EventCategory["SystemBoot"] = "system.boot";
54
54
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -169,6 +169,23 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
169
169
  */
170
170
  EventCategory["AddonLoadRecovered"] = "addon.load-recovered";
171
171
  /**
172
+ * An addon is RUNNING although its installed package is below what the
173
+ * running server requires (`camstack.requires.addons` of the server, or the
174
+ * addon's own `requires.server`). Raised once per streak by the unsupported-
175
+ * addon tracker, on the hub and on agents; AlertCenter turns it into one
176
+ * operator alert per node and package. The addon is never held back for it.
177
+ *
178
+ * Payload: `{ packageName, addonId, nodeId, installedVersion, requiredMinimum, serverVersion, unmet }`.
179
+ */
180
+ EventCategory["AddonUnsupportedVersion"] = "addon.unsupported-version";
181
+ /**
182
+ * The package of an {@link AddonUnsupportedVersion} alert reached a supported
183
+ * version, or is no longer installed on the node. AlertCenter dismisses the matching alert.
184
+ *
185
+ * Payload: `{ packageName, addonId, nodeId }`.
186
+ */
187
+ EventCategory["AddonUnsupportedResolved"] = "addon.unsupported-resolved";
188
+ /**
172
189
  * Monitor scheduled the next retry for a failed addon. Transient —
173
190
  * surfaced to the UI for live-updating the "next retry in Ns"
174
191
  * countdown on the Addons page row, NOT persisted as an alert.
@@ -279,7 +296,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
279
296
  EventCategory["ProcessRestarted"] = "process.restarted";
280
297
  /**
281
298
  * The SET of storage locations changed — one was created, edited, enabled,
282
- * disabled or deleted through `storage.upsertLocation` / `deleteLocation`.
299
+ * disabled or deleted through the location instances or the forced-delete action.
283
300
  *
284
301
  * Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
285
302
  * it must also converge on its own periodic path, because a dropped event
@@ -6044,7 +6061,7 @@ function number(params) {
6044
6061
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
6045
6062
  }
6046
6063
  //#endregion
6047
- //#region ../types/dist/sleep-CdKka2ri.mjs
6064
+ //#region ../types/dist/sleep-AEnTN3b4.mjs
6048
6065
  /**
6049
6066
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6050
6067
  * window to float samples (D455).
@@ -6753,10 +6770,33 @@ function integrationRowsApiOver(api) {
6753
6770
  };
6754
6771
  }
6755
6772
  var PREFIX = "records/";
6773
+ /**
6774
+ * Cross-realm brand. An addon bundles its own copy of this module (D15) while
6775
+ * `ctx.records` is the HOST's door, so an error thrown by the host is an
6776
+ * instance of the host's classes, not of the addon's. The brand plus the code
6777
+ * make `instanceof` answer by identity of the failure, not of the copy.
6778
+ */
6779
+ var RECORDS_ERROR_BRAND = Symbol.for("camstack.records-error");
6780
+ function carriesBrand(value) {
6781
+ return typeof value === "object" && value !== null && Reflect.get(value, RECORDS_ERROR_BRAND) === true;
6782
+ }
6756
6783
  /** The base of every records failure. `detail` is the human part of the message. */
6757
6784
  var RecordsError = class extends Error {
6785
+ /** The code a subclass stands for; the base stands for every code. */
6786
+ static code = void 0;
6787
+ /**
6788
+ * `instanceof` across copies of this module: any branded error is a
6789
+ * `RecordsError`, and is an instance of a subclass only when its code is the
6790
+ * subclass's code. Same-copy answers are unchanged.
6791
+ */
6792
+ static [Symbol.hasInstance](value) {
6793
+ if (Function.prototype[Symbol.hasInstance].call(this, value)) return true;
6794
+ if (!carriesBrand(value)) return false;
6795
+ return this.code === void 0 || value.code === this.code;
6796
+ }
6758
6797
  code;
6759
6798
  detail;
6799
+ [RECORDS_ERROR_BRAND] = true;
6760
6800
  constructor(code, detail) {
6761
6801
  super(`${PREFIX}${code}: ${detail}`);
6762
6802
  this.name = "RecordsError";
@@ -6765,29 +6805,33 @@ var RecordsError = class extends Error {
6765
6805
  }
6766
6806
  };
6767
6807
  /** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
6768
- var RevConflictError = class extends RecordsError {
6808
+ var RevConflictError = class RevConflictError extends RecordsError {
6809
+ static code = "rev-conflict";
6769
6810
  constructor(detail) {
6770
- super("rev-conflict", detail);
6811
+ super(RevConflictError.code, detail);
6771
6812
  this.name = "RevConflictError";
6772
6813
  }
6773
6814
  };
6774
6815
  /** The keychain could not store or serve a value: explicit, never an empty string. */
6775
- var RecordSecretUnavailableError = class extends RecordsError {
6816
+ var RecordSecretUnavailableError = class RecordSecretUnavailableError extends RecordsError {
6817
+ static code = "secret-unavailable";
6776
6818
  constructor(detail) {
6777
- super("secret-unavailable", detail);
6819
+ super(RecordSecretUnavailableError.code, detail);
6778
6820
  this.name = "RecordSecretUnavailableError";
6779
6821
  }
6780
6822
  };
6781
- var InvalidRecordError = class extends RecordsError {
6823
+ var InvalidRecordError = class InvalidRecordError extends RecordsError {
6824
+ static code = "invalid-record";
6782
6825
  constructor(detail) {
6783
- super("invalid-record", detail);
6826
+ super(InvalidRecordError.code, detail);
6784
6827
  this.name = "InvalidRecordError";
6785
6828
  }
6786
6829
  };
6787
6830
  /** A kind declaration the engine refuses (non-additive change, bad shape, wrong file). */
6788
- var DeclarationRefusedError = class extends RecordsError {
6831
+ var DeclarationRefusedError = class DeclarationRefusedError extends RecordsError {
6832
+ static code = "declaration-refused";
6789
6833
  constructor(detail) {
6790
- super("declaration-refused", detail);
6834
+ super(DeclarationRefusedError.code, detail);
6791
6835
  this.name = "DeclarationRefusedError";
6792
6836
  }
6793
6837
  };
@@ -7281,17 +7325,35 @@ var RecordIntegrationInstances = class {
7281
7325
  ...kind !== void 0 ? { kind } : {}
7282
7326
  });
7283
7327
  if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
7328
+ const initial = this.hooks.initialBody(name, kind);
7329
+ const patch = this.patchOf(values ?? {});
7330
+ if (Object.keys(patch).length > 0) Object.assign(patch, await this.hooks.validateWrite?.({
7331
+ kind,
7332
+ body: {
7333
+ ...initial,
7334
+ ...patch
7335
+ }
7336
+ }));
7284
7337
  await this.handle.put({
7285
7338
  id: minted,
7286
- data: this.hooks.initialBody(name, kind)
7339
+ data: initial
7287
7340
  }, { ifRev: 0 });
7288
- const patch = this.patchOf(values ?? {});
7289
7341
  if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
7290
7342
  await this.hooks.onChanged?.(minted, "created");
7291
7343
  return { id: minted };
7292
7344
  };
7293
7345
  updateInstance = async ({ id, name, values }) => {
7294
7346
  const patch = this.patchOf(values);
7347
+ if (this.hooks.validateWrite !== void 0 && Object.keys(patch).length > 0) {
7348
+ const stored = await this.handle.get(id);
7349
+ if (stored !== null) Object.assign(patch, await this.hooks.validateWrite({
7350
+ kind: void 0,
7351
+ body: {
7352
+ ...stored.data,
7353
+ ...patch
7354
+ }
7355
+ }));
7356
+ }
7295
7357
  if (name !== void 0) patch[this.nameField] = name;
7296
7358
  await this.handle.patch(id, patch);
7297
7359
  await this.hooks.onChanged?.(id, "updated");
@@ -10637,7 +10699,6 @@ var CAP_NAMES = Object.freeze([
10637
10699
  "notifier",
10638
10700
  "numeric-sensor",
10639
10701
  "oauth-integration",
10640
- "oidc-connections",
10641
10702
  "osd",
10642
10703
  "osd-manager",
10643
10704
  "pet-feeder",
@@ -12529,8 +12590,7 @@ Object.freeze({
12529
12590
  "device-provider": { "reloadDevice": 18e4 },
12530
12591
  "llm": {
12531
12592
  "generate": 6e5,
12532
- "generateVision": 6e5,
12533
- "testProfile": 6e5
12593
+ "generateVision": 6e5
12534
12594
  },
12535
12595
  "llm-runtime": {
12536
12596
  "complete": 6e5,
@@ -14141,17 +14201,18 @@ function composeInstanceKinds(parts, base) {
14141
14201
  * message to what the kind supports — callers never special-case a service.
14142
14202
  *
14143
14203
  * DESIGN DECISIONS (locked):
14144
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
14145
- * `setTargetEnabled`), each provider persisting via the `settings-store`
14146
- * cap. Rationale: the admin UI needs one uniform surface across the
14147
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
14148
- * alternative would fork the UI per addon and cannot host the
14149
- * discovery→adopt flow.
14150
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
14151
- * the generated cap-mount auto-`concatCollection`-fans them across every
14152
- * registered provider (notifiers addon + HA addon) so one catalog is
14153
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
14154
- * `addonId` the generated collection router extracts from the call input.
14204
+ * - A target is an INSTANCE of this function cap (epic #440, step 6): the
14205
+ * providers serve their targets as `integration-instances` records, one
14206
+ * record kind per target kind, and the admin lists, adds, edits, switches
14207
+ * and deletes them through that cap. This cap keeps what an instance cannot
14208
+ * say: the kind catalogue (`listTargetKinds`: capabilities, delivery
14209
+ * parameters, brand icon), dispatch (`send`, `testTarget`) and the form
14210
+ * helpers (`discoverTargets`, `probeTargetConfig`).
14211
+ * - `listTargetKinds` / `discoverTargets` return arrays → the generated
14212
+ * cap-mount auto-`concatCollection`-fans them across every registered
14213
+ * provider so one catalog is routable. `send` / `testTarget` /
14214
+ * `probeTargetConfig` route to ONE provider by the `addonId` the generated
14215
+ * collection router extracts from the call input.
14155
14216
  * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
14156
14217
  * transformer) + UDS MsgPack both round-trip typed arrays — already used by
14157
14218
  * `storage` / `storage-provider` / `recording` caps over the same path. No
@@ -14396,7 +14457,7 @@ var TargetKindCapsSchema = object({
14396
14457
  * the union is large and not meant for runtime validation here; the exported
14397
14458
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14398
14459
  */
14399
- var ConfigSchemaPassthrough$4 = unknown();
14460
+ var ConfigSchemaPassthrough$3 = unknown();
14400
14461
  var TargetKindSchema = object({
14401
14462
  kind: string(),
14402
14463
  label: string(),
@@ -14433,7 +14494,7 @@ var TargetKindSchema = object({
14433
14494
  * its raster path, which is the safe default for an unknown image.
14434
14495
  */
14435
14496
  iconMediaType: string().optional(),
14436
- configSchema: ConfigSchemaPassthrough$4,
14497
+ configSchema: ConfigSchemaPassthrough$3,
14437
14498
  /**
14438
14499
  * The config keys that decide WHERE this kind's secrets are sent (server,
14439
14500
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -14455,9 +14516,11 @@ var TargetKindSchema = object({
14455
14516
  caps: TargetKindCapsSchema
14456
14517
  });
14457
14518
  /**
14458
- * A persisted target. `config` holds secrets; providers REDACT secret fields
14459
- * (return a presence marker only) when serving `listTargets` — never
14460
- * round-trip a stored secret to the UI.
14519
+ * A target as a provider's own code sees it (the notification-center bridge, the
14520
+ * send path): the flat record body split into the name, the switch and the
14521
+ * kind's fields. `config` holds secrets; a reader that serves it to a client
14522
+ * REDACTS secret fields (a presence marker only) — never round-trip a stored
14523
+ * secret to the UI.
14461
14524
  */
14462
14525
  var TargetSchema = object({
14463
14526
  id: string(),
@@ -14506,21 +14569,13 @@ var notificationOutputCapability = {
14506
14569
  mode: "collection",
14507
14570
  methods: {
14508
14571
  listTargetKinds: method(object({}), array(TargetKindSchema)),
14509
- listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
14510
- /**
14511
- * One target WITH secrets — admin edit / duplicate hydration only.
14512
- * `listTargets` stays redacted; the editor loads this so password fields
14513
- * are filled (masked in the input) and a save can round-trip the token
14514
- * when a destination changes (Duplicate with a new chatId, etc.).
14515
- */
14516
- getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
14517
14572
  discoverTargets: method(object({
14518
14573
  kind: string(),
14519
14574
  config: record(string(), unknown()).optional(),
14520
14575
  /**
14521
14576
  * When editing / duplicating, the blank password field never round-trips
14522
14577
  * the stored token. Pass the source target id so the provider can restore
14523
- * secrets server-side before dialling (same merge as upsert).
14578
+ * secrets server-side before dialling.
14524
14579
  */
14525
14580
  targetId: string().optional()
14526
14581
  }), array(DiscoveredTargetSchema), { auth: "admin" }),
@@ -14546,30 +14601,6 @@ var notificationOutputCapability = {
14546
14601
  }), TestResultSchema, {
14547
14602
  kind: "mutation",
14548
14603
  auth: "admin"
14549
- }),
14550
- upsertTarget: method(object({
14551
- target: TargetSchema,
14552
- /**
14553
- * Duplicate flow: create a NEW target id while copying secrets from an
14554
- * existing row. The UI never holds the stored token; blank / redacted
14555
- * password fields are restored from this source (D634 still applies if
14556
- * destinations diverge without a re-typed secret).
14557
- */
14558
- cloneSecretsFrom: string().optional()
14559
- }), TargetSchema, {
14560
- kind: "mutation",
14561
- auth: "admin"
14562
- }),
14563
- deleteTarget: method(object({ targetId: string() }), _void(), {
14564
- kind: "mutation",
14565
- auth: "admin"
14566
- }),
14567
- setTargetEnabled: method(object({
14568
- targetId: string(),
14569
- enabled: boolean()
14570
- }), _void(), {
14571
- kind: "mutation",
14572
- auth: "admin"
14573
14604
  })
14574
14605
  }
14575
14606
  };
@@ -14585,8 +14616,9 @@ var notificationOutputCapability = {
14585
14616
  *
14586
14617
  * Three providers (notifiers, Home Assistant, Alexa) serve the same shape, so the pieces are shared:
14587
14618
  * - {@link defineNotificationTargetKind}: the record kind of one `TargetKind` descriptor;
14588
- * - {@link NotificationTargetStore}: the `Target`-shaped facade the existing provider code keeps
14589
- * using, over one handle per kind (the old `notification-output` CRUD keeps working on it);
14619
+ * - {@link NotificationTargetStore}: the `Target`-shaped facade the provider's own code keeps
14620
+ * using (send, the notification-center bridge), over one handle per kind;
14621
+ * - {@link listNotificationTargets}: the targets as an addon on `ctx.api` reads them;
14590
14622
  * - {@link buildNotificationTargetInstances}: the `integration-instances` provider, one kind each;
14591
14623
  * - {@link moveTargetRows}: the D987 procedure that flattens the old `config` blobs into the bodies.
14592
14624
  */
@@ -14843,6 +14875,10 @@ function notificationTargetParts(records, spec) {
14843
14875
  initialBody: (name) => ({ name }),
14844
14876
  mintId: () => spec.mintId(),
14845
14877
  configured: () => true,
14878
+ validateWrite: ({ body }) => spec.validate?.({
14879
+ kind: descriptor.kind,
14880
+ config: body
14881
+ }),
14846
14882
  test: async ({ id, values }) => {
14847
14883
  if (spec.probe === void 0 || !canTest) return {
14848
14884
  success: false,
@@ -23238,7 +23274,6 @@ var SYSTEM_CAP_VOCABULARY = {
23238
23274
  "notification-output": system("notification delivery — reached through notifier devices and rules"),
23239
23275
  "notification-rules": system("notification rules — authored on their own page"),
23240
23276
  "oauth-integration": system(IDENTITY),
23241
- "oidc-connections": system(IDENTITY),
23242
23277
  "osd-manager": system(PIPELINE),
23243
23278
  "pipeline-executor": system(PIPELINE),
23244
23279
  "pipeline-orchestrator": system(PIPELINE),
@@ -57030,7 +57065,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
57030
57065
  * or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
57031
57066
  * clears it.
57032
57067
  */
57033
- var ConfigSchemaPassthrough$3 = custom();
57068
+ var ConfigSchemaPassthrough$2 = custom();
57034
57069
  var IntegrationInstanceIdSchema = string().min(1);
57035
57070
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
57036
57071
  /**
@@ -57076,7 +57111,16 @@ var IntegrationInstanceKindSchema = object({
57076
57111
  /** Relative hub path to start an interactive account link of an instance of this kind. */
57077
57112
  connectPath: string().min(1).optional(),
57078
57113
  /** Relative hub path to disconnect that account. */
57079
- disconnectPath: string().min(1).optional()
57114
+ disconnectPath: string().min(1).optional(),
57115
+ /**
57116
+ * Parts of the edit dialog the flat form cannot draw (a node picker, a model source), named by a
57117
+ * GENERIC widget id the client maps to a component; a client that knows none of them shows none.
57118
+ * `connection` widgets write connection values, `model` widgets own the model choice.
57119
+ */
57120
+ widgets: array(object({
57121
+ id: string().min(1),
57122
+ placement: _enum(["connection", "model"])
57123
+ })).optional()
57080
57124
  });
57081
57125
  /**
57082
57126
  * How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
@@ -57181,7 +57225,7 @@ var integrationInstancesCapability = {
57181
57225
  id: IntegrationInstanceIdSchema.nullable(),
57182
57226
  /** The kind of the create form (`id: null`) of a provider with several. */
57183
57227
  kind: string().min(1).optional()
57184
- }), ConfigSchemaPassthrough$3, { auth: "admin" }),
57228
+ }), ConfigSchemaPassthrough$2, { auth: "admin" }),
57185
57229
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
57186
57230
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
57187
57231
  /**
@@ -58283,12 +58327,12 @@ var LlmModelInfoSchema = object({
58283
58327
  });
58284
58328
  /**
58285
58329
  * The provider-specific part of a profile, validated by the provider that owns
58286
- * the kind (`upsertProfile` refuses what it does not accept). A field its
58330
+ * the kind (saving an instance refuses what it does not accept). A field its
58287
58331
  * `connectionSchema` declares `type: 'password'` is a secret: it is held in the
58288
58332
  * keychain, never in the row, and reads back as the redaction marker.
58289
58333
  */
58290
58334
  var LlmProviderConfigSchema = record(string(), unknown());
58291
- var LlmProfileSchema = object({
58335
+ object({
58292
58336
  id: string(),
58293
58337
  name: string(),
58294
58338
  /** The provider-declared kind (`LlmProfileKindDescriptor.kind`). */
@@ -58382,8 +58426,8 @@ var LlmProviderWidgetSchema = object({
58382
58426
  /** ConfigUISchema tree passed through untyped on the wire (the
58383
58427
  * notification-output `ConfigSchemaPassthrough` precedent at
58384
58428
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
58385
- var ConfigSchemaPassthrough$2 = unknown();
58386
- var LlmProfileKindDescriptorSchema = object({
58429
+ var ConfigSchemaPassthrough$1 = unknown();
58430
+ object({
58387
58431
  kind: LlmProfileKindSchema,
58388
58432
  label: string(),
58389
58433
  /** A generic icon name (`cloud`, `plug`, `cpu`) — never a glyph or a logo. */
@@ -58391,9 +58435,9 @@ var LlmProfileKindDescriptorSchema = object({
58391
58435
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
58392
58436
  addonId: string(),
58393
58437
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
58394
- connectionSchema: ConfigSchemaPassthrough$2,
58438
+ connectionSchema: ConfigSchemaPassthrough$1,
58395
58439
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
58396
- advancedSchema: ConfigSchemaPassthrough$2.optional(),
58440
+ advancedSchema: ConfigSchemaPassthrough$1.optional(),
58397
58441
  /** The kind's defaults; a model from `listModels` may override any of them. */
58398
58442
  features: LlmModelFeaturesSchema,
58399
58443
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -58522,20 +58566,7 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
58522
58566
  }), method(object({
58523
58567
  addonId: string().optional(),
58524
58568
  requestId: string()
58525
- }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
58526
- kind: "mutation",
58527
- auth: "admin"
58528
- }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
58529
- kind: "mutation",
58530
- auth: "admin"
58531
- }), method(ProfileRefInputSchema, _void(), {
58532
- kind: "mutation",
58533
- auth: "admin"
58534
- }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
58535
- kind: "mutation",
58536
- auth: "admin",
58537
- timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
58538
- }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
58569
+ }), _void(), { kind: "mutation" }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
58539
58570
  kind: "mutation",
58540
58571
  auth: "admin"
58541
58572
  }), method(object({}), array(LlmDefaultSchema)), method(object({
@@ -59598,20 +59629,7 @@ method(_void(), MeshStatusSchema), method(object({
59598
59629
  /** Optional hostname override the host should advertise once joined. */
59599
59630
  hostname: string().optional() }), object({
59600
59631
  /** Authentication URL the operator should open in a browser. */
59601
- 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() })), method(object({
59602
- /** Optional auth key — when provided, probes the key validity
59603
- * against the provider's API. Omit when already joined to
59604
- * just ping the daemon. */
59605
- authKey: string().optional() }), object({
59606
- ok: boolean(),
59607
- /** Provider-side identifier resolved by the probe (tailnet
59608
- * name for Tailscale, network id for ZeroTier, etc.). */
59609
- tenant: string().optional(),
59610
- /** Daemon binary version, when reachable. */
59611
- daemonVersion: string().optional(),
59612
- /** Human-readable error when `ok: false`. */
59613
- error: string().optional()
59614
- }), { kind: "mutation" });
59632
+ 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() }));
59615
59633
  /**
59616
59634
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
59617
59635
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -69818,61 +69836,6 @@ var OauthIntegrationDescriptorSchema = object({
69818
69836
  });
69819
69837
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
69820
69838
  /**
69821
- * `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
69822
- *
69823
- * One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
69824
- * ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
69825
- * Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
69826
- * immutable after create.
69827
- */
69828
- var ConfigSchemaPassthrough$1 = unknown();
69829
- var OidcConnectionSchema = object({
69830
- id: string().min(1),
69831
- displayName: string(),
69832
- icon: string(),
69833
- issuerUrl: string(),
69834
- clientId: string(),
69835
- /** Client id + secret present — never the secret value. */
69836
- configured: boolean(),
69837
- /**
69838
- * Values for the connection form schema. `clientSecret` is a keychain
69839
- * {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
69840
- */
69841
- formValues: record(string(), unknown()).optional()
69842
- });
69843
- /**
69844
- * Patch one connection. Omitted or empty `clientSecret` keeps the stored
69845
- * secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
69846
- */
69847
- var OidcConnectionUpdateInputSchema = object({
69848
- id: string().min(1),
69849
- displayName: string().min(1),
69850
- icon: string().optional(),
69851
- issuerUrl: string().optional(),
69852
- clientId: string().optional(),
69853
- clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
69854
- redirectUri: string().optional(),
69855
- scopes: string().optional(),
69856
- usernameClaim: _enum([
69857
- "preferred_username",
69858
- "email",
69859
- "sub"
69860
- ]).optional()
69861
- });
69862
- method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
69863
- id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
69864
- displayName: string().min(1)
69865
- }), object({ id: string() }), {
69866
- kind: "mutation",
69867
- auth: "admin"
69868
- }), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
69869
- kind: "mutation",
69870
- auth: "admin"
69871
- }), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
69872
- kind: "mutation",
69873
- auth: "admin"
69874
- });
69875
- /**
69876
69839
  * Generic on-screen-display (video overlay) cap. Each camera exposes
69877
69840
  * a firmware-defined list of overlays (channel name, timestamp,
69878
69841
  * watermark, logo, …); the cap models them as a homogeneous array of
@@ -76954,21 +76917,6 @@ method(object({
76954
76917
  */
76955
76918
  ttlSec: union([number$1().int().positive(), literal("never")]).optional()
76956
76919
  }), object({ token: string() }), { auth: "admin" }), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" });
76957
- var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
76958
- providerId: string().min(1),
76959
- displayName: string().min(1),
76960
- configSchema: unknown(),
76961
- nodeLocal: boolean(),
76962
- shouldSaveDiskSpace: literal(true),
76963
- minFreePercent: number$1().min(0).max(100)
76964
- }), object({
76965
- providerId: string().min(1),
76966
- displayName: string().min(1),
76967
- configSchema: unknown(),
76968
- nodeLocal: boolean(),
76969
- shouldSaveDiskSpace: literal(false),
76970
- minFreePercent: literal(null)
76971
- })]);
76972
76920
  method(object({
76973
76921
  location: StorageLocationRefSchema,
76974
76922
  relativePath: string()
@@ -77030,42 +76978,7 @@ method(object({
77030
76978
  }), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
77031
76979
  kind: "mutation",
77032
76980
  auth: "admin"
77033
- }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
77034
- createdAt: true,
77035
- updatedAt: true
77036
- }), StorageLocationSchema, {
77037
- kind: "mutation",
77038
- auth: "admin"
77039
- }), method(object({
77040
- id: string(),
77041
- force: boolean().optional()
77042
- }), _void(), {
77043
- kind: "mutation",
77044
- auth: "admin"
77045
- }), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
77046
- ok: boolean(),
77047
- error: string().optional()
77048
- }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
77049
- providerId: string(),
77050
- /**
77051
- * The location this config is an UNSAVED edit of, when there is one.
77052
- *
77053
- * `listLocations` replaces every declared secret with the redaction
77054
- * sentinel, so the edit modal's form state holds the sentinel for any
77055
- * credential the operator did not retype — and posting that here
77056
- * without a way to resolve it makes the provider try to authenticate
77057
- * as `__camstack_redacted__` and report the operator's own working
77058
- * password as wrong. Given this id, the orchestrator restores each
77059
- * sentinel from the stored config (same rule as `upsertLocation`)
77060
- * before dispatching. Omitted by the "Add location" wizard, where
77061
- * every value was typed just now and nothing is stored yet.
77062
- */
77063
- locationId: string().optional(),
77064
- config: record(string(), unknown())
77065
- }), object({
77066
- ok: boolean(),
77067
- error: string().optional()
77068
- }), { auth: "admin" });
76981
+ }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(_void(), array(StorageDrainProgressSchema).readonly());
77069
76982
  /** One table of the settings database. */
77070
76983
  var StorageTableSchema = object({
77071
76984
  /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
@@ -86951,13 +86864,6 @@ Object.freeze({
86951
86864
  access: "delete",
86952
86865
  role: "admin"
86953
86866
  },
86954
- "llm.deleteProfile": {
86955
- capName: "llm",
86956
- capScope: "system",
86957
- addonId: null,
86958
- access: "delete",
86959
- role: "admin"
86960
- },
86961
86867
  "llm.generate": {
86962
86868
  capName: "llm",
86963
86869
  capScope: "system",
@@ -87021,20 +86927,6 @@ Object.freeze({
87021
86927
  access: "view",
87022
86928
  role: "admin"
87023
86929
  },
87024
- "llm.listProfileKinds": {
87025
- capName: "llm",
87026
- capScope: "system",
87027
- addonId: null,
87028
- access: "view",
87029
- role: "admin"
87030
- },
87031
- "llm.listProfiles": {
87032
- capName: "llm",
87033
- capScope: "system",
87034
- addonId: null,
87035
- access: "view",
87036
- role: "admin"
87037
- },
87038
86930
  "llm.listRuntimeNodes": {
87039
86931
  capName: "llm",
87040
86932
  capScope: "system",
@@ -87042,13 +86934,6 @@ Object.freeze({
87042
86934
  access: "view",
87043
86935
  role: "admin"
87044
86936
  },
87045
- "llm.migrateRawSecrets": {
87046
- capName: "llm",
87047
- capScope: "system",
87048
- addonId: null,
87049
- access: "create",
87050
- role: "admin"
87051
- },
87052
86937
  "llm.resolveModelRef": {
87053
86938
  capName: "llm",
87054
86939
  capScope: "system",
@@ -87077,20 +86962,6 @@ Object.freeze({
87077
86962
  access: "create",
87078
86963
  role: "admin"
87079
86964
  },
87080
- "llm.testProfile": {
87081
- capName: "llm",
87082
- capScope: "system",
87083
- addonId: null,
87084
- access: "create",
87085
- role: "admin"
87086
- },
87087
- "llm.upsertProfile": {
87088
- capName: "llm",
87089
- capScope: "system",
87090
- addonId: null,
87091
- access: "create",
87092
- role: "admin"
87093
- },
87094
86965
  "llmRuntime.appleAvailability": {
87095
86966
  capName: "llm-runtime",
87096
86967
  capScope: "system",
@@ -87595,13 +87466,6 @@ Object.freeze({
87595
87466
  access: "create",
87596
87467
  role: "admin"
87597
87468
  },
87598
- "meshNetwork.testConnection": {
87599
- capName: "mesh-network",
87600
- capScope: "system",
87601
- addonId: null,
87602
- access: "create",
87603
- role: "admin"
87604
- },
87605
87469
  "meter.getStatus": {
87606
87470
  capName: "meter",
87607
87471
  capScope: "device",
@@ -88260,13 +88124,6 @@ Object.freeze({
88260
88124
  access: "create",
88261
88125
  role: "admin"
88262
88126
  },
88263
- "notificationOutput.deleteTarget": {
88264
- capName: "notification-output",
88265
- capScope: "system",
88266
- addonId: null,
88267
- access: "delete",
88268
- role: "admin"
88269
- },
88270
88127
  "notificationOutput.discoverTargets": {
88271
88128
  capName: "notification-output",
88272
88129
  capScope: "system",
@@ -88274,13 +88131,6 @@ Object.freeze({
88274
88131
  access: "view",
88275
88132
  role: "admin"
88276
88133
  },
88277
- "notificationOutput.getTarget": {
88278
- capName: "notification-output",
88279
- capScope: "system",
88280
- addonId: null,
88281
- access: "view",
88282
- role: "admin"
88283
- },
88284
88134
  "notificationOutput.listTargetKinds": {
88285
88135
  capName: "notification-output",
88286
88136
  capScope: "system",
@@ -88288,13 +88138,6 @@ Object.freeze({
88288
88138
  access: "view",
88289
88139
  role: "admin"
88290
88140
  },
88291
- "notificationOutput.listTargets": {
88292
- capName: "notification-output",
88293
- capScope: "system",
88294
- addonId: null,
88295
- access: "view",
88296
- role: "admin"
88297
- },
88298
88141
  "notificationOutput.probeTargetConfig": {
88299
88142
  capName: "notification-output",
88300
88143
  capScope: "system",
@@ -88309,13 +88152,6 @@ Object.freeze({
88309
88152
  access: "create",
88310
88153
  role: "admin"
88311
88154
  },
88312
- "notificationOutput.setTargetEnabled": {
88313
- capName: "notification-output",
88314
- capScope: "system",
88315
- addonId: null,
88316
- access: "create",
88317
- role: "admin"
88318
- },
88319
88155
  "notificationOutput.testTarget": {
88320
88156
  capName: "notification-output",
88321
88157
  capScope: "system",
@@ -88323,13 +88159,6 @@ Object.freeze({
88323
88159
  access: "create",
88324
88160
  role: "admin"
88325
88161
  },
88326
- "notificationOutput.upsertTarget": {
88327
- capName: "notification-output",
88328
- capScope: "system",
88329
- addonId: null,
88330
- access: "create",
88331
- role: "admin"
88332
- },
88333
88162
  "notificationRules.activateAlarmProfile": {
88334
88163
  capName: "notification-rules",
88335
88164
  capScope: "system",
@@ -88589,41 +88418,6 @@ Object.freeze({
88589
88418
  access: "view",
88590
88419
  role: "admin"
88591
88420
  },
88592
- "oidcConnections.connectionFormSchema": {
88593
- capName: "oidc-connections",
88594
- capScope: "system",
88595
- addonId: null,
88596
- access: "view",
88597
- role: "admin"
88598
- },
88599
- "oidcConnections.createConnection": {
88600
- capName: "oidc-connections",
88601
- capScope: "system",
88602
- addonId: null,
88603
- access: "create",
88604
- role: "admin"
88605
- },
88606
- "oidcConnections.deleteConnection": {
88607
- capName: "oidc-connections",
88608
- capScope: "system",
88609
- addonId: null,
88610
- access: "delete",
88611
- role: "admin"
88612
- },
88613
- "oidcConnections.listConnections": {
88614
- capName: "oidc-connections",
88615
- capScope: "system",
88616
- addonId: null,
88617
- access: "view",
88618
- role: "admin"
88619
- },
88620
- "oidcConnections.updateConnection": {
88621
- capName: "oidc-connections",
88622
- capScope: "system",
88623
- addonId: null,
88624
- access: "create",
88625
- role: "admin"
88626
- },
88627
88421
  "osd.getStatus": {
88628
88422
  capName: "osd",
88629
88423
  capScope: "device",
@@ -91655,13 +91449,6 @@ Object.freeze({
91655
91449
  access: "delete",
91656
91450
  role: "admin"
91657
91451
  },
91658
- "storage.deleteLocation": {
91659
- capName: "storage",
91660
- capScope: "system",
91661
- addonId: null,
91662
- access: "delete",
91663
- role: "admin"
91664
- },
91665
91452
  "storage.endDownload": {
91666
91453
  capName: "storage",
91667
91454
  capScope: "system",
@@ -91718,13 +91505,6 @@ Object.freeze({
91718
91505
  access: "view",
91719
91506
  role: "admin"
91720
91507
  },
91721
- "storage.listProviders": {
91722
- capName: "storage",
91723
- capScope: "system",
91724
- addonId: null,
91725
- access: "view",
91726
- role: "admin"
91727
- },
91728
91508
  "storage.read": {
91729
91509
  capName: "storage",
91730
91510
  capScope: "system",
@@ -91746,27 +91526,6 @@ Object.freeze({
91746
91526
  access: "view",
91747
91527
  role: "admin"
91748
91528
  },
91749
- "storage.testConfig": {
91750
- capName: "storage",
91751
- capScope: "system",
91752
- addonId: null,
91753
- access: "view",
91754
- role: "admin"
91755
- },
91756
- "storage.testLocation": {
91757
- capName: "storage",
91758
- capScope: "system",
91759
- addonId: null,
91760
- access: "view",
91761
- role: "admin"
91762
- },
91763
- "storage.upsertLocation": {
91764
- capName: "storage",
91765
- capScope: "system",
91766
- addonId: null,
91767
- access: "create",
91768
- role: "admin"
91769
- },
91770
91529
  "storage.write": {
91771
91530
  capName: "storage",
91772
91531
  capScope: "system",
@@ -145494,6 +145253,33 @@ function quietLogger$1() {
145494
145253
  };
145495
145254
  return logger;
145496
145255
  }
145256
+ /**
145257
+ * The account a target speaks through: the one it names; else the only one there is; else the one
145258
+ * whose Echoes include the target's Echo.
145259
+ */
145260
+ function pickBroker(brokers, config) {
145261
+ const named = readString(config, "brokerId");
145262
+ if (named.length > 0) return brokers.find((broker) => broker.id === named);
145263
+ if (brokers.length === 1) return brokers[0];
145264
+ const echoId = readString(config, "echoId");
145265
+ const owners = brokers.filter((broker) => broker.devices.some((device) => device.id === echoId));
145266
+ return owners.length === 1 ? owners[0] : void 0;
145267
+ }
145268
+ /**
145269
+ * The save-time rule of an Echo target: an Echo and an account must be picked, and the account is
145270
+ * pinned the moment it is known, so a second account added later cannot silently change where an
145271
+ * existing target speaks. Throws the reason; returns the keys to keep with the write.
145272
+ */
145273
+ function validateEchoTarget(brokers, config) {
145274
+ if (readString(config, "echoId").length === 0) throw new Error("Pick the Echo that should speak");
145275
+ const owner = pickBroker(brokers, config);
145276
+ if (owner === void 0) {
145277
+ const named = readString(config, "brokerId");
145278
+ if (named.length > 0) throw new Error(`The Amazon account ${named} is not available`);
145279
+ throw new Error(brokers.length === 0 ? "Add an Amazon account first: an Echo speaks through one" : "Pick the Amazon account this Echo belongs to");
145280
+ }
145281
+ return readString(config, "brokerId").length === 0 ? { brokerId: owner.id } : {};
145282
+ }
145497
145283
  function readString(config, key) {
145498
145284
  const value = config[key];
145499
145285
  return typeof value === "string" ? value : "";
@@ -145523,7 +145309,7 @@ function echoOptionLabel(device) {
145523
145309
  /**
145524
145310
  * The two pickers are not `required`: a required select shows its first option
145525
145311
  * while the form still holds no value, so a save would drop the choice. Left
145526
- * optional they open on "Select", and `upsertTarget` refuses an empty pick.
145312
+ * optional they open on "Select", and `validateEchoTarget` refuses an empty pick on save.
145527
145313
  *
145528
145314
  * The account is a broker pick (existing, or created in place); the Echo list
145529
145315
  * is fetched from that account, so a new Echo shows up without a deploy.
@@ -145615,18 +145401,8 @@ function createAlexaNotificationProvider(ports) {
145615
145401
  function record(row) {
145616
145402
  deliveries = [...deliveries, row];
145617
145403
  }
145618
- /**
145619
- * The account a target speaks through: the one it names; else the only one
145620
- * there is; else the one whose Echoes include the target's Echo.
145621
- */
145622
145404
  function brokerFor(config) {
145623
- const all = ports.brokers();
145624
- const named = readString(config, "brokerId");
145625
- if (named.length > 0) return all.find((broker) => broker.id === named);
145626
- if (all.length === 1) return all[0];
145627
- const echoId = readString(config, "echoId");
145628
- const owners = all.filter((broker) => broker.devices.some((device) => device.id === echoId));
145629
- return owners.length === 1 ? owners[0] : void 0;
145405
+ return pickBroker(ports.brokers(), config);
145630
145406
  }
145631
145407
  async function deliver(target, notification) {
145632
145408
  const speech = speechFromNotification(notification);
@@ -145828,14 +145604,6 @@ function createAlexaNotificationProvider(ports) {
145828
145604
  async listTargetKinds() {
145829
145605
  return [alexaTargetKind()];
145830
145606
  },
145831
- async listTargets() {
145832
- return [...await ports.loadTargets()];
145833
- },
145834
- async getTarget(input) {
145835
- const found = (await ports.loadTargets()).find((target) => target.id === input.targetId);
145836
- if (found === void 0) throw new Error(`target not found: ${input.targetId}`);
145837
- return found;
145838
- },
145839
145607
  async discoverTargets(input) {
145840
145608
  for (const broker of ports.brokers()) if (!broker.ready) ports.logger.warn(`alexa echo discovery: no Echo listed for '${broker.name}', the Amazon account is not signed in for this broker`, { tags: { brokerId: broker.id } });
145841
145609
  const named = readString(input.config ?? {}, "brokerId");
@@ -145896,33 +145664,6 @@ function createAlexaNotificationProvider(ports) {
145896
145664
  format: "text",
145897
145665
  priority: 3
145898
145666
  });
145899
- },
145900
- async upsertTarget(input) {
145901
- const owner = brokerFor(input.target.config);
145902
- if (readString(input.target.config, "echoId").length === 0) throw new Error("Pick the Echo that should speak");
145903
- if (owner === void 0) throw new Error("Pick the Amazon account this Echo belongs to");
145904
- const pinned = readString(input.target.config, "brokerId").length === 0 ? {
145905
- ...input.target.config,
145906
- brokerId: owner.id
145907
- } : input.target.config;
145908
- const target = {
145909
- ...input.target,
145910
- config: pinned,
145911
- addonId: ALEXA_SPEAK_ADDON_ID,
145912
- kind: ALEXA_SPEAK_KIND
145913
- };
145914
- await ports.putTarget(target);
145915
- return target;
145916
- },
145917
- async deleteTarget(input) {
145918
- await ports.removeTarget(input.targetId);
145919
- },
145920
- async setTargetEnabled(input) {
145921
- const found = (await ports.loadTargets()).find((row) => row.id === input.targetId);
145922
- if (found !== void 0) await ports.putTarget({
145923
- ...found,
145924
- enabled: input.enabled
145925
- });
145926
145667
  }
145927
145668
  };
145928
145669
  }
@@ -146013,6 +145754,14 @@ function echoTargetProbe(speak) {
146013
145754
  });
146014
145755
  };
146015
145756
  }
145757
+ /**
145758
+ * The Echo instance's save-time rule over the accounts now (D1013): the check the removed
145759
+ * `upsertTarget` made, on the instance door. Speech not started yet has no account to name, so the
145760
+ * write is refused with the reason, never accepted blind.
145761
+ */
145762
+ function echoTargetValidator(brokers) {
145763
+ return ({ config }) => validateEchoTarget(brokers() ?? [], config);
145764
+ }
146016
145765
  process.platform, process.env["SystemRoot"];
146017
145766
  /**
146018
145767
  * Files only their owner may read: the one place a secret-bearing file is
@@ -146853,8 +146602,6 @@ var AlexaSpeakController = class {
146853
146602
  now: this.now,
146854
146603
  logger: host.logger,
146855
146604
  loadTargets: () => host.readTargets(),
146856
- putTarget: (target) => host.putTarget(target),
146857
- removeTarget: (targetId) => host.removeTarget(targetId),
146858
146605
  speak: (command) => this.utter(command.brokerId, command.mode, command.targets, command.text)
146859
146606
  });
146860
146607
  }
@@ -146884,6 +146631,7 @@ var AlexaSpeakController = class {
146884
146631
  echoFor(brokerId) {
146885
146632
  return this.echoes.get(brokerId) ?? null;
146886
146633
  }
146634
+ /** The accounts right now, as the provider and the save-time rule see them. */
146887
146635
  brokerViews() {
146888
146636
  return this.host.listBrokers().map((ref) => {
146889
146637
  const echo = this.echoes.get(ref.id);
@@ -147431,8 +147179,6 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
147431
147179
  const speak = new AlexaSpeakController({
147432
147180
  logger: this.ctx.logger.child("speak"),
147433
147181
  readTargets: () => echoStore.list(),
147434
- putTarget: (target) => echoStore.upsert(target),
147435
- removeTarget: (targetId) => echoStore.delete(targetId),
147436
147182
  listBrokers: () => this.activeBrokers.map((entry) => ({
147437
147183
  id: entry.id,
147438
147184
  name: entry.name,
@@ -147534,6 +147280,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
147534
147280
  kinds: alexaNotifyRecordKinds,
147535
147281
  mintId: () => (0, node_crypto.randomUUID)(),
147536
147282
  probe: echoTargetProbe(() => this.speak),
147283
+ validate: echoTargetValidator(() => this.speak?.brokerViews() ?? null),
147537
147284
  canProbe: () => true
147538
147285
  })
147539
147286
  ], {