@camstack/addon-import-alexa 0.2.194 → 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.mjs CHANGED
@@ -46,7 +46,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
46
46
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
47
47
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
48
  //#endregion
49
- //#region ../types/dist/event-category-BmPm98n0.mjs
49
+ //#region ../types/dist/event-category-78tcBemo.mjs
50
50
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
51
51
  EventCategory["SystemBoot"] = "system.boot";
52
52
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -167,6 +167,23 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
167
167
  */
168
168
  EventCategory["AddonLoadRecovered"] = "addon.load-recovered";
169
169
  /**
170
+ * An addon is RUNNING although its installed package is below what the
171
+ * running server requires (`camstack.requires.addons` of the server, or the
172
+ * addon's own `requires.server`). Raised once per streak by the unsupported-
173
+ * addon tracker, on the hub and on agents; AlertCenter turns it into one
174
+ * operator alert per node and package. The addon is never held back for it.
175
+ *
176
+ * Payload: `{ packageName, addonId, nodeId, installedVersion, requiredMinimum, serverVersion, unmet }`.
177
+ */
178
+ EventCategory["AddonUnsupportedVersion"] = "addon.unsupported-version";
179
+ /**
180
+ * The package of an {@link AddonUnsupportedVersion} alert reached a supported
181
+ * version, or is no longer installed on the node. AlertCenter dismisses the matching alert.
182
+ *
183
+ * Payload: `{ packageName, addonId, nodeId }`.
184
+ */
185
+ EventCategory["AddonUnsupportedResolved"] = "addon.unsupported-resolved";
186
+ /**
170
187
  * Monitor scheduled the next retry for a failed addon. Transient —
171
188
  * surfaced to the UI for live-updating the "next retry in Ns"
172
189
  * countdown on the Addons page row, NOT persisted as an alert.
@@ -277,7 +294,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
277
294
  EventCategory["ProcessRestarted"] = "process.restarted";
278
295
  /**
279
296
  * The SET of storage locations changed — one was created, edited, enabled,
280
- * disabled or deleted through `storage.upsertLocation` / `deleteLocation`.
297
+ * disabled or deleted through the location instances or the forced-delete action.
281
298
  *
282
299
  * Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
283
300
  * it must also converge on its own periodic path, because a dropped event
@@ -6042,7 +6059,7 @@ function number(params) {
6042
6059
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
6043
6060
  }
6044
6061
  //#endregion
6045
- //#region ../types/dist/sleep-D7ZKm4nb.mjs
6062
+ //#region ../types/dist/sleep-AEnTN3b4.mjs
6046
6063
  /**
6047
6064
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6048
6065
  * window to float samples (D455).
@@ -7306,17 +7323,35 @@ var RecordIntegrationInstances = class {
7306
7323
  ...kind !== void 0 ? { kind } : {}
7307
7324
  });
7308
7325
  if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
7326
+ const initial = this.hooks.initialBody(name, kind);
7327
+ const patch = this.patchOf(values ?? {});
7328
+ if (Object.keys(patch).length > 0) Object.assign(patch, await this.hooks.validateWrite?.({
7329
+ kind,
7330
+ body: {
7331
+ ...initial,
7332
+ ...patch
7333
+ }
7334
+ }));
7309
7335
  await this.handle.put({
7310
7336
  id: minted,
7311
- data: this.hooks.initialBody(name, kind)
7337
+ data: initial
7312
7338
  }, { ifRev: 0 });
7313
- const patch = this.patchOf(values ?? {});
7314
7339
  if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
7315
7340
  await this.hooks.onChanged?.(minted, "created");
7316
7341
  return { id: minted };
7317
7342
  };
7318
7343
  updateInstance = async ({ id, name, values }) => {
7319
7344
  const patch = this.patchOf(values);
7345
+ if (this.hooks.validateWrite !== void 0 && Object.keys(patch).length > 0) {
7346
+ const stored = await this.handle.get(id);
7347
+ if (stored !== null) Object.assign(patch, await this.hooks.validateWrite({
7348
+ kind: void 0,
7349
+ body: {
7350
+ ...stored.data,
7351
+ ...patch
7352
+ }
7353
+ }));
7354
+ }
7320
7355
  if (name !== void 0) patch[this.nameField] = name;
7321
7356
  await this.handle.patch(id, patch);
7322
7357
  await this.hooks.onChanged?.(id, "updated");
@@ -10662,7 +10697,6 @@ var CAP_NAMES = Object.freeze([
10662
10697
  "notifier",
10663
10698
  "numeric-sensor",
10664
10699
  "oauth-integration",
10665
- "oidc-connections",
10666
10700
  "osd",
10667
10701
  "osd-manager",
10668
10702
  "pet-feeder",
@@ -12554,8 +12588,7 @@ Object.freeze({
12554
12588
  "device-provider": { "reloadDevice": 18e4 },
12555
12589
  "llm": {
12556
12590
  "generate": 6e5,
12557
- "generateVision": 6e5,
12558
- "testProfile": 6e5
12591
+ "generateVision": 6e5
12559
12592
  },
12560
12593
  "llm-runtime": {
12561
12594
  "complete": 6e5,
@@ -14166,17 +14199,18 @@ function composeInstanceKinds(parts, base) {
14166
14199
  * message to what the kind supports — callers never special-case a service.
14167
14200
  *
14168
14201
  * DESIGN DECISIONS (locked):
14169
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
14170
- * `setTargetEnabled`), each provider persisting via the `settings-store`
14171
- * cap. Rationale: the admin UI needs one uniform surface across the
14172
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
14173
- * alternative would fork the UI per addon and cannot host the
14174
- * discovery→adopt flow.
14175
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
14176
- * the generated cap-mount auto-`concatCollection`-fans them across every
14177
- * registered provider (notifiers addon + HA addon) so one catalog is
14178
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
14179
- * `addonId` the generated collection router extracts from the call input.
14202
+ * - A target is an INSTANCE of this function cap (epic #440, step 6): the
14203
+ * providers serve their targets as `integration-instances` records, one
14204
+ * record kind per target kind, and the admin lists, adds, edits, switches
14205
+ * and deletes them through that cap. This cap keeps what an instance cannot
14206
+ * say: the kind catalogue (`listTargetKinds`: capabilities, delivery
14207
+ * parameters, brand icon), dispatch (`send`, `testTarget`) and the form
14208
+ * helpers (`discoverTargets`, `probeTargetConfig`).
14209
+ * - `listTargetKinds` / `discoverTargets` return arrays → the generated
14210
+ * cap-mount auto-`concatCollection`-fans them across every registered
14211
+ * provider so one catalog is routable. `send` / `testTarget` /
14212
+ * `probeTargetConfig` route to ONE provider by the `addonId` the generated
14213
+ * collection router extracts from the call input.
14180
14214
  * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
14181
14215
  * transformer) + UDS MsgPack both round-trip typed arrays — already used by
14182
14216
  * `storage` / `storage-provider` / `recording` caps over the same path. No
@@ -14421,7 +14455,7 @@ var TargetKindCapsSchema = object({
14421
14455
  * the union is large and not meant for runtime validation here; the exported
14422
14456
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14423
14457
  */
