@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.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-CdKka2ri.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).
@@ -6751,10 +6768,33 @@ function integrationRowsApiOver(api) {
6751
6768
  };
6752
6769
  }
6753
6770
  var PREFIX = "records/";
6771
+ /**
6772
+ * Cross-realm brand. An addon bundles its own copy of this module (D15) while
6773
+ * `ctx.records` is the HOST's door, so an error thrown by the host is an
6774
+ * instance of the host's classes, not of the addon's. The brand plus the code
6775
+ * make `instanceof` answer by identity of the failure, not of the copy.
6776
+ */
6777
+ var RECORDS_ERROR_BRAND = Symbol.for("camstack.records-error");
6778
+ function carriesBrand(value) {
6779
+ return typeof value === "object" && value !== null && Reflect.get(value, RECORDS_ERROR_BRAND) === true;
6780
+ }
6754
6781
  /** The base of every records failure. `detail` is the human part of the message. */
6755
6782
  var RecordsError = class extends Error {
6783
+ /** The code a subclass stands for; the base stands for every code. */
6784
+ static code = void 0;
6785
+ /**
6786
+ * `instanceof` across copies of this module: any branded error is a
6787
+ * `RecordsError`, and is an instance of a subclass only when its code is the
6788
+ * subclass's code. Same-copy answers are unchanged.
6789
+ */
6790
+ static [Symbol.hasInstance](value) {
6791
+ if (Function.prototype[Symbol.hasInstance].call(this, value)) return true;
6792
+ if (!carriesBrand(value)) return false;
6793
+ return this.code === void 0 || value.code === this.code;
6794
+ }
6756
6795
  code;
6757
6796
  detail;
6797
+ [RECORDS_ERROR_BRAND] = true;
6758
6798
  constructor(code, detail) {
6759
6799
  super(`${PREFIX}${code}: ${detail}`);
6760
6800
  this.name = "RecordsError";
@@ -6763,29 +6803,33 @@ var RecordsError = class extends Error {
6763
6803
  }
6764
6804
  };
6765
6805
  /** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
6766
- var RevConflictError = class extends RecordsError {
6806
+ var RevConflictError = class RevConflictError extends RecordsError {
6807
+ static code = "rev-conflict";
6767
6808
  constructor(detail) {
6768
- super("rev-conflict", detail);
6809
+ super(RevConflictError.code, detail);
6769
6810
  this.name = "RevConflictError";
6770
6811
  }
6771
6812
  };
6772
6813
  /** The keychain could not store or serve a value: explicit, never an empty string. */
6773
- var RecordSecretUnavailableError = class extends RecordsError {
6814
+ var RecordSecretUnavailableError = class RecordSecretUnavailableError extends RecordsError {
6815
+ static code = "secret-unavailable";
6774
6816
  constructor(detail) {
6775
- super("secret-unavailable", detail);
6817
+ super(RecordSecretUnavailableError.code, detail);
6776
6818
  this.name = "RecordSecretUnavailableError";
6777
6819
  }
6778
6820
  };
6779
- var InvalidRecordError = class extends RecordsError {
6821
+ var InvalidRecordError = class InvalidRecordError extends RecordsError {
6822
+ static code = "invalid-record";
6780
6823
  constructor(detail) {
6781
- super("invalid-record", detail);
6824
+ super(InvalidRecordError.code, detail);
6782
6825
  this.name = "InvalidRecordError";
6783
6826
  }
6784
6827
  };
6785
6828
  /** A kind declaration the engine refuses (non-additive change, bad shape, wrong file). */
6786
- var DeclarationRefusedError = class extends RecordsError {
6829
+ var DeclarationRefusedError = class DeclarationRefusedError extends RecordsError {
6830
+ static code = "declaration-refused";
6787
6831
  constructor(detail) {
6788
- super("declaration-refused", detail);
6832
+ super(DeclarationRefusedError.code, detail);
6789
6833
  this.name = "DeclarationRefusedError";
6790
6834
  }
6791
6835
  };
@@ -7279,17 +7323,35 @@ var RecordIntegrationInstances = class {
7279
7323
  ...kind !== void 0 ? { kind } : {}
7280
7324
  });
7281
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
+ }));
7282
7335
  await this.handle.put({
7283
7336
  id: minted,
7284
- data: this.hooks.initialBody(name, kind)
7337
+ data: initial
7285
7338
  }, { ifRev: 0 });
7286
- const patch = this.patchOf(values ?? {});
7287
7339
  if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
7288
7340
  await this.hooks.onChanged?.(minted, "created");
7289
7341
  return { id: minted };
7290
7342
  };
7291
7343
  updateInstance = async ({ id, name, values }) => {
7292
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
+ }
7293
7355
  if (name !== void 0) patch[this.nameField] = name;
7294
7356
  await this.handle.patch(id, patch);
7295
7357
  await this.hooks.onChanged?.(id, "updated");
@@ -10635,7 +10697,6 @@ var CAP_NAMES = Object.freeze([
10635
10697
  "notifier",
10636
10698
  "numeric-sensor",
10637
10699
  "oauth-integration",
10638
- "oidc-connections",
10639
10700
  "osd",
10640
10701
  "osd-manager",
10641
10702
  "pet-feeder",
@@ -12527,8 +12588,7 @@ Object.freeze({
12527
12588
  "device-provider": { "reloadDevice": 18e4 },
12528
12589
  "llm": {
12529
12590
  "generate": 6e5,
12530
- "generateVision": 6e5,
12531
- "testProfile": 6e5
12591
+ "generateVision": 6e5
12532
12592
  },
12533
12593
  "llm-runtime": {
12534
12594
  "complete": 6e5,
@@ -14139,17 +14199,18 @@ function composeInstanceKinds(parts, base) {
14139
14199
  * message to what the kind supports — callers never special-case a service.
14140
14200
  *
14141
14201
  * DESIGN DECISIONS (locked):
14142
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
14143
- * `setTargetEnabled`), each provider persisting via the `settings-store`
14144
- * cap. Rationale: the admin UI needs one uniform surface across the
14145
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
14146
- * alternative would fork the UI per addon and cannot host the
14147
- * discovery→adopt flow.
14148
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
14149
- * the generated cap-mount auto-`concatCollection`-fans them across every
14150
- * registered provider (notifiers addon + HA addon) so one catalog is
14151
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
14152
- * `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.
14153
14214
  * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
14154
14215
  * transformer) + UDS MsgPack both round-trip typed arrays — already used by
14155
14216
  * `storage` / `storage-provider` / `recording` caps over the same path. No
@@ -14394,7 +14455,7 @@ var TargetKindCapsSchema = object({
14394
14455
  * the union is large and not meant for runtime validation here; the exported
14395
14456
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14396
14457
  */
14397
- var ConfigSchemaPassthrough$4 = unknown();
14458
+ var ConfigSchemaPassthrough$3 = unknown();
14398
14459
  var TargetKindSchema = object({
14399
14460
  kind: string(),
14400
14461
  label: string(),
@@ -14431,7 +14492,7 @@ var TargetKindSchema = object({
14431
14492
  * its raster path, which is the safe default for an unknown image.
14432
14493
  */
14433
14494
  iconMediaType: string().optional(),
14434
- configSchema: ConfigSchemaPassthrough$4,
14495
+ configSchema: ConfigSchemaPassthrough$3,
14435
14496
  /**
14436
14497
  * The config keys that decide WHERE this kind's secrets are sent (server,
14437
14498
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -14453,9 +14514,11 @@ var TargetKindSchema = object({
14453
14514
  caps: TargetKindCapsSchema
14454
14515
  });
14455
14516
  /**
14456
- * A persisted target. `config` holds secrets; providers REDACT secret fields
14457
- * (return a presence marker only) when serving `listTargets` — never
14458
- * 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.
14459
14522
  */
14460
14523
  var TargetSchema = object({
14461
14524
  id: string(),
@@ -14504,21 +14567,13 @@ var notificationOutputCapability = {
14504
14567
  mode: "collection",
14505
14568
  methods: {
14506
14569
  listTargetKinds: method(object({}), array(TargetKindSchema)),
14507
- listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
14508
- /**
14509
- * One target WITH secrets — admin edit / duplicate hydration only.
14510
- * `listTargets` stays redacted; the editor loads this so password fields
14511
- * are filled (masked in the input) and a save can round-trip the token
14512
- * when a destination changes (Duplicate with a new chatId, etc.).
14513
- */
14514
- getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
14515
14570
  discoverTargets: method(object({
14516
14571
  kind: string(),
14517
14572
  config: record(string(), unknown()).optional(),
14518
14573
  /**
14519
14574
  * When editing / duplicating, the blank password field never round-trips
14520
14575
  * the stored token. Pass the source target id so the provider can restore
14521
- * secrets server-side before dialling (same merge as upsert).
14576
+ * secrets server-side before dialling.
14522
14577
  */
14523
14578
  targetId: string().optional()
14524
14579
  }), array(DiscoveredTargetSchema), { auth: "admin" }),
@@ -14544,30 +14599,6 @@ var notificationOutputCapability = {
14544
14599
  }), TestResultSchema, {
14545
14600
  kind: "mutation",
14546
14601
  auth: "admin"
14547
- }),
14548
- upsertTarget: method(object({
14549
- target: TargetSchema,
14550
- /**
14551
- * Duplicate flow: create a NEW target id while copying secrets from an
14552
- * existing row. The UI never holds the stored token; blank / redacted
14553
- * password fields are restored from this source (D634 still applies if
14554
- * destinations diverge without a re-typed secret).
14555
- */
14556
- cloneSecretsFrom: string().optional()
14557
- }), TargetSchema, {
14558
- kind: "mutation",
14559
- auth: "admin"
14560
- }),
14561
- deleteTarget: method(object({ targetId: string() }), _void(), {
14562
- kind: "mutation",
14563
- auth: "admin"
14564
- }),
14565
- setTargetEnabled: method(object({
14566
- targetId: string(),
14567
- enabled: boolean()
14568
- }), _void(), {
14569
- kind: "mutation",
14570
- auth: "admin"
14571
14602
  })
14572
14603
  }
14573
14604
  };
@@ -14583,8 +14614,9 @@ var notificationOutputCapability = {
14583
14614
  *
14584
14615
  * Three providers (notifiers, Home Assistant, Alexa) serve the same shape, so the pieces are shared:
14585
14616
  * - {@link defineNotificationTargetKind}: the record kind of one `TargetKind` descriptor;
14586
- * - {@link NotificationTargetStore}: the `Target`-shaped facade the existing provider code keeps
14587
- * 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;
14588
14620
  * - {@link buildNotificationTargetInstances}: the `integration-instances` provider, one kind each;
14589
14621
  * - {@link moveTargetRows}: the D987 procedure that flattens the old `config` blobs into the bodies.
14590
14622
  */
@@ -14841,6 +14873,10 @@ function notificationTargetParts(records, spec) {
14841
14873
  initialBody: (name) => ({ name }),
14842
14874
  mintId: () => spec.mintId(),
14843
14875
  configured: () => true,
14876
+ validateWrite: ({ body }) => spec.validate?.({
14877
+ kind: descriptor.kind,
14878
+ config: body
14879
+ }),
14844
14880
  test: async ({ id, values }) => {
14845
14881
  if (spec.probe === void 0 || !canTest) return {
14846
14882
  success: false,
@@ -23236,7 +23272,6 @@ var SYSTEM_CAP_VOCABULARY = {
23236
23272
  "notification-output": system("notification delivery — reached through notifier devices and rules"),
23237
23273
  "notification-rules": system("notification rules — authored on their own page"),
23238
23274
  "oauth-integration": system(IDENTITY),
23239
- "oidc-connections": system(IDENTITY),
23240
23275
  "osd-manager": system(PIPELINE),
23241
23276
  "pipeline-executor": system(PIPELINE),
23242
23277
  "pipeline-orchestrator": system(PIPELINE),
@@ -57028,7 +57063,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
57028
57063
  * or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
57029
57064
  * clears it.
57030
57065
  */
57031
- var ConfigSchemaPassthrough$3 = custom();
57066
+ var ConfigSchemaPassthrough$2 = custom();
57032
57067
  var IntegrationInstanceIdSchema = string().min(1);
57033
57068
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
57034
57069
  /**
@@ -57074,7 +57109,16 @@ var IntegrationInstanceKindSchema = object({
57074
57109
  /** Relative hub path to start an interactive account link of an instance of this kind. */
57075
57110
  connectPath: string().min(1).optional(),
57076
57111
  /** Relative hub path to disconnect that account. */
57077
- 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()
57078
57122
  });
57079
57123
  /**
57080
57124
  * How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
@@ -57179,7 +57223,7 @@ var integrationInstancesCapability = {
57179
57223
  id: IntegrationInstanceIdSchema.nullable(),
57180
57224
  /** The kind of the create form (`id: null`) of a provider with several. */
57181
57225
  kind: string().min(1).optional()
57182
- }), ConfigSchemaPassthrough$3, { auth: "admin" }),
57226
+ }), ConfigSchemaPassthrough$2, { auth: "admin" }),
57183
57227
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
57184
57228
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
57185
57229
  /**
@@ -58281,12 +58325,12 @@ var LlmModelInfoSchema = object({
58281
58325
  });
58282
58326
  /**
58283
58327
  * The provider-specific part of a profile, validated by the provider that owns
58284
- * 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
58285
58329
  * `connectionSchema` declares `type: 'password'` is a secret: it is held in the
58286
58330
  * keychain, never in the row, and reads back as the redaction marker.
58287
58331
  */
58288
58332
  var LlmProviderConfigSchema = record(string(), unknown());
58289
- var LlmProfileSchema = object({
58333
+ object({
58290
58334
  id: string(),
58291
58335
  name: string(),
58292
58336
  /** The provider-declared kind (`LlmProfileKindDescriptor.kind`). */
@@ -58380,8 +58424,8 @@ var LlmProviderWidgetSchema = object({
58380
58424
  /** ConfigUISchema tree passed through untyped on the wire (the
58381
58425
  * notification-output `ConfigSchemaPassthrough` precedent at
58382
58426
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
58383
- var ConfigSchemaPassthrough$2 = unknown();
58384
- var LlmProfileKindDescriptorSchema = object({
58427
+ var ConfigSchemaPassthrough$1 = unknown();
58428
+ object({
58385
58429
  kind: LlmProfileKindSchema,
58386
58430
  label: string(),
58387
58431
  /** A generic icon name (`cloud`, `plug`, `cpu`) — never a glyph or a logo. */
@@ -58389,9 +58433,9 @@ var LlmProfileKindDescriptorSchema = object({
58389
58433
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
58390
58434
  addonId: string(),
58391
58435
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
58392
- connectionSchema: ConfigSchemaPassthrough$2,
58436
+ connectionSchema: ConfigSchemaPassthrough$1,
58393
58437
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
58394
- advancedSchema: ConfigSchemaPassthrough$2.optional(),
58438
+ advancedSchema: ConfigSchemaPassthrough$1.optional(),
58395
58439
  /** The kind's defaults; a model from `listModels` may override any of them. */
58396
58440
  features: LlmModelFeaturesSchema,
58397
58441
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -58520,20 +58564,7 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
58520
58564
  }), method(object({
58521
58565
  addonId: string().optional(),
58522
58566
  requestId: string()
58523
- }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
58524
- kind: "mutation",
58525
- auth: "admin"
58526
- }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
58527
- kind: "mutation",
58528
- auth: "admin"
58529
- }), method(ProfileRefInputSchema, _void(), {
58530
- kind: "mutation",
58531
- auth: "admin"
58532
- }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
58533
- kind: "mutation",
58534
- auth: "admin",
58535
- timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
58536
- }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
58567
+ }), _void(), { kind: "mutation" }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
58537
58568
  kind: "mutation",
58538
58569
  auth: "admin"
58539
58570
  }), method(object({}), array(LlmDefaultSchema)), method(object({
@@ -59596,20 +59627,7 @@ method(_void(), MeshStatusSchema), method(object({
59596
59627
  /** Optional hostname override the host should advertise once joined. */
59597
59628
  hostname: string().optional() }), object({
59598
59629
  /** Authentication URL the operator should open in a browser. */
59599
- 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({
59600
- /** Optional auth key — when provided, probes the key validity
59601
- * against the provider's API. Omit when already joined to
59602
- * just ping the daemon. */
59603
- authKey: string().optional() }), object({
59604
- ok: boolean(),
59605
- /** Provider-side identifier resolved by the probe (tailnet
59606
- * name for Tailscale, network id for ZeroTier, etc.). */
59607
- tenant: string().optional(),
59608
- /** Daemon binary version, when reachable. */
59609
- daemonVersion: string().optional(),
59610
- /** Human-readable error when `ok: false`. */
59611
- error: string().optional()
59612
- }), { 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() }));
59613
59631
  /**
59614
59632
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
59615
59633
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -69816,61 +69834,6 @@ var OauthIntegrationDescriptorSchema = object({
69816
69834
  });
69817
69835
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
69818
69836
  /**
69819
- * `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
69820
- *
69821
- * One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
69822
- * ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
69823
- * Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
69824
- * immutable after create.
69825
- */
69826
- var ConfigSchemaPassthrough$1 = unknown();
69827
- var OidcConnectionSchema = object({
69828
- id: string().min(1),
69829
- displayName: string(),
69830
- icon: string(),
69831
- issuerUrl: string(),
69832
- clientId: string(),
69833
- /** Client id + secret present — never the secret value. */
69834
- configured: boolean(),
69835
- /**
69836
- * Values for the connection form schema. `clientSecret` is a keychain
69837
- * {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
69838
- */
69839
- formValues: record(string(), unknown()).optional()
69840
- });
69841
- /**
69842
- * Patch one connection. Omitted or empty `clientSecret` keeps the stored
69843
- * secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
69844
- */
69845
- var OidcConnectionUpdateInputSchema = object({
69846
- id: string().min(1),
69847
- displayName: string().min(1),
69848
- icon: string().optional(),
69849
- issuerUrl: string().optional(),
69850
- clientId: string().optional(),
69851
- clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
69852
- redirectUri: string().optional(),
69853
- scopes: string().optional(),
69854
- usernameClaim: _enum([
69855
- "preferred_username",
69856
- "email",
69857
- "sub"
69858
- ]).optional()
69859
- });
69860
- method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
69861
- id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
69862
- displayName: string().min(1)
69863
- }), object({ id: string() }), {
69864
- kind: "mutation",
69865
- auth: "admin"
69866
- }), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
69867
- kind: "mutation",
69868
- auth: "admin"
69869
- }), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
69870
- kind: "mutation",
69871
- auth: "admin"
69872
- });
69873
- /**
69874
69837
  * Generic on-screen-display (video overlay) cap. Each camera exposes
69875
69838
  * a firmware-defined list of overlays (channel name, timestamp,
69876
69839
  * watermark, logo, …); the cap models them as a homogeneous array of
@@ -76952,21 +76915,6 @@ method(object({
76952
76915
  */