14424
- var ConfigSchemaPassthrough$4 = unknown();
14458
+ var ConfigSchemaPassthrough$3 = unknown();
14425
14459
  var TargetKindSchema = object({
14426
14460
  kind: string(),
14427
14461
  label: string(),
@@ -14458,7 +14492,7 @@ var TargetKindSchema = object({
14458
14492
  * its raster path, which is the safe default for an unknown image.
14459
14493
  */
14460
14494
  iconMediaType: string().optional(),
14461
- configSchema: ConfigSchemaPassthrough$4,
14495
+ configSchema: ConfigSchemaPassthrough$3,
14462
14496
  /**
14463
14497
  * The config keys that decide WHERE this kind's secrets are sent (server,
14464
14498
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -14480,9 +14514,11 @@ var TargetKindSchema = object({
14480
14514
  caps: TargetKindCapsSchema
14481
14515
  });
14482
14516
  /**
14483
- * A persisted target. `config` holds secrets; providers REDACT secret fields
14484
- * (return a presence marker only) when serving `listTargets` — never
14485
- * round-trip a stored secret to the UI.
14517
+ * A target as a provider's own code sees it (the notification-center bridge, the
14518
+ * send path): the flat record body split into the name, the switch and the
14519
+ * kind's fields. `config` holds secrets; a reader that serves it to a client
14520
+ * REDACTS secret fields (a presence marker only) — never round-trip a stored
14521
+ * secret to the UI.
14486
14522
  */
14487
14523
  var TargetSchema = object({
14488
14524
  id: string(),
@@ -14531,21 +14567,13 @@ var notificationOutputCapability = {
14531
14567
  mode: "collection",
14532
14568
  methods: {
14533
14569
  listTargetKinds: method(object({}), array(TargetKindSchema)),
14534
- listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
14535
- /**
14536
- * One target WITH secrets — admin edit / duplicate hydration only.
14537
- * `listTargets` stays redacted; the editor loads this so password fields
14538
- * are filled (masked in the input) and a save can round-trip the token
14539
- * when a destination changes (Duplicate with a new chatId, etc.).
14540
- */
14541
- getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
14542
14570
  discoverTargets: method(object({
14543
14571
  kind: string(),
14544
14572
  config: record(string(), unknown()).optional(),
14545
14573
  /**
14546
14574
  * When editing / duplicating, the blank password field never round-trips
14547
14575
  * the stored token. Pass the source target id so the provider can restore
14548
- * secrets server-side before dialling (same merge as upsert).
14576
+ * secrets server-side before dialling.
14549
14577
  */
14550
14578
  targetId: string().optional()
14551
14579
  }), array(DiscoveredTargetSchema), { auth: "admin" }),
@@ -14571,30 +14599,6 @@ var notificationOutputCapability = {
14571
14599
  }), TestResultSchema, {
14572
14600
  kind: "mutation",
14573
14601
  auth: "admin"
14574
- }),
14575
- upsertTarget: method(object({
14576
- target: TargetSchema,
14577
- /**
14578
- * Duplicate flow: create a NEW target id while copying secrets from an
14579
- * existing row. The UI never holds the stored token; blank / redacted
14580
- * password fields are restored from this source (D634 still applies if
14581
- * destinations diverge without a re-typed secret).
14582
- */
14583
- cloneSecretsFrom: string().optional()
14584
- }), TargetSchema, {
14585
- kind: "mutation",
14586
- auth: "admin"
14587
- }),
14588
- deleteTarget: method(object({ targetId: string() }), _void(), {
14589
- kind: "mutation",
14590
- auth: "admin"
14591
- }),
14592
- setTargetEnabled: method(object({
14593
- targetId: string(),
14594
- enabled: boolean()
14595
- }), _void(), {
14596
- kind: "mutation",
14597
- auth: "admin"
14598
14602
  })
14599
14603
  }
14600
14604
  };
@@ -14610,8 +14614,9 @@ var notificationOutputCapability = {
14610
14614
  *
14611
14615
  * Three providers (notifiers, Home Assistant, Alexa) serve the same shape, so the pieces are shared:
14612
14616
  * - {@link defineNotificationTargetKind}: the record kind of one `TargetKind` descriptor;
14613
- * - {@link NotificationTargetStore}: the `Target`-shaped facade the existing provider code keeps
14614
- * using, over one handle per kind (the old `notification-output` CRUD keeps working on it);
14617
+ * - {@link NotificationTargetStore}: the `Target`-shaped facade the provider's own code keeps
14618
+ * using (send, the notification-center bridge), over one handle per kind;
14619
+ * - {@link listNotificationTargets}: the targets as an addon on `ctx.api` reads them;
14615
14620
  * - {@link buildNotificationTargetInstances}: the `integration-instances` provider, one kind each;
14616
14621
  * - {@link moveTargetRows}: the D987 procedure that flattens the old `config` blobs into the bodies.
14617
14622
  */
@@ -14868,6 +14873,10 @@ function notificationTargetParts(records, spec) {
14868
14873
  initialBody: (name) => ({ name }),
14869
14874
  mintId: () => spec.mintId(),
14870
14875
  configured: () => true,
14876
+ validateWrite: ({ body }) => spec.validate?.({
14877
+ kind: descriptor.kind,
14878
+ config: body
14879
+ }),
14871
14880
  test: async ({ id, values }) => {
14872
14881
  if (spec.probe === void 0 || !canTest) return {
14873
14882
  success: false,
@@ -23263,7 +23272,6 @@ var SYSTEM_CAP_VOCABULARY = {
23263
23272
  "notification-output": system("notification delivery — reached through notifier devices and rules"),
23264
23273
  "notification-rules": system("notification rules — authored on their own page"),
23265
23274
  "oauth-integration": system(IDENTITY),
23266
- "oidc-connections": system(IDENTITY),
23267
23275
  "osd-manager": system(PIPELINE),
23268
23276
  "pipeline-executor": system(PIPELINE),
23269
23277
  "pipeline-orchestrator": system(PIPELINE),
@@ -57055,7 +57063,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
57055
57063
  * or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
57056
57064
  * clears it.
57057
57065
  */
57058
- var ConfigSchemaPassthrough$3 = custom();
57066
+ var ConfigSchemaPassthrough$2 = custom();
57059
57067
  var IntegrationInstanceIdSchema = string().min(1);
57060
57068
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
57061
57069
  /**
@@ -57101,7 +57109,16 @@ var IntegrationInstanceKindSchema = object({
57101
57109
  /** Relative hub path to start an interactive account link of an instance of this kind. */
57102
57110
  connectPath: string().min(1).optional(),
57103
57111
  /** Relative hub path to disconnect that account. */
57104
- disconnectPath: string().min(1).optional()
57112
+ disconnectPath: string().min(1).optional(),
57113
+ /**
57114
+ * Parts of the edit dialog the flat form cannot draw (a node picker, a model source), named by a
57115
+ * GENERIC widget id the client maps to a component; a client that knows none of them shows none.
57116
+ * `connection` widgets write connection values, `model` widgets own the model choice.
57117
+ */
57118
+ widgets: array(object({
57119
+ id: string().min(1),
57120
+ placement: _enum(["connection", "model"])
57121
+ })).optional()
57105
57122
  });
57106
57123
  /**
57107
57124
  * How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
@@ -57206,7 +57223,7 @@ var integrationInstancesCapability = {
57206
57223
  id: IntegrationInstanceIdSchema.nullable(),
57207
57224
  /** The kind of the create form (`id: null`) of a provider with several. */
57208
57225
  kind: string().min(1).optional()
57209
- }), ConfigSchemaPassthrough$3, { auth: "admin" }),
57226
+ }), ConfigSchemaPassthrough$2, { auth: "admin" }),
57210
57227
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
57211
57228
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
57212
57229
  /**
@@ -58308,12 +58325,12 @@ var LlmModelInfoSchema = object({
58308
58325
  });
58309
58326
  /**
58310
58327
  * The provider-specific part of a profile, validated by the provider that owns
58311
- * the kind (`upsertProfile` refuses what it does not accept). A field its
58328
+ * the kind (saving an instance refuses what it does not accept). A field its
58312
58329
  * `connectionSchema` declares `type: 'password'` is a secret: it is held in the
58313
58330
  * keychain, never in the row, and reads back as the redaction marker.
58314
58331
  */
58315
58332
  var LlmProviderConfigSchema = record(string(), unknown());
58316
- var LlmProfileSchema = object({
58333
+ object({
58317
58334
  id: string(),
58318
58335
  name: string(),
58319
58336
  /** The provider-declared kind (`LlmProfileKindDescriptor.kind`). */
@@ -58407,8 +58424,8 @@ var LlmProviderWidgetSchema = object({
58407
58424
  /** ConfigUISchema tree passed through untyped on the wire (the
58408
58425
  * notification-output `ConfigSchemaPassthrough` precedent at
58409
58426
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
58410
- var ConfigSchemaPassthrough$2 = unknown();
58411
- var LlmProfileKindDescriptorSchema = object({
58427
+ var ConfigSchemaPassthrough$1 = unknown();
58428
+ object({
58412
58429
  kind: LlmProfileKindSchema,
58413
58430
  label: string(),
58414
58431
  /** A generic icon name (`cloud`, `plug`, `cpu`) — never a glyph or a logo. */
@@ -58416,9 +58433,9 @@ var LlmProfileKindDescriptorSchema = object({
58416
58433
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
58417
58434
  addonId: string(),
58418
58435
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
58419
- connectionSchema: ConfigSchemaPassthrough$2,
58436
+ connectionSchema: ConfigSchemaPassthrough$1,
58420
58437
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
58421
- advancedSchema: ConfigSchemaPassthrough$2.optional(),
58438
+ advancedSchema: ConfigSchemaPassthrough$1.optional(),
58422
58439
  /** The kind's defaults; a model from `listModels` may override any of them. */
58423
58440
  features: LlmModelFeaturesSchema,
58424
58441
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -58547,20 +58564,7 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
58547
58564
  }), method(object({
58548
58565
  addonId: string().optional(),
58549
58566
  requestId: string()
58550
- }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
58551
- kind: "mutation",
58552
- auth: "admin"
58553
- }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
58554
- kind: "mutation",
58555
- auth: "admin"
58556
- }), method(ProfileRefInputSchema, _void(), {
58557
- kind: "mutation",
58558
- auth: "admin"
58559
- }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
58560
- kind: "mutation",
58561
- auth: "admin",
58562
- timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
58563
- }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
58567
+ }), _void(), { kind: "mutation" }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
58564
58568
  kind: "mutation",
58565
58569
  auth: "admin"
58566
58570
  }), method(object({}), array(LlmDefaultSchema)), method(object({
@@ -59623,20 +59627,7 @@ method(_void(), MeshStatusSchema), method(object({
59623
59627
  /** Optional hostname override the host should advertise once joined. */
59624
59628
  hostname: string().optional() }), object({
59625
59629
  /** Authentication URL the operator should open in a browser. */
59626
- 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({
59627
- /** Optional auth key — when provided, probes the key validity
59628
- * against the provider's API. Omit when already joined to
59629
- * just ping the daemon. */
59630
- authKey: string().optional() }), object({
59631
- ok: boolean(),
59632
- /** Provider-side identifier resolved by the probe (tailnet
59633
- * name for Tailscale, network id for ZeroTier, etc.). */
59634
- tenant: string().optional(),
59635
- /** Daemon binary version, when reachable. */
59636
- daemonVersion: string().optional(),
59637
- /** Human-readable error when `ok: false`. */
59638
- error: string().optional()
59639
- }), { kind: "mutation" });
59630
+ 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() }));
59640
59631
  /**
59641
59632
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
59642
59633
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -69843,61 +69834,6 @@ var OauthIntegrationDescriptorSchema = object({
69843
69834
  });
69844
69835
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
69845
69836
  /**
69846
- * `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
69847
- *
69848
- * One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
69849
- * ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
69850
- * Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
69851
- * immutable after create.
69852
- */
69853
- var ConfigSchemaPassthrough$1 = unknown();
69854
- var OidcConnectionSchema = object({
69855
- id: string().min(1),
69856
- displayName: string(),
69857
- icon: string(),
69858
- issuerUrl: string(),
69859
- clientId: string(),
69860
- /** Client id + secret present — never the secret value. */
69861
- configured: boolean(),
69862
- /**
69863
- * Values for the connection form schema. `clientSecret` is a keychain
69864
- * {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
69865
- */
69866
- formValues: record(string(), unknown()).optional()
69867
- });
69868
- /**
69869
- * Patch one connection. Omitted or empty `clientSecret` keeps the stored
69870
- * secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
69871
- */
69872
- var OidcConnectionUpdateInputSchema = object({
69873
- id: string().min(1),
69874
- displayName: string().min(1),
69875
- icon: string().optional(),
69876
- issuerUrl: string().optional(),
69877
- clientId: string().optional(),
69878
- clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
69879
- redirectUri: string().optional(),
69880
- scopes: string().optional(),
69881
- usernameClaim: _enum([
69882
- "preferred_username",
69883
- "email",
69884
- "sub"
69885
- ]).optional()
69886
- });
69887
- method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
69888
- id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
69889
- displayName: string().min(1)
69890
- }), object({ id: string() }), {
69891
- kind: "mutation",
69892
- auth: "admin"
69893
- }), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
69894
- kind: "mutation",
69895
- auth: "admin"
69896
- }), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
69897
- kind: "mutation",
69898
- auth: "admin"
69899
- });
69900
- /**
69901
69837
  * Generic on-screen-display (video overlay) cap. Each camera exposes
69902
69838
  * a firmware-defined list of overlays (channel name, timestamp,
69903
69839
  * watermark, logo, …); the cap models them as a homogeneous array of
@@ -76979,21 +76915,6 @@ method(object({
76979
76915
  */