76953
76916
  ttlSec: union([number$1().int().positive(), literal("never")]).optional()
76954
76917
  }), object({ token: string() }), { auth: "admin" }), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" });
76955
- var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
76956
- providerId: string().min(1),
76957
- displayName: string().min(1),
76958
- configSchema: unknown(),
76959
- nodeLocal: boolean(),
76960
- shouldSaveDiskSpace: literal(true),
76961
- minFreePercent: number$1().min(0).max(100)
76962
- }), object({
76963
- providerId: string().min(1),
76964
- displayName: string().min(1),
76965
- configSchema: unknown(),
76966
- nodeLocal: boolean(),
76967
- shouldSaveDiskSpace: literal(false),
76968
- minFreePercent: literal(null)
76969
- })]);
76970
76918
  method(object({
76971
76919
  location: StorageLocationRefSchema,
76972
76920
  relativePath: string()
@@ -77028,42 +76976,7 @@ method(object({
77028
76976
  }), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
77029
76977
  kind: "mutation",
77030
76978
  auth: "admin"
77031
- }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
77032
- createdAt: true,
77033
- updatedAt: true
77034
- }), StorageLocationSchema, {
77035
- kind: "mutation",
77036
- auth: "admin"
77037
- }), method(object({
77038
- id: string(),
77039
- force: boolean().optional()
77040
- }), _void(), {
77041
- kind: "mutation",
77042
- auth: "admin"
77043
- }), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
77044
- ok: boolean(),
77045
- error: string().optional()
77046
- }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
77047
- providerId: string(),
77048
- /**
77049
- * The location this config is an UNSAVED edit of, when there is one.
77050
- *
77051
- * `listLocations` replaces every declared secret with the redaction
77052
- * sentinel, so the edit modal's form state holds the sentinel for any
77053
- * credential the operator did not retype — and posting that here
77054
- * without a way to resolve it makes the provider try to authenticate
77055
- * as `__camstack_redacted__` and report the operator's own working
77056
- * password as wrong. Given this id, the orchestrator restores each
77057
- * sentinel from the stored config (same rule as `upsertLocation`)
77058
- * before dispatching. Omitted by the "Add location" wizard, where
77059
- * every value was typed just now and nothing is stored yet.
77060
- */
77061
- locationId: string().optional(),
77062
- config: record(string(), unknown())
77063
- }), object({
77064
- ok: boolean(),
77065
- error: string().optional()
77066
- }), { auth: "admin" });
76979
+ }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(_void(), array(StorageDrainProgressSchema).readonly());
77067
76980
  /** One table of the settings database. */
77068
76981
  var StorageTableSchema = object({
77069
76982
  /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
@@ -86949,13 +86862,6 @@ Object.freeze({
86949
86862
  access: "delete",
86950
86863
  role: "admin"
86951
86864
  },
86952
- "llm.deleteProfile": {
86953
- capName: "llm",
86954
- capScope: "system",
86955
- addonId: null,
86956
- access: "delete",
86957
- role: "admin"
86958
- },
86959
86865
  "llm.generate": {
86960
86866
  capName: "llm",
86961
86867
  capScope: "system",
@@ -87019,20 +86925,6 @@ Object.freeze({
87019
86925
  access: "view",
87020
86926
  role: "admin"
87021
86927
  },
87022
- "llm.listProfileKinds": {
87023
- capName: "llm",
87024
- capScope: "system",
87025
- addonId: null,
87026
- access: "view",
87027
- role: "admin"
87028
- },
87029
- "llm.listProfiles": {
87030
- capName: "llm",
87031
- capScope: "system",
87032
- addonId: null,
87033
- access: "view",
87034
- role: "admin"
87035
- },
87036
86928
  "llm.listRuntimeNodes": {
87037
86929
  capName: "llm",
87038
86930
  capScope: "system",
@@ -87040,13 +86932,6 @@ Object.freeze({
87040
86932
  access: "view",
87041
86933
  role: "admin"
87042
86934
  },
87043
- "llm.migrateRawSecrets": {
87044
- capName: "llm",
87045
- capScope: "system",
87046
- addonId: null,
87047
- access: "create",
87048
- role: "admin"
87049
- },
87050
86935
  "llm.resolveModelRef": {
87051
86936
  capName: "llm",
87052
86937
  capScope: "system",
@@ -87075,20 +86960,6 @@ Object.freeze({
87075
86960
  access: "create",
87076
86961
  role: "admin"
87077
86962
  },
87078
- "llm.testProfile": {
87079
- capName: "llm",
87080
- capScope: "system",
87081
- addonId: null,
87082
- access: "create",
87083
- role: "admin"
87084
- },
87085
- "llm.upsertProfile": {
87086
- capName: "llm",
87087
- capScope: "system",
87088
- addonId: null,
87089
- access: "create",
87090
- role: "admin"
87091
- },
87092
86963
  "llmRuntime.appleAvailability": {
87093
86964
  capName: "llm-runtime",
87094
86965
  capScope: "system",
@@ -87593,13 +87464,6 @@ Object.freeze({
87593
87464
  access: "create",
87594
87465
  role: "admin"
87595
87466
  },
87596
- "meshNetwork.testConnection": {
87597
- capName: "mesh-network",
87598
- capScope: "system",
87599
- addonId: null,
87600
- access: "create",
87601
- role: "admin"
87602
- },
87603
87467
  "meter.getStatus": {
87604
87468
  capName: "meter",
87605
87469
  capScope: "device",
@@ -88258,13 +88122,6 @@ Object.freeze({
88258
88122
  access: "create",
88259
88123
  role: "admin"
88260
88124
  },
88261
- "notificationOutput.deleteTarget": {
88262
- capName: "notification-output",
88263
- capScope: "system",
88264
- addonId: null,
88265
- access: "delete",
88266
- role: "admin"
88267
- },
88268
88125
  "notificationOutput.discoverTargets": {
88269
88126
  capName: "notification-output",
88270
88127
  capScope: "system",
@@ -88272,13 +88129,6 @@ Object.freeze({
88272
88129
  access: "view",
88273
88130
  role: "admin"
88274
88131
  },
88275
- "notificationOutput.getTarget": {
88276
- capName: "notification-output",
88277
- capScope: "system",
88278
- addonId: null,
88279
- access: "view",
88280
- role: "admin"
88281
- },
88282
88132
  "notificationOutput.listTargetKinds": {
88283
88133
  capName: "notification-output",
88284
88134
  capScope: "system",
@@ -88286,13 +88136,6 @@ Object.freeze({
88286
88136
  access: "view",
88287
88137
  role: "admin"
88288
88138
  },
88289
- "notificationOutput.listTargets": {
88290
- capName: "notification-output",
88291
- capScope: "system",
88292
- addonId: null,
88293
- access: "view",
88294
- role: "admin"
88295
- },
88296
88139
  "notificationOutput.probeTargetConfig": {
88297
88140
  capName: "notification-output",
88298
88141
  capScope: "system",
@@ -88307,13 +88150,6 @@ Object.freeze({
88307
88150
  access: "create",
88308
88151
  role: "admin"
88309
88152
  },
88310
- "notificationOutput.setTargetEnabled": {
88311
- capName: "notification-output",
88312
- capScope: "system",
88313
- addonId: null,
88314
- access: "create",
88315
- role: "admin"
88316
- },
88317
88153
  "notificationOutput.testTarget": {
88318
88154
  capName: "notification-output",
88319
88155
  capScope: "system",
@@ -88321,13 +88157,6 @@ Object.freeze({
88321
88157
  access: "create",
88322
88158
  role: "admin"
88323
88159
  },
88324
- "notificationOutput.upsertTarget": {
88325
- capName: "notification-output",
88326
- capScope: "system",
88327
- addonId: null,
88328
- access: "create",
88329
- role: "admin"
88330
- },
88331
88160
  "notificationRules.activateAlarmProfile": {
88332
88161
  capName: "notification-rules",
88333
88162
  capScope: "system",
@@ -88587,41 +88416,6 @@ Object.freeze({
88587
88416
  access: "view",
88588
88417
  role: "admin"
88589
88418
  },
88590
- "oidcConnections.connectionFormSchema": {
88591
- capName: "oidc-connections",
88592
- capScope: "system",
88593
- addonId: null,
88594
- access: "view",
88595
- role: "admin"
88596
- },
88597
- "oidcConnections.createConnection": {
88598
- capName: "oidc-connections",
88599
- capScope: "system",
88600
- addonId: null,
88601
- access: "create",
88602
- role: "admin"
88603
- },
88604
- "oidcConnections.deleteConnection": {
88605
- capName: "oidc-connections",
88606
- capScope: "system",
88607
- addonId: null,
88608
- access: "delete",
88609
- role: "admin"
88610
- },
88611
- "oidcConnections.listConnections": {
88612
- capName: "oidc-connections",
88613
- capScope: "system",
88614
- addonId: null,
88615
- access: "view",
88616
- role: "admin"
88617
- },
88618
- "oidcConnections.updateConnection": {
88619
- capName: "oidc-connections",
88620
- capScope: "system",
88621
- addonId: null,
88622
- access: "create",
88623
- role: "admin"
88624
- },
88625
88419
  "osd.getStatus": {
88626
88420
  capName: "osd",
88627
88421
  capScope: "device",
@@ -91653,13 +91447,6 @@ Object.freeze({
91653
91447
  access: "delete",
91654
91448
  role: "admin"
91655
91449
  },
91656
- "storage.deleteLocation": {
91657
- capName: "storage",
91658
- capScope: "system",
91659
- addonId: null,
91660
- access: "delete",
91661
- role: "admin"
91662
- },
91663
91450
  "storage.endDownload": {
91664
91451
  capName: "storage",
91665
91452
  capScope: "system",
@@ -91716,13 +91503,6 @@ Object.freeze({
91716
91503
  access: "view",
91717
91504
  role: "admin"
91718
91505
  },
91719
- "storage.listProviders": {
91720
- capName: "storage",
91721
- capScope: "system",
91722
- addonId: null,
91723
- access: "view",
91724
- role: "admin"
91725
- },
91726
91506
  "storage.read": {
91727
91507
  capName: "storage",
91728
91508
  capScope: "system",
@@ -91744,27 +91524,6 @@ Object.freeze({
91744
91524
  access: "view",
91745
91525
  role: "admin"
91746
91526
  },
91747
- "storage.testConfig": {
91748
- capName: "storage",
91749
- capScope: "system",
91750
- addonId: null,
91751
- access: "view",
91752
- role: "admin"
91753
- },
91754
- "storage.testLocation": {
91755
- capName: "storage",
91756
- capScope: "system",
91757
- addonId: null,
91758
- access: "view",
91759
- role: "admin"
91760
- },
91761
- "storage.upsertLocation": {
91762
- capName: "storage",
91763
- capScope: "system",
91764
- addonId: null,
91765
- access: "create",
91766
- role: "admin"
91767
- },
91768
91527
  "storage.write": {
91769
91528
  capName: "storage",
91770
91529
  capScope: "system",
@@ -145492,6 +145251,33 @@ function quietLogger$1() {
145492
145251
  };
145493
145252
  return logger;
145494
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
+ }
145495
145281
  function readString(config, key) {
145496
145282
  const value = config[key];
145497
145283
  return typeof value === "string" ? value : "";
@@ -145521,7 +145307,7 @@ function echoOptionLabel(device) {
145521
145307
  /**
145522
145308
  * The two pickers are not `required`: a required select shows its first option
145523
145309
  * while the form still holds no value, so a save would drop the choice. Left
145524
- * 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.
145525
145311
  *
145526
145312
  * The account is a broker pick (existing, or created in place); the Echo list
145527
145313
  * is fetched from that account, so a new Echo shows up without a deploy.
@@ -145613,18 +145399,8 @@ function createAlexaNotificationProvider(ports) {
145613
145399
  function record(row) {
145614
145400
  deliveries = [...deliveries, row];
145615
145401
  }
145616
- /**
145617
- * The account a target speaks through: the one it names; else the only one
145618
- * there is; else the one whose Echoes include the target's Echo.
145619
- */
145620
145402
  function brokerFor(config) {
145621
- const all = ports.brokers();
145622
- const named = readString(config, "brokerId");
145623
- if (named.length > 0) return all.find((broker) => broker.id === named);
145624
- if (all.length === 1) return all[0];
145625
- const echoId = readString(config, "echoId");
145626
- const owners = all.filter((broker) => broker.devices.some((device) => device.id === echoId));
145627
- return owners.length === 1 ? owners[0] : void 0;
145403
+ return pickBroker(ports.brokers(), config);
145628
145404
  }
145629
145405
  async function deliver(target, notification) {
145630
145406
  const speech = speechFromNotification(notification);
@@ -145826,14 +145602,6 @@ function createAlexaNotificationProvider(ports) {
145826
145602
  async listTargetKinds() {
145827
145603
  return [alexaTargetKind()];
145828
145604
  },
145829
- async listTargets() {
145830
- return [...await ports.loadTargets()];
145831
- },
145832
- async getTarget(input) {
145833
- const found = (await ports.loadTargets()).find((target) => target.id === input.targetId);
145834
- if (found === void 0) throw new Error(`target not found: ${input.targetId}`);
145835
- return found;
145836
- },
145837
145605
  async discoverTargets(input) {
145838
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 } });
145839
145607
  const named = readString(input.config ?? {}, "brokerId");
@@ -145894,33 +145662,6 @@ function createAlexaNotificationProvider(ports) {
145894
145662
  format: "text",
145895
145663
  priority: 3
145896
145664
  });
145897
- },
145898
- async upsertTarget(input) {
145899
- const owner = brokerFor(input.target.config);
145900
- if (readString(input.target.config, "echoId").length === 0) throw new Error("Pick the Echo that should speak");
145901
- if (owner === void 0) throw new Error("Pick the Amazon account this Echo belongs to");
145902
- const pinned = readString(input.target.config, "brokerId").length === 0 ? {
145903
- ...input.target.config,
145904
- brokerId: owner.id
145905
- } : input.target.config;
145906
- const target = {
145907
- ...input.target,
145908
- config: pinned,
145909
- addonId: ALEXA_SPEAK_ADDON_ID,
145910
- kind: ALEXA_SPEAK_KIND
145911
- };
145912
- await ports.putTarget(target);
145913
- return target;
145914
- },
145915
- async deleteTarget(input) {
145916
- await ports.removeTarget(input.targetId);
145917
- },
145918
- async setTargetEnabled(input) {
145919
- const found = (await ports.loadTargets()).find((row) => row.id === input.targetId);
145920
- if (found !== void 0) await ports.putTarget({
145921
- ...found,
145922
- enabled: input.enabled
145923
- });
145924
145665
  }
145925
145666
  };
145926
145667
  }
@@ -146011,6 +145752,14 @@ function echoTargetProbe(speak) {
146011
145752
  });
146012
145753
  };
146013
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
+ }
146014
145763
  process.platform, process.env["SystemRoot"];
146015
145764
  /**
146016
145765
  * Files only their owner may read: the one place a secret-bearing file is
@@ -146851,8 +146600,6 @@ var AlexaSpeakController = class {
146851
146600
  now: this.now,
146852
146601
  logger: host.logger,
146853
146602
  loadTargets: () => host.readTargets(),
146854
- putTarget: (target) => host.putTarget(target),
146855
- removeTarget: (targetId) => host.removeTarget(targetId),
146856
146603
  speak: (command) => this.utter(command.brokerId, command.mode, command.targets, command.text)
146857
146604
  });
146858
146605
  }
@@ -146882,6 +146629,7 @@ var AlexaSpeakController = class {
146882
146629
  echoFor(brokerId) {
146883
146630
  return this.echoes.get(brokerId) ?? null;
146884
146631
  }
146632
+ /** The accounts right now, as the provider and the save-time rule see them. */
146885
146633
  brokerViews() {
146886
146634
  return this.host.listBrokers().map((ref) => {
146887
146635
  const echo = this.echoes.get(ref.id);
@@ -147429,8 +147177,6 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
147429
147177
  const speak = new AlexaSpeakController({
147430
147178
  logger: this.ctx.logger.child("speak"),
147431
147179
  readTargets: () => echoStore.list(),
147432
- putTarget: (target) => echoStore.upsert(target),
147433
- removeTarget: (targetId) => echoStore.delete(targetId),
147434
147180
  listBrokers: () => this.activeBrokers.map((entry) => ({
147435
147181
  id: entry.id,
147436
147182
  name: entry.name,
@@ -147532,6 +147278,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
147532
147278
  kinds: alexaNotifyRecordKinds,
147533
147279
  mintId: () => randomUUID(),
147534
147280
  probe: echoTargetProbe(() => this.speak),
147281
+ validate: echoTargetValidator(() => this.speak?.brokerViews() ?? null),
147535
147282
  canProbe: () => true
147536
147283
  })
147537
147284
  ], {