76980
76916
  ttlSec: union([number$1().int().positive(), literal("never")]).optional()
76981
76917
  }), object({ token: string() }), { auth: "admin" }), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" });
76982
- var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
76983
- providerId: string().min(1),
76984
- displayName: string().min(1),
76985
- configSchema: unknown(),
76986
- nodeLocal: boolean(),
76987
- shouldSaveDiskSpace: literal(true),
76988
- minFreePercent: number$1().min(0).max(100)
76989
- }), object({
76990
- providerId: string().min(1),
76991
- displayName: string().min(1),
76992
- configSchema: unknown(),
76993
- nodeLocal: boolean(),
76994
- shouldSaveDiskSpace: literal(false),
76995
- minFreePercent: literal(null)
76996
- })]);
76997
76918
  method(object({
76998
76919
  location: StorageLocationRefSchema,
76999
76920
  relativePath: string()
@@ -77055,42 +76976,7 @@ method(object({
77055
76976
  }), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
77056
76977
  kind: "mutation",
77057
76978
  auth: "admin"
77058
- }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
77059
- createdAt: true,
77060
- updatedAt: true
77061
- }), StorageLocationSchema, {
77062
- kind: "mutation",
77063
- auth: "admin"
77064
- }), method(object({
77065
- id: string(),
77066
- force: boolean().optional()
77067
- }), _void(), {
77068
- kind: "mutation",
77069
- auth: "admin"
77070
- }), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
77071
- ok: boolean(),
77072
- error: string().optional()
77073
- }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
77074
- providerId: string(),
77075
- /**
77076
- * The location this config is an UNSAVED edit of, when there is one.
77077
- *
77078
- * `listLocations` replaces every declared secret with the redaction
77079
- * sentinel, so the edit modal's form state holds the sentinel for any
77080
- * credential the operator did not retype — and posting that here
77081
- * without a way to resolve it makes the provider try to authenticate
77082
- * as `__camstack_redacted__` and report the operator's own working
77083
- * password as wrong. Given this id, the orchestrator restores each
77084
- * sentinel from the stored config (same rule as `upsertLocation`)
77085
- * before dispatching. Omitted by the "Add location" wizard, where
77086
- * every value was typed just now and nothing is stored yet.
77087
- */
77088
- locationId: string().optional(),
77089
- config: record(string(), unknown())
77090
- }), object({
77091
- ok: boolean(),
77092
- error: string().optional()
77093
- }), { auth: "admin" });
76979
+ }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(_void(), array(StorageDrainProgressSchema).readonly());
77094
76980
  /** One table of the settings database. */
77095
76981
  var StorageTableSchema = object({
77096
76982
  /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
@@ -86976,13 +86862,6 @@ Object.freeze({
86976
86862
  access: "delete",
86977
86863
  role: "admin"
86978
86864
  },
86979
- "llm.deleteProfile": {
86980
- capName: "llm",
86981
- capScope: "system",
86982
- addonId: null,
86983
- access: "delete",
86984
- role: "admin"
86985
- },
86986
86865
  "llm.generate": {
86987
86866
  capName: "llm",
86988
86867
  capScope: "system",
@@ -87046,20 +86925,6 @@ Object.freeze({
87046
86925
  access: "view",
87047
86926
  role: "admin"
87048
86927
  },
87049
- "llm.listProfileKinds": {
87050
- capName: "llm",
87051
- capScope: "system",
87052
- addonId: null,
87053
- access: "view",
87054
- role: "admin"
87055
- },
87056
- "llm.listProfiles": {
87057
- capName: "llm",
87058
- capScope: "system",
87059
- addonId: null,
87060
- access: "view",
87061
- role: "admin"
87062
- },
87063
86928
  "llm.listRuntimeNodes": {
87064
86929
  capName: "llm",
87065
86930
  capScope: "system",
@@ -87067,13 +86932,6 @@ Object.freeze({
87067
86932
  access: "view",
87068
86933
  role: "admin"
87069
86934
  },
87070
- "llm.migrateRawSecrets": {
87071
- capName: "llm",
87072
- capScope: "system",
87073
- addonId: null,
87074
- access: "create",
87075
- role: "admin"
87076
- },
87077
86935
  "llm.resolveModelRef": {
87078
86936
  capName: "llm",
87079
86937
  capScope: "system",
@@ -87102,20 +86960,6 @@ Object.freeze({
87102
86960
  access: "create",
87103
86961
  role: "admin"
87104
86962
  },
87105
- "llm.testProfile": {
87106
- capName: "llm",
87107
- capScope: "system",
87108
- addonId: null,
87109
- access: "create",
87110
- role: "admin"
87111
- },
87112
- "llm.upsertProfile": {
87113
- capName: "llm",
87114
- capScope: "system",
87115
- addonId: null,
87116
- access: "create",
87117
- role: "admin"
87118
- },
87119
86963
  "llmRuntime.appleAvailability": {
87120
86964
  capName: "llm-runtime",
87121
86965
  capScope: "system",
@@ -87620,13 +87464,6 @@ Object.freeze({
87620
87464
  access: "create",
87621
87465
  role: "admin"
87622
87466
  },
87623
- "meshNetwork.testConnection": {
87624
- capName: "mesh-network",
87625
- capScope: "system",
87626
- addonId: null,
87627
- access: "create",
87628
- role: "admin"
87629
- },
87630
87467
  "meter.getStatus": {
87631
87468
  capName: "meter",
87632
87469
  capScope: "device",
@@ -88285,13 +88122,6 @@ Object.freeze({
88285
88122
  access: "create",
88286
88123
  role: "admin"
88287
88124
  },
88288
- "notificationOutput.deleteTarget": {
88289
- capName: "notification-output",
88290
- capScope: "system",
88291
- addonId: null,
88292
- access: "delete",
88293
- role: "admin"
88294
- },
88295
88125
  "notificationOutput.discoverTargets": {
88296
88126
  capName: "notification-output",
88297
88127
  capScope: "system",
@@ -88299,13 +88129,6 @@ Object.freeze({
88299
88129
  access: "view",
88300
88130
  role: "admin"
88301
88131
  },
88302
- "notificationOutput.getTarget": {
88303
- capName: "notification-output",
88304
- capScope: "system",
88305
- addonId: null,
88306
- access: "view",
88307
- role: "admin"
88308
- },
88309
88132
  "notificationOutput.listTargetKinds": {
88310
88133
  capName: "notification-output",
88311
88134
  capScope: "system",
@@ -88313,13 +88136,6 @@ Object.freeze({
88313
88136
  access: "view",
88314
88137
  role: "admin"
88315
88138
  },
88316
- "notificationOutput.listTargets": {
88317
- capName: "notification-output",
88318
- capScope: "system",
88319
- addonId: null,
88320
- access: "view",
88321
- role: "admin"
88322
- },
88323
88139
  "notificationOutput.probeTargetConfig": {
88324
88140
  capName: "notification-output",
88325
88141
  capScope: "system",
@@ -88334,13 +88150,6 @@ Object.freeze({
88334
88150
  access: "create",
88335
88151
  role: "admin"
88336
88152
  },
88337
- "notificationOutput.setTargetEnabled": {
88338
- capName: "notification-output",
88339
- capScope: "system",
88340
- addonId: null,
88341
- access: "create",
88342
- role: "admin"
88343
- },
88344
88153
  "notificationOutput.testTarget": {
88345
88154
  capName: "notification-output",
88346
88155
  capScope: "system",
@@ -88348,13 +88157,6 @@ Object.freeze({
88348
88157
  access: "create",
88349
88158
  role: "admin"
88350
88159
  },
88351
- "notificationOutput.upsertTarget": {
88352
- capName: "notification-output",
88353
- capScope: "system",
88354
- addonId: null,
88355
- access: "create",
88356
- role: "admin"
88357
- },
88358
88160
  "notificationRules.activateAlarmProfile": {
88359
88161
  capName: "notification-rules",
88360
88162
  capScope: "system",
@@ -88614,41 +88416,6 @@ Object.freeze({
88614
88416
  access: "view",
88615
88417
  role: "admin"
88616
88418
  },
88617
- "oidcConnections.connectionFormSchema": {
88618
- capName: "oidc-connections",
88619
- capScope: "system",
88620
- addonId: null,
88621
- access: "view",
88622
- role: "admin"
88623
- },
88624
- "oidcConnections.createConnection": {
88625
- capName: "oidc-connections",
88626
- capScope: "system",
88627
- addonId: null,
88628
- access: "create",
88629
- role: "admin"
88630
- },
88631
- "oidcConnections.deleteConnection": {
88632
- capName: "oidc-connections",
88633
- capScope: "system",
88634
- addonId: null,
88635
- access: "delete",
88636
- role: "admin"
88637
- },
88638
- "oidcConnections.listConnections": {
88639
- capName: "oidc-connections",
88640
- capScope: "system",
88641
- addonId: null,
88642
- access: "view",
88643
- role: "admin"
88644
- },
88645
- "oidcConnections.updateConnection": {
88646
- capName: "oidc-connections",
88647
- capScope: "system",
88648
- addonId: null,
88649
- access: "create",
88650
- role: "admin"
88651
- },
88652
88419
  "osd.getStatus": {
88653
88420
  capName: "osd",
88654
88421
  capScope: "device",
@@ -91680,13 +91447,6 @@ Object.freeze({
91680
91447
  access: "delete",
91681
91448
  role: "admin"
91682
91449
  },
91683
- "storage.deleteLocation": {
91684
- capName: "storage",
91685
- capScope: "system",
91686
- addonId: null,
91687
- access: "delete",
91688
- role: "admin"
91689
- },
91690
91450
  "storage.endDownload": {
91691
91451
  capName: "storage",
91692
91452
  capScope: "system",
@@ -91743,13 +91503,6 @@ Object.freeze({
91743
91503
  access: "view",
91744
91504
  role: "admin"
91745
91505
  },
91746
- "storage.listProviders": {
91747
- capName: "storage",
91748
- capScope: "system",
91749
- addonId: null,
91750
- access: "view",
91751
- role: "admin"
91752
- },
91753
91506
  "storage.read": {
91754
91507
  capName: "storage",
91755
91508
  capScope: "system",
@@ -91771,27 +91524,6 @@ Object.freeze({
91771
91524
  access: "view",
91772
91525
  role: "admin"
91773
91526
  },
91774
- "storage.testConfig": {
91775
- capName: "storage",
91776
- capScope: "system",
91777
- addonId: null,
91778
- access: "view",
91779
- role: "admin"
91780
- },
91781
- "storage.testLocation": {
91782
- capName: "storage",
91783
- capScope: "system",
91784
- addonId: null,
91785
- access: "view",
91786
- role: "admin"
91787
- },
91788
- "storage.upsertLocation": {
91789
- capName: "storage",
91790
- capScope: "system",
91791
- addonId: null,
91792
- access: "create",
91793
- role: "admin"
91794
- },
91795
91527
  "storage.write": {
91796
91528
  capName: "storage",
91797
91529
  capScope: "system",
@@ -145519,6 +145251,33 @@ function quietLogger$1() {
145519
145251
  };
145520
145252
  return logger;
145521
145253
  }
145254
+ /**
145255
+ * The account a target speaks through: the one it names; else the only one there is; else the one
145256
+ * whose Echoes include the target's Echo.
145257
+ */
145258
+ function pickBroker(brokers, config) {
145259
+ const named = readString(config, "brokerId");
145260
+ if (named.length > 0) return brokers.find((broker) => broker.id === named);
145261
+ if (brokers.length === 1) return brokers[0];
145262
+ const echoId = readString(config, "echoId");
145263
+ const owners = brokers.filter((broker) => broker.devices.some((device) => device.id === echoId));
145264
+ return owners.length === 1 ? owners[0] : void 0;
145265
+ }
145266
+ /**
145267
+ * The save-time rule of an Echo target: an Echo and an account must be picked, and the account is
145268
+ * pinned the moment it is known, so a second account added later cannot silently change where an
145269
+ * existing target speaks. Throws the reason; returns the keys to keep with the write.
145270
+ */
145271
+ function validateEchoTarget(brokers, config) {
145272
+ if (readString(config, "echoId").length === 0) throw new Error("Pick the Echo that should speak");
145273
+ const owner = pickBroker(brokers, config);
145274
+ if (owner === void 0) {
145275
+ const named = readString(config, "brokerId");
145276
+ if (named.length > 0) throw new Error(`The Amazon account ${named} is not available`);
145277
+ throw new Error(brokers.length === 0 ? "Add an Amazon account first: an Echo speaks through one" : "Pick the Amazon account this Echo belongs to");
145278
+ }
145279
+ return readString(config, "brokerId").length === 0 ? { brokerId: owner.id } : {};
145280
+ }
145522
145281
  function readString(config, key) {
145523
145282
  const value = config[key];
145524
145283
  return typeof value === "string" ? value : "";
@@ -145548,7 +145307,7 @@ function echoOptionLabel(device) {
145548
145307
  /**
145549
145308
  * The two pickers are not `required`: a required select shows its first option
145550
145309
  * while the form still holds no value, so a save would drop the choice. Left
145551
- * optional they open on "Select", and `upsertTarget` refuses an empty pick.
145310
+ * optional they open on "Select", and `validateEchoTarget` refuses an empty pick on save.
145552
145311
  *
145553
145312
  * The account is a broker pick (existing, or created in place); the Echo list
145554
145313
  * is fetched from that account, so a new Echo shows up without a deploy.
@@ -145640,18 +145399,8 @@ function createAlexaNotificationProvider(ports) {
145640
145399
  function record(row) {
145641
145400
  deliveries = [...deliveries, row];
145642
145401
  }
145643
- /**
145644
- * The account a target speaks through: the one it names; else the only one
145645
- * there is; else the one whose Echoes include the target's Echo.
145646
- */
145647
145402
  function brokerFor(config) {
145648
- const all = ports.brokers();
145649
- const named = readString(config, "brokerId");
145650
- if (named.length > 0) return all.find((broker) => broker.id === named);
145651
- if (all.length === 1) return all[0];
145652
- const echoId = readString(config, "echoId");
145653
- const owners = all.filter((broker) => broker.devices.some((device) => device.id === echoId));
145654
- return owners.length === 1 ? owners[0] : void 0;
145403
+ return pickBroker(ports.brokers(), config);
145655
145404
  }
145656
145405
  async function deliver(target, notification) {
145657
145406
  const speech = speechFromNotification(notification);
@@ -145853,14 +145602,6 @@ function createAlexaNotificationProvider(ports) {
145853
145602
  async listTargetKinds() {
145854
145603
  return [alexaTargetKind()];
145855
145604
  },
145856
- async listTargets() {
145857
- return [...await ports.loadTargets()];
145858
- },
145859
- async getTarget(input) {
145860
- const found = (await ports.loadTargets()).find((target) => target.id === input.targetId);
145861
- if (found === void 0) throw new Error(`target not found: ${input.targetId}`);
145862
- return found;
145863
- },
145864
145605
  async discoverTargets(input) {
145865
145606
  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 } });
145866
145607
  const named = readString(input.config ?? {}, "brokerId");
@@ -145921,33 +145662,6 @@ function createAlexaNotificationProvider(ports) {
145921
145662
  format: "text",
145922
145663
  priority: 3
145923
145664
  });
145924
- },
145925
- async upsertTarget(input) {
145926
- const owner = brokerFor(input.target.config);
145927
- if (readString(input.target.config, "echoId").length === 0) throw new Error("Pick the Echo that should speak");
145928
- if (owner === void 0) throw new Error("Pick the Amazon account this Echo belongs to");
145929
- const pinned = readString(input.target.config, "brokerId").length === 0 ? {
145930
- ...input.target.config,
145931
- brokerId: owner.id
145932
- } : input.target.config;
145933
- const target = {
145934
- ...input.target,
145935
- config: pinned,
145936
- addonId: ALEXA_SPEAK_ADDON_ID,
145937
- kind: ALEXA_SPEAK_KIND
145938
- };
145939
- await ports.putTarget(target);
145940
- return target;
145941
- },
145942
- async deleteTarget(input) {
145943
- await ports.removeTarget(input.targetId);
145944
- },
145945
- async setTargetEnabled(input) {
145946
- const found = (await ports.loadTargets()).find((row) => row.id === input.targetId);
145947
- if (found !== void 0) await ports.putTarget({
145948
- ...found,
145949
- enabled: input.enabled
145950
- });
145951
145665
  }
145952
145666
  };
145953
145667
  }
@@ -146038,6 +145752,14 @@ function echoTargetProbe(speak) {
146038
145752
  });
146039
145753
  };
146040
145754
  }
145755
+ /**
145756
+ * The Echo instance's save-time rule over the accounts now (D1013): the check the removed
145757
+ * `upsertTarget` made, on the instance door. Speech not started yet has no account to name, so the
145758
+ * write is refused with the reason, never accepted blind.
145759
+ */
145760
+ function echoTargetValidator(brokers) {
145761
+ return ({ config }) => validateEchoTarget(brokers() ?? [], config);
145762
+ }
146041
145763
  process.platform, process.env["SystemRoot"];
146042
145764
  /**
146043
145765
  * Files only their owner may read: the one place a secret-bearing file is
@@ -146878,8 +146600,6 @@ var AlexaSpeakController = class {
146878
146600
  now: this.now,
146879
146601
  logger: host.logger,
146880
146602
  loadTargets: () => host.readTargets(),
146881
- putTarget: (target) => host.putTarget(target),
146882
- removeTarget: (targetId) => host.removeTarget(targetId),
146883
146603
  speak: (command) => this.utter(command.brokerId, command.mode, command.targets, command.text)
146884
146604
  });
146885
146605
  }
@@ -146909,6 +146629,7 @@ var AlexaSpeakController = class {
146909
146629
  echoFor(brokerId) {
146910
146630
  return this.echoes.get(brokerId) ?? null;
146911
146631
  }
146632
+ /** The accounts right now, as the provider and the save-time rule see them. */
146912
146633
  brokerViews() {
146913
146634
  return this.host.listBrokers().map((ref) => {
146914
146635
  const echo = this.echoes.get(ref.id);
@@ -147456,8 +147177,6 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
147456
147177
  const speak = new AlexaSpeakController({
147457
147178
  logger: this.ctx.logger.child("speak"),
147458
147179
  readTargets: () => echoStore.list(),
147459
- putTarget: (target) => echoStore.upsert(target),
147460
- removeTarget: (targetId) => echoStore.delete(targetId),
147461
147180
  listBrokers: () => this.activeBrokers.map((entry) => ({
147462
147181
  id: entry.id,
147463
147182
  name: entry.name,
@@ -147559,6 +147278,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
147559
147278
  kinds: alexaNotifyRecordKinds,
147560
147279
  mintId: () => randomUUID(),
147561
147280
  probe: echoTargetProbe(() => this.speak),
147281
+ validate: echoTargetValidator(() => this.speak?.brokerViews() ?? null),
147562
147282
  canProbe: () => true
147563
147283
  })
147564
147284
  ], {