@camstack/addon-import-alexa 0.2.166 → 0.2.167

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
@@ -5247,6 +5247,14 @@ function object(shape, params) {
5247
5247
  ...normalizeParams(params)
5248
5248
  });
5249
5249
  }
5250
+ function strictObject(shape, params) {
5251
+ return new ZodObject({
5252
+ type: "object",
5253
+ shape,
5254
+ catchall: never(),
5255
+ ...normalizeParams(params)
5256
+ });
5257
+ }
5250
5258
  function looseObject(shape, params) {
5251
5259
  return new ZodObject({
5252
5260
  type: "object",
@@ -5626,7 +5634,7 @@ var ZodIssueCode = {
5626
5634
  var ZodFirstPartyTypeKind;
5627
5635
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5628
5636
  //#endregion
5629
- //#region ../types/dist/sleep-C_u6cEAJ.mjs
5637
+ //#region ../types/dist/sleep-6eG9C8Xl.mjs
5630
5638
  /**
5631
5639
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5632
5640
  * window to float samples (D455).
@@ -8861,9 +8869,76 @@ function optionalMethod(input, output, options) {
8861
8869
  function event(data) {
8862
8870
  return { data };
8863
8871
  }
8872
+ /**
8873
+ * `addon-pages-source` — collection cap exposing per-provider raw page
8874
+ * declarations. Every addon that contributes a UI page registers a
8875
+ * provider here. The hub-side singleton aggregator (`addon-pages` cap,
8876
+ * see `addon-pages.cap.ts`) walks this collection, stamps versioned
8877
+ * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
8878
+ * that admin-ui consumes.
8879
+ *
8880
+ * The split exists because the public listing has a different output
8881
+ * shape than the per-provider raw declarations, and we want both ends
8882
+ * to flow through codegen instead of relying on a hand-written wrapper.
8883
+ */
8884
+ var AddonPageDeclarationSchema = object({
8885
+ id: string(),
8886
+ label: string(),
8887
+ icon: string(),
8888
+ path: string(),
8889
+ /**
8890
+ * Module Federation remote name — must match the `name` field on the
8891
+ * page addon's `federation()` plugin config. Used by admin-ui's
8892
+ * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
8893
+ * Conventionally `addon_<id>_page` (snake_case; MF names cannot
8894
+ * contain hyphens).
8895
+ */
8896
+ remoteName: string(),
8897
+ /**
8898
+ * Bundle filename inside the addon's `dist/` dir served at
8899
+ * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
8900
+ * is always `'remoteEntry.js'`; the value is kept on the metadata so
8901
+ * the static-file route can compute an mtime-based cache-buster URL
8902
+ * without a separate filesystem stat.
8903
+ */
8904
+ bundle: string(),
8905
+ /**
8906
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
8907
+ * `'cluster'`, `'administration'` — the page renders inside that group.
8908
+ * Any OTHER string creates (or joins) a custom section rendered after
8909
+ * the built-in groups; its label comes from `sectionLabel` (first
8910
+ * declaration wins), falling back to the id. Absent → the legacy
8911
+ * "Addon Pages" group.
8912
+ */
8913
+ section: string().optional(),
8914
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
8915
+ sectionLabel: string().optional()
8916
+ });
8917
+ method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
8864
8918
  var StaticDirOutputSchema$1 = object({ staticDir: string() });
8865
8919
  var VersionOutputSchema$1 = object({ version: string() });
8866
8920
  method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
8921
+ /**
8922
+ * The pipeline step-input shape and the run-result bridge, in a module that
8923
+ * imports nothing but zod and types.
8924
+ *
8925
+ * WHY A SEPARATE FILE (addon footprint, D28): both were declared in
8926
+ * `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
8927
+ * graph. An addon that only needs these two (the benchmark's `runPipelineTest`
8928
+ * action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
8929
+ * them, so every existing import keeps working; addon server code takes them
8930
+ * from `@camstack/types/addon`.
8931
+ */
8932
+ var PipelineStepInputSchema = lazy(() => object({
8933
+ addonId: string(),
8934
+ modelId: string().optional(),
8935
+ enabled: boolean().default(true),
8936
+ children: array(PipelineStepInputSchema).optional(),
8937
+ settings: record(string(), unknown()).optional(),
8938
+ jumpDeviceKey: string().optional()
8939
+ }));
8940
+ /** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
8941
+ var PipelineRunResultBridge = custom();
8867
8942
  var StaticDirOutputSchema = object({ staticDir: string() });
8868
8943
  var VersionOutputSchema = object({ version: string() });
8869
8944
  method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
@@ -9059,6 +9134,7 @@ var CAP_NAMES = Object.freeze([
9059
9134
  "osd",
9060
9135
  "osd-manager",
9061
9136
  "pet-feeder",
9137
+ "pet-fountain",
9062
9138
  "pipeline-analytics",
9063
9139
  "pipeline-executor",
9064
9140
  "pipeline-orchestrator",
@@ -10338,6 +10414,56 @@ Object.freeze({
10338
10414
  "valueType": "number"
10339
10415
  }
10340
10416
  ],
10417
+ "pet-fountain": [
10418
+ {
10419
+ "path": "waterLevel",
10420
+ "valueType": "number"
10421
+ },
10422
+ {
10423
+ "path": "waterVolume",
10424
+ "valueType": "number"
10425
+ },
10426
+ {
10427
+ "path": "empty",
10428
+ "valueType": "boolean"
10429
+ },
10430
+ {
10431
+ "path": "pumpRunning",
10432
+ "valueType": "boolean"
10433
+ },
10434
+ {
10435
+ "path": "pumpIntervalEnabled",
10436
+ "valueType": "boolean"
10437
+ },
10438
+ {
10439
+ "path": "pumpOnTime",
10440
+ "valueType": "number"
10441
+ },
10442
+ {
10443
+ "path": "pumpOffTime",
10444
+ "valueType": "number"
10445
+ },
10446
+ {
10447
+ "path": "lastDrink",
10448
+ "valueType": "number"
10449
+ },
10450
+ {
10451
+ "path": "currentHourDrink",
10452
+ "valueType": "number"
10453
+ },
10454
+ {
10455
+ "path": "last24hDrink",
10456
+ "valueType": "number"
10457
+ },
10458
+ {
10459
+ "path": "todayDrink",
10460
+ "valueType": "number"
10461
+ },
10462
+ {
10463
+ "path": "error",
10464
+ "valueType": "string"
10465
+ }
10466
+ ],
10341
10467
  "power-meter": [
10342
10468
  {
10343
10469
  "path": "power",
@@ -10875,7 +11001,7 @@ object({
10875
11001
  params: record(string(), union([string(), number()])).optional()
10876
11002
  });
10877
11003
  //#endregion
10878
- //#region ../types/dist/err-msg-BCdlR2Li.mjs
11004
+ //#region ../types/dist/err-msg-DWXWc0Di.mjs
10879
11005
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10880
11006
  DeviceType["Camera"] = "camera";
10881
11007
  DeviceType["Hub"] = "hub";
@@ -10967,6 +11093,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10967
11093
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
10968
11094
  * integrations sharing the same food/desiccant/hopper surface. */
10969
11095
  DeviceType["PetFeeder"] = "pet-feeder";
11096
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11097
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11098
+ * of a container and is BUILT as a composed device (D967). */
11099
+ DeviceType["PetFountain"] = "pet-fountain";
10970
11100
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10971
11101
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10972
11102
  * Sources: native Bosch Home Connect, reusable by similar integrations.
@@ -11237,7 +11367,7 @@ function errMsg(err) {
11237
11367
  return String(err);
11238
11368
  }
11239
11369
  //#endregion
11240
- //#region ../types/dist/composition-bIBW5_13.mjs
11370
+ //#region ../types/dist/composition-kSuOifuG.mjs
11241
11371
  /**
11242
11372
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11243
11373
  *
@@ -11285,11 +11415,11 @@ function errMsg(err) {
11285
11415
  */
11286
11416
  /** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
11287
11417
  var FFMPEG_RELEASE = "8.1.2-5";
11288
- var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
11418
+ var BASE$1 = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
11289
11419
  function portable(target) {
11290
- return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
11420
+ return `${BASE$1}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
11291
11421
  }
11292
- portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64"), `${BASE}${FFMPEG_RELEASE}`;
11422
+ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64"), `${BASE$1}${FFMPEG_RELEASE}`;
11293
11423
  /**
11294
11424
  * THE table "which boolean of this device type counts as HIGH", and the
11295
11425
  * pure evaluator of its EDGE.
@@ -12322,12 +12452,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12322
12452
  ])
12323
12453
  })
12324
12454
  ]);
12325
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
12455
+ var forwardArgUnion = union([
12456
+ string(),
12457
+ number(),
12458
+ boolean(),
12459
+ strictObject({ ["$input"]: string().min(1) }),
12460
+ record(string().min(1), lazy(() => forwardArgUnion))
12461
+ ]);
12462
+ var CompositionForwardArgSchema = forwardArgUnion;
12463
+ /**
12464
+ * `forward` (D659, amended by D967): one method of a BUILT capability is one
12465
+ * call of `cap.method` on `source`. Without `args` the built method's input
12466
+ * passes through minus `deviceId`; with `args` the template IS the source's
12467
+ * whole input. `deviceId` is always the source's own, never an argument.
12468
+ */
12469
+ var CompositionForwardCommandSchema = object({
12326
12470
  kind: literal("forward"),
12327
12471
  source: CompositionSourceRefSchema,
12328
12472
  cap: string().min(1),
12329
- method: string().min(1)
12330
- }), CompositionCodeSourceSchema]);
12473
+ method: string().min(1),
12474
+ args: record(string().min(1), CompositionForwardArgSchema).optional()
12475
+ });
12476
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
12331
12477
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
12332
12478
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12333
12479
  /**
@@ -12337,7 +12483,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12337
12483
  */
12338
12484
  var CompositionItemEntrySchema = object({
12339
12485
  label: string().min(1).max(80),
12340
- fields: record(string().min(1), CompositionFieldSourceSchema)
12486
+ fields: record(string().min(1), CompositionFieldSourceSchema),
12487
+ /**
12488
+ * What the cap's `reset` does for THIS item (consumables). Present = the item
12489
+ * is resettable and a `reset` call naming its key forwards here. The item key
12490
+ * selects the command, so it is never an argument: there is no pass-through.
12491
+ */
12492
+ reset: CompositionForwardCommandSchema.optional()
12341
12493
  });
12342
12494
  var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12343
12495
  kind: literal("fields"),
@@ -12349,7 +12501,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12349
12501
  * owns the WHOLE array field: the composed value holds exactly these items.
12350
12502
  */
12351
12503
  items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
12352
- /** RESERVED for slice 3; refused by the validator when non-empty. */
12504
+ /** Method name → what serves it. Only a BUILT capability takes commands (D967). */
12353
12505
  commands: record(string().min(1), CompositionCommandTargetSchema).optional()
12354
12506
  }), object({
12355
12507
  kind: literal("passthrough"),
@@ -16406,6 +16558,449 @@ object({
16406
16558
  lastError: string().nullable()
16407
16559
  });
16408
16560
  /**
16561
+ * notification-output — canonical, capability-gated notification delivery.
16562
+ *
16563
+ * Apprise-derived model (see
16564
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16565
+ * callers emit ONE canonical `Notification`; each provider declares a
16566
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16567
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16568
+ * message to what the kind supports — callers never special-case a service.
16569
+ *
16570
+ * DESIGN DECISIONS (locked):
16571
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16572
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16573
+ * cap. Rationale: the admin UI needs one uniform surface across the
16574
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16575
+ * alternative would fork the UI per addon and cannot host the
16576
+ * discovery→adopt flow.
16577
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16578
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16579
+ * registered provider (notifiers addon + HA addon) so one catalog is
16580
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16581
+ * `addonId` the generated collection router extracts from the call input.
16582
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16583
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16584
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16585
+ * base64 fallback needed.
16586
+ *
16587
+ * TODO (deferred, closed-set change — separate decision): add
16588
+ * `providerKind: 'notify'` so notification providers surface on the unified
16589
+ * admin "Integrations" page.
16590
+ */
16591
+ /**
16592
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16593
+ * adapter picks what it supports and the degrade engine filters the rest.
16594
+ */
16595
+ var AttachmentMediaTypeSchema = _enum([
16596
+ "image",
16597
+ "video",
16598
+ "gif",
16599
+ "audio",
16600
+ "icon"
16601
+ ]);
16602
+ /**
16603
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16604
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16605
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16606
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16607
+ */
16608
+ var AttachmentSchema = object({
16609
+ mediaType: AttachmentMediaTypeSchema,
16610
+ url: string().optional(),
16611
+ bytes: _instanceof(Uint8Array).optional(),
16612
+ mime: string().optional(),
16613
+ name: string().optional()
16614
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16615
+ var NotificationFormatSchema = _enum([
16616
+ "text",
16617
+ "markdown",
16618
+ "html"
16619
+ ]);
16620
+ /**
16621
+ * The CLOSED icon vocabulary an action button may use.
16622
+ *
16623
+ * A closed set, not a free string, and that is the whole point: an arbitrary
16624
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
16625
+ * notices — the same class of gap as a zone vocabulary nothing produced
16626
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
16627
+ * declares `actionIcons: false` and the degrade engine strips the field.
16628
+ *
16629
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
16630
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
16631
+ * differently.
16632
+ *
16633
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
16634
+ *
16635
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
16636
+ * the whole set onto its own renderer's vocabulary through a
16637
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
16638
+ * fallback, so adding a member here fails every adapter's build until someone
16639
+ * decides its glyph, which is the only place that decision can be made
16640
+ * honestly.
16641
+ *
16642
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
16643
+ * `disarm` straight through; iOS feeds that string to
16644
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
16645
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
16646
+ * mapping, and "the field is documented" is not "the value renders".
16647
+ *
16648
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
16649
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
16650
+ * so an addon that emits a token the running hub does not know does not lose an
16651
+ * icon — its whole `send` fails Zod validation and the notification never
16652
+ * arrives. Never emit a new token from an addon before the train carrying it.
16653
+ */
16654
+ var NotificationActionIconSchema = _enum([
16655
+ "acknowledge",
16656
+ "dismiss",
16657
+ "silence",
16658
+ "snooze",
16659
+ "view",
16660
+ "play",
16661
+ "open",
16662
+ "close",
16663
+ "lock",
16664
+ "unlock",
16665
+ "arm",
16666
+ "arm-home",
16667
+ "arm-away",
16668
+ "arm-night",
16669
+ "disarm",
16670
+ "light",
16671
+ "alert",
16672
+ "camera"
16673
+ ]);
16674
+ /** A single tap-through action button. */
16675
+ var NotificationActionSchema = object({
16676
+ id: string(),
16677
+ label: string(),
16678
+ url: string().optional(),
16679
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
16680
+ icon: NotificationActionIconSchema.optional(),
16681
+ /**
16682
+ * Renders in a warning style where the notifier supports it.
16683
+ *
16684
+ * A HINT, never a gate. The callback's authority is its token and nothing
16685
+ * else — see `notification-center/action-token.ts` for what that does and
16686
+ * does not buy.
16687
+ */
16688
+ destructive: boolean().optional(),
16689
+ /**
16690
+ * How the tap should REACH the url.
16691
+ *
16692
+ * `navigate` (absent, and every button authored before this field) opens it:
16693
+ * the phone leaves the notification and shows whatever the callback returns.
16694
+ * That is right for a button whose answer the operator wants to read.
16695
+ *
16696
+ * `background` fires it as a POST and stays put. It exists for the buttons
16697
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
16698
+ * an answer to the notification, and being thrown into a browser tab to
16699
+ * confirm it costs more attention than the notification did. A backend that
16700
+ * cannot do a background call renders it as an ordinary link (the adapters
16701
+ * fall back rather than dropping the button), so this is a preference, never
16702
+ * a requirement.
16703
+ */
16704
+ mode: _enum(["navigate", "background"]).optional()
16705
+ });
16706
+ /** A delivery parameter value: what a notifier declared it takes. */
16707
+ var DeliveryParamValueSchema = union([
16708
+ string(),
16709
+ number(),
16710
+ boolean()
16711
+ ]);
16712
+ /**
16713
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
16714
+ * addresses a target of that kind may set it; a kind that declares nothing is
16715
+ * never handed one. The admin UI derives its control from this descriptor —
16716
+ * no editor knows a service by name.
16717
+ *
16718
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
16719
+ * run on the addon cap path).
16720
+ */
16721
+ var DeliveryParamSchema = object({
16722
+ key: string().min(1),
16723
+ label: string(),
16724
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
16725
+ labelKey: string().optional(),
16726
+ type: _enum([
16727
+ "enum",
16728
+ "number",
16729
+ "boolean",
16730
+ "string"
16731
+ ]),
16732
+ /** `enum` only: the closed set of accepted values. */
16733
+ options: array(object({
16734
+ value: string(),
16735
+ label: string()
16736
+ })).optional(),
16737
+ /** `number` only. */
16738
+ min: number().optional(),
16739
+ max: number().optional(),
16740
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
16741
+ default: DeliveryParamValueSchema.optional()
16742
+ });
16743
+ /**
16744
+ * The canonical notification. `body` is the only hard field (Apprise model).
16745
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16746
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16747
+ * the adapter maps this ordinal onto its native level. `level?` is an
16748
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16749
+ * `priority` for that one target.
16750
+ */
16751
+ var NotificationSchema = object({
16752
+ body: string(),
16753
+ title: string().optional(),
16754
+ format: NotificationFormatSchema.default("text"),
16755
+ priority: number().int().min(1).max(5).default(3),
16756
+ level: string().optional(),
16757
+ attachments: array(AttachmentSchema).optional(),
16758
+ clickUrl: string().optional(),
16759
+ actions: array(NotificationActionSchema).optional(),
16760
+ sound: string().optional(),
16761
+ ttl: number().optional(),
16762
+ tag: string().optional(),
16763
+ deviceId: number().optional(),
16764
+ eventId: string().optional(),
16765
+ metadata: record(string(), unknown()).optional(),
16766
+ /**
16767
+ * Values for the parameters the target's kind DECLARES in
16768
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
16769
+ * declaration by the sender; a notifier reads only the keys it declared.
16770
+ */
16771
+ delivery: record(string(), DeliveryParamValueSchema).optional()
16772
+ });
16773
+ /** One declared native severity/priority level for a kind. */
16774
+ var TargetKindLevelSchema = object({
16775
+ id: string(),
16776
+ label: string(),
16777
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16778
+ ordinal: number().int().min(1).max(5).nullable(),
16779
+ flags: object({
16780
+ critical: boolean().optional(),
16781
+ silent: boolean().optional(),
16782
+ noPush: boolean().optional()
16783
+ }).optional(),
16784
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16785
+ requires: array(string()).optional(),
16786
+ description: string().optional()
16787
+ });
16788
+ /** The full capability block consulted before dispatch. */
16789
+ var TargetKindCapsSchema = object({
16790
+ attachments: object({
16791
+ mediaTypes: array(AttachmentMediaTypeSchema),
16792
+ mode: _enum([
16793
+ "url",
16794
+ "bytes",
16795
+ "both"
16796
+ ]),
16797
+ max: number().int().nonnegative(),
16798
+ maxBytes: number().int().positive().optional()
16799
+ }),
16800
+ /** Max action buttons (0 = none). */
16801
+ actions: number().int().nonnegative(),
16802
+ /**
16803
+ * Whether this kind renders a per-action ICON.
16804
+ *
16805
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
16806
+ * run on the addon cap path — three production failures in one day taught
16807
+ * this repo that once. Absent is read as false by the degrade engine, which
16808
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
16809
+ * assumed and dropped costs the operator's trust in the field.
16810
+ */
16811
+ actionIcons: boolean().optional(),
16812
+ levels: array(TargetKindLevelSchema),
16813
+ format: array(NotificationFormatSchema),
16814
+ clickUrl: boolean(),
16815
+ sound: boolean(),
16816
+ ttl: boolean(),
16817
+ bodyMaxLen: number().int().positive()
16818
+ });
16819
+ /**
16820
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16821
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16822
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16823
+ * the union is large and not meant for runtime validation here; the exported
16824
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16825
+ */
16826
+ var ConfigSchemaPassthrough$3 = unknown();
16827
+ var TargetKindSchema = object({
16828
+ kind: string(),
16829
+ label: string(),
16830
+ icon: string(),
16831
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16832
+ addonId: string(),
16833
+ /**
16834
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
16835
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
16836
+ * when the addon bundles no icon for that kind — the client then falls back
16837
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
16838
+ *
16839
+ * Root-relative on purpose: it resolves against whatever origin serves a web
16840
+ * client, and a native client joins it onto its own hub base.
16841
+ *
16842
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
16843
+ * field that survived only because the runtime cap-router forwards provider
16844
+ * output verbatim — so every consumer had to re-declare it by hand to stop
16845
+ * its own Zod parse from stripping it, and the whole arrangement would have
16846
+ * broken silently the moment output validation was tightened anywhere.
16847
+ */
16848
+ iconUrl: string().optional(),
16849
+ /**
16850
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
16851
+ *
16852
+ * The server knows this and therefore says it, because the client cannot
16853
+ * safely guess: a React-Native client renders SVG and raster through two
16854
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
16855
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
16856
+ * placeholder glyph for every vector icon while the web build looked fine.
16857
+ *
16858
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
16859
+ * not been updated — a client that cannot determine the type should prefer
16860
+ * its raster path, which is the safe default for an unknown image.
16861
+ */
16862
+ iconMediaType: string().optional(),
16863
+ configSchema: ConfigSchemaPassthrough$3,
16864
+ /**
16865
+ * The config keys that decide WHERE this kind's secrets are sent (server,
16866
+ * base URL, instance, chat, recipient). A save that changes any of them
16867
+ * while leaving a secret redacted is refused by the provider (D634), so an
16868
+ * editor uses this to ask for the secret on exactly those edits instead of
16869
+ * on every edit.
16870
+ *
16871
+ * Optional: absent from a provider that holds no secret (the Home Assistant
16872
+ * kinds) and from a provider older than this field — an editor that finds
16873
+ * it absent must assume ANY edit may need the secret.
16874
+ */
16875
+ destinationKeys: array(string()).optional(),
16876
+ supportsDiscovery: boolean(),
16877
+ /**
16878
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
16879
+ * Absent = none; the rule editor then offers nothing.
16880
+ */
16881
+ deliveryParams: array(DeliveryParamSchema).optional(),
16882
+ caps: TargetKindCapsSchema
16883
+ });
16884
+ /**
16885
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16886
+ * (return a presence marker only) when serving `listTargets` — never
16887
+ * round-trip a stored secret to the UI.
16888
+ */
16889
+ var TargetSchema = object({
16890
+ id: string(),
16891
+ name: string(),
16892
+ kind: string(),
16893
+ addonId: string(),
16894
+ enabled: boolean(),
16895
+ config: record(string(), unknown())
16896
+ });
16897
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16898
+ var DiscoveredTargetSchema = object({
16899
+ kind: string(),
16900
+ suggestedName: string(),
16901
+ config: record(string(), unknown())
16902
+ });
16903
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
16904
+ var RenderedAsSchema = object({
16905
+ level: string(),
16906
+ format: NotificationFormatSchema,
16907
+ attachmentsSent: number().int().nonnegative(),
16908
+ actionsSent: number().int().nonnegative(),
16909
+ truncated: boolean(),
16910
+ dropped: array(string())
16911
+ });
16912
+ var SendResultSchema = object({
16913
+ success: boolean(),
16914
+ error: string().optional(),
16915
+ renderedAs: RenderedAsSchema.optional()
16916
+ });
16917
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16918
+ var TestResultSchema = SendResultSchema;
16919
+ /**
16920
+ * Pre-save credential probe against a partial (unsaved) target config.
16921
+ * `resolvedUrl` never includes secrets (token stripped from path).
16922
+ */
16923
+ var ProbeTargetConfigResultSchema = object({
16924
+ ok: boolean(),
16925
+ resolvedUrl: string(),
16926
+ httpStatus: number().int().optional(),
16927
+ stateInstance: string().optional(),
16928
+ error: string().optional()
16929
+ });
16930
+ var notificationOutputCapability = {
16931
+ name: "notification-output",
16932
+ scope: "system",
16933
+ mode: "collection",
16934
+ methods: {
16935
+ listTargetKinds: method(object({}), array(TargetKindSchema)),
16936
+ listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
16937
+ /**
16938
+ * One target WITH secrets — admin edit / duplicate hydration only.
16939
+ * `listTargets` stays redacted; the editor loads this so password fields
16940
+ * are filled (masked in the input) and a save can round-trip the token
16941
+ * when a destination changes (Duplicate with a new chatId, etc.).
16942
+ */
16943
+ getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
16944
+ discoverTargets: method(object({
16945
+ kind: string(),
16946
+ config: record(string(), unknown()).optional(),
16947
+ /**
16948
+ * When editing / duplicating, the blank password field never round-trips
16949
+ * the stored token. Pass the source target id so the provider can restore
16950
+ * secrets server-side before dialling (same merge as upsert).
16951
+ */
16952
+ targetId: string().optional()
16953
+ }), array(DiscoveredTargetSchema), { auth: "admin" }),
16954
+ probeTargetConfig: method(object({
16955
+ kind: string(),
16956
+ config: record(string(), unknown()),
16957
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
16958
+ targetId: string().optional()
16959
+ }), ProbeTargetConfigResultSchema, {
16960
+ kind: "mutation",
16961
+ auth: "admin"
16962
+ }),
16963
+ send: method(object({
16964
+ targetId: string(),
16965
+ notification: NotificationSchema
16966
+ }), SendResultSchema, {
16967
+ kind: "mutation",
16968
+ auth: "admin"
16969
+ }),
16970
+ testTarget: method(object({
16971
+ targetId: string(),
16972
+ sample: NotificationSchema.optional()
16973
+ }), TestResultSchema, {
16974
+ kind: "mutation",
16975
+ auth: "admin"
16976
+ }),
16977
+ upsertTarget: method(object({
16978
+ target: TargetSchema,
16979
+ /**
16980
+ * Duplicate flow: create a NEW target id while copying secrets from an
16981
+ * existing row. The UI never holds the stored token; blank / redacted
16982
+ * password fields are restored from this source (D634 still applies if
16983
+ * destinations diverge without a re-typed secret).
16984
+ */
16985
+ cloneSecretsFrom: string().optional()
16986
+ }), TargetSchema, {
16987
+ kind: "mutation",
16988
+ auth: "admin"
16989
+ }),
16990
+ deleteTarget: method(object({ targetId: string() }), _void(), {
16991
+ kind: "mutation",
16992
+ auth: "admin"
16993
+ }),
16994
+ setTargetEnabled: method(object({
16995
+ targetId: string(),
16996
+ enabled: boolean()
16997
+ }), _void(), {
16998
+ kind: "mutation",
16999
+ auth: "admin"
17000
+ })
17001
+ }
17002
+ };
17003
+ /**
16409
17004
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
16410
17005
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
16411
17006
  *
@@ -16437,52 +17032,6 @@ var AddonPageInfoSchema = object({
16437
17032
  bundleUrl: string()
16438
17033
  });
16439
17034
  method(_void(), array(AddonPageInfoSchema).readonly());
16440
- /**
16441
- * `addon-pages-source` — collection cap exposing per-provider raw page
16442
- * declarations. Every addon that contributes a UI page registers a
16443
- * provider here. The hub-side singleton aggregator (`addon-pages` cap,
16444
- * see `addon-pages.cap.ts`) walks this collection, stamps versioned
16445
- * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
16446
- * that admin-ui consumes.
16447
- *
16448
- * The split exists because the public listing has a different output
16449
- * shape than the per-provider raw declarations, and we want both ends
16450
- * to flow through codegen instead of relying on a hand-written wrapper.
16451
- */
16452
- var AddonPageDeclarationSchema = object({
16453
- id: string(),
16454
- label: string(),
16455
- icon: string(),
16456
- path: string(),
16457
- /**
16458
- * Module Federation remote name — must match the `name` field on the
16459
- * page addon's `federation()` plugin config. Used by admin-ui's
16460
- * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
16461
- * Conventionally `addon_<id>_page` (snake_case; MF names cannot
16462
- * contain hyphens).
16463
- */
16464
- remoteName: string(),
16465
- /**
16466
- * Bundle filename inside the addon's `dist/` dir served at
16467
- * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
16468
- * is always `'remoteEntry.js'`; the value is kept on the metadata so
16469
- * the static-file route can compute an mtime-based cache-buster URL
16470
- * without a separate filesystem stat.
16471
- */
16472
- bundle: string(),
16473
- /**
16474
- * Sidebar section this page docks into. Well-known ids: `'detection'`,
16475
- * `'cluster'`, `'administration'` — the page renders inside that group.
16476
- * Any OTHER string creates (or joins) a custom section rendered after
16477
- * the built-in groups; its label comes from `sectionLabel` (first
16478
- * declaration wins), falling back to the id. Absent → the legacy
16479
- * "Addon Pages" group.
16480
- */
16481
- section: string().optional(),
16482
- /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
16483
- sectionLabel: string().optional()
16484
- });
16485
- method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
16486
17035
  var AddonHttpRouteSchema = object({
16487
17036
  method: _enum([
16488
17037
  "GET",
@@ -19199,7 +19748,11 @@ var CompositionProblemSchema = object({
19199
19748
  "mixed-quantity",
19200
19749
  "helper-mismatch",
19201
19750
  "member-constraint",
19202
- "retention"
19751
+ "retention",
19752
+ "command-method-unknown",
19753
+ "command-source-unknown",
19754
+ "command-input-unknown",
19755
+ "command-input-missing"
19203
19756
  ]),
19204
19757
  path: string(),
19205
19758
  message: string()
@@ -19238,7 +19791,9 @@ var CompositionFieldRoleSchema = _enum([
19238
19791
  var CompositionFieldItemSchema = object({
19239
19792
  arrayPath: string(),
19240
19793
  key: string(),
19241
- label: string()
19794
+ label: string(),
19795
+ /** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
19796
+ resettable: boolean().optional()
19242
19797
  });
19243
19798
  var CompositionFieldPlanSchema = object({
19244
19799
  cap: string(),
@@ -19443,7 +19998,7 @@ Object.freeze({
19443
19998
  * One catalog row per base reading a group helper can build (D944 §3).
19444
19999
  * Data, not a form default written into the block.
19445
20000
  */
19446
- var NOW$2 = {
20001
+ var NOW$3 = {
19447
20002
  fieldPath: "lastFetchedAt",
19448
20003
  expr: "now"
19449
20004
  };
@@ -19495,7 +20050,7 @@ var GROUP_TEMPLATES = [
19495
20050
  quantity: "temperature",
19496
20051
  defaultAggregate: "mean",
19497
20052
  empty: "null",
19498
- companions: [NOW$2],
20053
+ companions: [NOW$3],
19499
20054
  alsoFrom: {
19500
20055
  deviceTypes: CLIMATE_DEVICES,
19501
20056
  accept: {
@@ -19513,7 +20068,7 @@ var GROUP_TEMPLATES = [
19513
20068
  quantity: "humidity",
19514
20069
  defaultAggregate: "mean",
19515
20070
  empty: "null",
19516
- companions: [NOW$2],
20071
+ companions: [NOW$3],
19517
20072
  alsoFrom: {
19518
20073
  deviceTypes: CLIMATE_DEVICES,
19519
20074
  accept: {
@@ -19531,7 +20086,7 @@ var GROUP_TEMPLATES = [
19531
20086
  quantity: "pressure",
19532
20087
  defaultAggregate: "mean",
19533
20088
  empty: "null",
19534
- companions: [NOW$2]
20089
+ companions: [NOW$3]
19535
20090
  }),
19536
20091
  numeric({
19537
20092
  id: "illuminance",
@@ -19542,7 +20097,7 @@ var GROUP_TEMPLATES = [
19542
20097
  quantity: "illuminance",
19543
20098
  defaultAggregate: "mean",
19544
20099
  empty: "null",
19545
- companions: [NOW$2]
20100
+ companions: [NOW$3]
19546
20101
  }),
19547
20102
  numeric({
19548
20103
  id: "power",
@@ -19553,7 +20108,7 @@ var GROUP_TEMPLATES = [
19553
20108
  quantity: "power",
19554
20109
  defaultAggregate: "sum",
19555
20110
  empty: "omit",
19556
- companions: [NOW$2]
20111
+ companions: [NOW$3]
19557
20112
  }),
19558
20113
  numeric({
19559
20114
  id: "energy",
@@ -19564,7 +20119,7 @@ var GROUP_TEMPLATES = [
19564
20119
  quantity: "energy",
19565
20120
  defaultAggregate: "sum",
19566
20121
  empty: "omit",
19567
- companions: [NOW$2]
20122
+ companions: [NOW$3]
19568
20123
  }),
19569
20124
  numeric({
19570
20125
  id: "battery",
@@ -19599,7 +20154,7 @@ var GROUP_TEMPLATES = [
19599
20154
  quantity: "device",
19600
20155
  defaultAggregate: "mean",
19601
20156
  empty: "null",
19602
- companions: [NOW$2]
20157
+ companions: [NOW$3]
19603
20158
  }),
19604
20159
  binary({
19605
20160
  id: "motion",
@@ -22073,12 +22628,48 @@ var ExposeInputSchema = object({
22073
22628
  capabilities: array(string()).optional()
22074
22629
  });
22075
22630
  var UnexposeInputSchema = object({ deviceId: string() });
22631
+ /**
22632
+ * What an exporter may expose besides devices (D968). An action and a
22633
+ * notification target are LIST ENTRIES, never devices (D954): they enter an
22634
+ * exporter's own selection, not the device registry.
22635
+ */
22636
+ var ExportEntityKindSchema = _enum([
22637
+ "device",
22638
+ "action",
22639
+ "notifier"
22640
+ ]);
22641
+ var ExportEntityRefSchema = discriminatedUnion("kind", [
22642
+ object({
22643
+ kind: literal("device"),
22644
+ deviceId: string()
22645
+ }),
22646
+ object({
22647
+ kind: literal("action"),
22648
+ actionId: string().min(1)
22649
+ }),
22650
+ object({
22651
+ kind: literal("notifier"),
22652
+ targetId: string().min(1)
22653
+ })
22654
+ ]);
22655
+ var ExposedEntitySchema = object({
22656
+ entity: ExportEntityRefSchema,
22657
+ exposedAs: string().optional()
22658
+ });
22659
+ var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
22660
+ var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
22076
22661
  method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
22077
22662
  kind: "mutation",
22078
22663
  auth: "admin"
22079
22664
  }), method(UnexposeInputSchema, _void(), {
22080
22665
  kind: "mutation",
22081
22666
  auth: "admin"
22667
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
22668
+ kind: "mutation",
22669
+ auth: "admin"
22670
+ }), optionalMethod(UnexposeEntityInputSchema, _void(), {
22671
+ kind: "mutation",
22672
+ auth: "admin"
22082
22673
  });
22083
22674
  /** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
22084
22675
  var ICON_CATEGORY_IDS = [
@@ -44028,7 +44619,7 @@ var LlmProviderWidgetSchema = object({
44028
44619
  /** ConfigUISchema tree passed through untyped on the wire (the
44029
44620
  * notification-output `ConfigSchemaPassthrough` precedent at
44030
44621
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
44031
- var ConfigSchemaPassthrough$3 = unknown();
44622
+ var ConfigSchemaPassthrough$2 = unknown();
44032
44623
  var LlmProfileKindDescriptorSchema = object({
44033
44624
  kind: LlmProfileKindSchema,
44034
44625
  label: string(),
@@ -44037,9 +44628,9 @@ var LlmProfileKindDescriptorSchema = object({
44037
44628
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
44038
44629
  addonId: string(),
44039
44630
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
44040
- connectionSchema: ConfigSchemaPassthrough$3,
44631
+ connectionSchema: ConfigSchemaPassthrough$2,
44041
44632
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
44042
- advancedSchema: ConfigSchemaPassthrough$3.optional(),
44633
+ advancedSchema: ConfigSchemaPassthrough$2.optional(),
44043
44634
  /** The kind's defaults; a model from `listModels` may override any of them. */
44044
44635
  features: LlmModelFeaturesSchema,
44045
44636
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -44565,10 +45156,10 @@ var CpuBreakdownSchema = object({
44565
45156
  cores: number()
44566
45157
  });
44567
45158
  var MemoryInfoSchema = object({
44568
- percent: number(),
45159
+ percent: number().nullable(),
44569
45160
  totalBytes: number(),
44570
- usedBytes: number(),
44571
- availableBytes: number(),
45161
+ usedBytes: number().nullable(),
45162
+ availableBytes: number().nullable(),
44572
45163
  swapUsedBytes: number(),
44573
45164
  swapTotalBytes: number()
44574
45165
  });
@@ -44872,8 +45463,9 @@ var GetLoadSeriesInputSchema = object({
44872
45463
  });
44873
45464
  var SystemMetricsSchema = object({
44874
45465
  cpuPercent: number(),
44875
- memoryPercent: number(),
44876
- memoryUsedMB: number(),
45466
+ /** `null` when the memory sample failed or was not taken yet (D393). */
45467
+ memoryPercent: number().nullable(),
45468
+ memoryUsedMB: number().nullable(),
44877
45469
  memoryTotalMB: number(),
44878
45470
  diskPercent: number().optional(),
44879
45471
  temperature: number().optional(),
@@ -44980,578 +45572,135 @@ var BrokerStatusSchema$1 = _enum([
44980
45572
  "connected",
44981
45573
  "disconnected",
44982
45574
  "auth-failed",
44983
- "unreachable",
44984
- "tls-error"
44985
- ]);
44986
- var BrokerInfoSchema$1 = object({
44987
- id: string(),
44988
- name: string(),
44989
- url: string(),
44990
- kind: BrokerKindSchema,
44991
- status: BrokerStatusSchema$1,
44992
- latencyMs: number().nullable(),
44993
- error: string().optional(),
44994
- /** Embedded brokers only: number of MQTT clients currently connected. */
44995
- connectedClients: number().int().nonnegative().optional(),
44996
- /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
44997
- lastCheckedAt: number().optional()
44998
- });
44999
- /**
45000
- * Connection details — what a consumer needs to call
45001
- * `mqtt.connect(url, options)`. We split URL + credentials so the
45002
- * consumer can pass them as `mqtt.connect(url, { username, password })`
45003
- * instead of stuffing creds into the URL (which leaks them into logs).
45004
- */
45005
- var BrokerConnectionDetailsSchema = object({
45006
- url: string(),
45007
- username: string().optional(),
45008
- password: string().optional(),
45009
- /**
45010
- * Suggested prefix for `clientId`. Each consumer should suffix this
45011
- * with its own discriminator (addon id, instance id) so reconnects
45012
- * don't kick each other off (MQTT spec: clientId must be unique per
45013
- * broker).
45014
- */
45015
- clientIdPrefix: string().optional()
45016
- });
45017
- var AddBrokerInputSchema = object({
45018
- name: string().min(1),
45019
- url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
45020
- username: string().optional(),
45021
- password: string().optional(),
45022
- clientIdPrefix: string().optional()
45023
- });
45024
- var AddBrokerResultSchema = object({ id: string() });
45025
- var IdInputSchema = object({ id: string() });
45026
- var TestResultSchema$1 = discriminatedUnion("ok", [object({
45027
- ok: literal(true),
45028
- latencyMs: number()
45029
- }), object({
45030
- ok: literal(false),
45031
- error: string()
45032
- })]);
45033
- var StartEmbeddedInputSchema = object({
45034
- port: number().int().min(1).max(65535).default(1883),
45035
- /** Allow anonymous connect (no username/password). Default: false. */
45036
- allowAnonymous: boolean().default(false),
45037
- /** Optional shared username/password for clients. */
45038
- username: string().optional(),
45039
- password: string().optional()
45040
- });
45041
- var StartEmbeddedResultSchema = object({
45042
- id: string(),
45043
- url: string()
45044
- });
45045
- var StatusSchema = object({
45046
- brokerCount: number(),
45047
- embeddedRunning: boolean()
45048
- });
45049
- method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
45050
- kind: "mutation",
45051
- auth: "admin"
45052
- }), method(IdInputSchema, _void(), {
45053
- kind: "mutation",
45054
- auth: "admin"
45055
- }), method(IdInputSchema, TestResultSchema$1, {
45056
- kind: "mutation",
45057
- auth: "admin"
45058
- }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
45059
- kind: "mutation",
45060
- auth: "admin"
45061
- }), method(IdInputSchema, _void(), {
45062
- kind: "mutation",
45063
- auth: "admin"
45064
- }), method(_void(), StatusSchema);
45065
- var NetworkEndpointSchema = object({
45066
- url: string(),
45067
- hostname: string(),
45068
- port: number(),
45069
- protocol: _enum(["http", "https"]),
45070
- /**
45071
- * Whether this address survives a restart of its provider. `false` = it is
45072
- * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
45073
- * hub never selects such an address AUTOMATICALLY for a role (D860), though
45074
- * it stays visible and an operator may still flag it. Absent = stable: every
45075
- * provider except a Quick tunnel owns a name that does not change.
45076
- */
45077
- stable: boolean().optional()
45078
- });
45079
- object({
45080
- url: string(),
45081
- /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
45082
- stable: boolean().optional(),
45083
- updated: boolean().optional()
45084
- });
45085
- var NetworkAccessStatusSchema = object({
45086
- connected: boolean(),
45087
- endpoint: NetworkEndpointSchema.nullable(),
45088
- error: string().optional()
45089
- });
45090
- /**
45091
- * Optional, richer endpoint shape returned by providers that expose
45092
- * MORE than one ingress concurrently (Tailscale Funnel with mixed
45093
- * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
45094
- * the originating provider config (mode + sourcePort) so the
45095
- * orchestrator UI can label rows distinctly. Providers that expose only
45096
- * one endpoint just omit `listEndpoints` from their provider impl.
45097
- */
45098
- var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
45099
- /**
45100
- * Stable id within the provider — typically `<mode>-<sourcePort>` so
45101
- * the orchestrator can dedupe across `listEndpoints` polls.
45102
- */
45103
- id: string(),
45104
- /** Operator-facing label (mirrors `MeshEndpoint.label`). */
45105
- label: string(),
45106
- /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
45107
- mode: string().optional(),
45108
- /** Originating local port the ingress fronts (informational). */
45109
- sourcePort: number().optional()
45110
- });
45111
- method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
45112
- /**
45113
- * notification-output — canonical, capability-gated notification delivery.
45114
- *
45115
- * Apprise-derived model (see
45116
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
45117
- * callers emit ONE canonical `Notification`; each provider declares a
45118
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
45119
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
45120
- * message to what the kind supports — callers never special-case a service.
45121
- *
45122
- * DESIGN DECISIONS (locked):
45123
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
45124
- * `setTargetEnabled`), each provider persisting via the `settings-store`
45125
- * cap. Rationale: the admin UI needs one uniform surface across the
45126
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
45127
- * alternative would fork the UI per addon and cannot host the
45128
- * discovery→adopt flow.
45129
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
45130
- * the generated cap-mount auto-`concatCollection`-fans them across every
45131
- * registered provider (notifiers addon + HA addon) so one catalog is
45132
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
45133
- * `addonId` the generated collection router extracts from the call input.
45134
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
45135
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
45136
- * `storage` / `storage-provider` / `recording` caps over the same path. No
45137
- * base64 fallback needed.
45138
- *
45139
- * TODO (deferred, closed-set change — separate decision): add
45140
- * `providerKind: 'notify'` so notification providers surface on the unified
45141
- * admin "Integrations" page.
45142
- */
45143
- /**
45144
- * Zentik-derived typed-media enum — the superset across every kind. Each
45145
- * adapter picks what it supports and the degrade engine filters the rest.
45146
- */
45147
- var AttachmentMediaTypeSchema = _enum([
45148
- "image",
45149
- "video",
45150
- "gif",
45151
- "audio",
45152
- "icon"
45153
- ]);
45154
- /**
45155
- * A single attachment. Exactly one of `url` (remote source, most adapters
45156
- * prefer this) or `bytes` (inline source; required for Pushover-style
45157
- * bytes-only kinds) MUST be present — the degrade engine expresses a
45158
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
45159
- */
45160
- var AttachmentSchema = object({
45161
- mediaType: AttachmentMediaTypeSchema,
45162
- url: string().optional(),
45163
- bytes: _instanceof(Uint8Array).optional(),
45164
- mime: string().optional(),
45165
- name: string().optional()
45166
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
45167
- var NotificationFormatSchema = _enum([
45168
- "text",
45169
- "markdown",
45170
- "html"
45171
- ]);
45172
- /**
45173
- * The CLOSED icon vocabulary an action button may use.
45174
- *
45175
- * A closed set, not a free string, and that is the whole point: an arbitrary
45176
- * icon name is one that ntfy renders, zentik silently drops, and nobody
45177
- * notices — the same class of gap as a zone vocabulary nothing produced
45178
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
45179
- * declares `actionIcons: false` and the degrade engine strips the field.
45180
- *
45181
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
45182
- * renderer's icon set; "acknowledge" survives an adapter that draws it
45183
- * differently.
45184
- *
45185
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
45186
- *
45187
- * These names are for US. **No adapter may forward one verbatim.** Each maps
45188
- * the whole set onto its own renderer's vocabulary through a
45189
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
45190
- * fallback, so adding a member here fails every adapter's build until someone
45191
- * decides its glyph, which is the only place that decision can be made
45192
- * honestly.
45193
- *
45194
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
45195
- * `disarm` straight through; iOS feeds that string to
45196
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
45197
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
45198
- * mapping, and "the field is documented" is not "the value renders".
45199
- *
45200
- * Adding a member is TRAIN-BOUND. The enum lives in the published
45201
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
45202
- * so an addon that emits a token the running hub does not know does not lose an
45203
- * icon — its whole `send` fails Zod validation and the notification never
45204
- * arrives. Never emit a new token from an addon before the train carrying it.
45205
- */
45206
- var NotificationActionIconSchema = _enum([
45207
- "acknowledge",
45208
- "dismiss",
45209
- "silence",
45210
- "snooze",
45211
- "view",
45212
- "play",
45213
- "open",
45214
- "close",
45215
- "lock",
45216
- "unlock",
45217
- "arm",
45218
- "arm-home",
45219
- "arm-away",
45220
- "arm-night",
45221
- "disarm",
45222
- "light",
45223
- "alert",
45224
- "camera"
45225
- ]);
45226
- /** A single tap-through action button. */
45227
- var NotificationActionSchema = object({
45228
- id: string(),
45229
- label: string(),
45230
- url: string().optional(),
45231
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
45232
- icon: NotificationActionIconSchema.optional(),
45233
- /**
45234
- * Renders in a warning style where the notifier supports it.
45235
- *
45236
- * A HINT, never a gate. The callback's authority is its token and nothing
45237
- * else — see `notification-center/action-token.ts` for what that does and
45238
- * does not buy.
45239
- */
45240
- destructive: boolean().optional(),
45241
- /**
45242
- * How the tap should REACH the url.
45243
- *
45244
- * `navigate` (absent, and every button authored before this field) opens it:
45245
- * the phone leaves the notification and shows whatever the callback returns.
45246
- * That is right for a button whose answer the operator wants to read.
45247
- *
45248
- * `background` fires it as a POST and stays put. It exists for the buttons
45249
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
45250
- * an answer to the notification, and being thrown into a browser tab to
45251
- * confirm it costs more attention than the notification did. A backend that
45252
- * cannot do a background call renders it as an ordinary link (the adapters
45253
- * fall back rather than dropping the button), so this is a preference, never
45254
- * a requirement.
45255
- */
45256
- mode: _enum(["navigate", "background"]).optional()
45257
- });
45258
- /** A delivery parameter value: what a notifier declared it takes. */
45259
- var DeliveryParamValueSchema = union([
45260
- string(),
45261
- number(),
45262
- boolean()
45263
- ]);
45264
- /**
45265
- * One extra delivery parameter a notifier KIND declares (D938). A rule that
45266
- * addresses a target of that kind may set it; a kind that declares nothing is
45267
- * never handed one. The admin UI derives its control from this descriptor —
45268
- * no editor knows a service by name.
45269
- *
45270
- * Optional fields are `.optional()`, never `.default()` (a Zod default does not
45271
- * run on the addon cap path).
45272
- */
45273
- var DeliveryParamSchema = object({
45274
- key: string().min(1),
45275
- label: string(),
45276
- /** i18n key of the label, resolved by the declaring addon's namespace. */
45277
- labelKey: string().optional(),
45278
- type: _enum([
45279
- "enum",
45280
- "number",
45281
- "boolean",
45282
- "string"
45283
- ]),
45284
- /** `enum` only: the closed set of accepted values. */
45285
- options: array(object({
45286
- value: string(),
45287
- label: string()
45288
- })).optional(),
45289
- /** `number` only. */
45290
- min: number().optional(),
45291
- max: number().optional(),
45292
- /** What the notifier does when the rule sets nothing; shown as the placeholder. */
45293
- default: DeliveryParamValueSchema.optional()
45294
- });
45295
- /**
45296
- * The canonical notification. `body` is the only hard field (Apprise model).
45297
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
45298
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
45299
- * the adapter maps this ordinal onto its native level. `level?` is an
45300
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
45301
- * `priority` for that one target.
45302
- */
45303
- var NotificationSchema = object({
45304
- body: string(),
45305
- title: string().optional(),
45306
- format: NotificationFormatSchema.default("text"),
45307
- priority: number().int().min(1).max(5).default(3),
45308
- level: string().optional(),
45309
- attachments: array(AttachmentSchema).optional(),
45310
- clickUrl: string().optional(),
45311
- actions: array(NotificationActionSchema).optional(),
45312
- sound: string().optional(),
45313
- ttl: number().optional(),
45314
- tag: string().optional(),
45315
- deviceId: number().optional(),
45316
- eventId: string().optional(),
45317
- metadata: record(string(), unknown()).optional(),
45318
- /**
45319
- * Values for the parameters the target's kind DECLARES in
45320
- * {@link TargetKindSchema} `deliveryParams`. Already validated against the
45321
- * declaration by the sender; a notifier reads only the keys it declared.
45322
- */
45323
- delivery: record(string(), DeliveryParamValueSchema).optional()
45324
- });
45325
- /** One declared native severity/priority level for a kind. */
45326
- var TargetKindLevelSchema = object({
45327
- id: string(),
45328
- label: string(),
45329
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
45330
- ordinal: number().int().min(1).max(5).nullable(),
45331
- flags: object({
45332
- critical: boolean().optional(),
45333
- silent: boolean().optional(),
45334
- noPush: boolean().optional()
45335
- }).optional(),
45336
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
45337
- requires: array(string()).optional(),
45338
- description: string().optional()
45339
- });
45340
- /** The full capability block consulted before dispatch. */
45341
- var TargetKindCapsSchema = object({
45342
- attachments: object({
45343
- mediaTypes: array(AttachmentMediaTypeSchema),
45344
- mode: _enum([
45345
- "url",
45346
- "bytes",
45347
- "both"
45348
- ]),
45349
- max: number().int().nonnegative(),
45350
- maxBytes: number().int().positive().optional()
45351
- }),
45352
- /** Max action buttons (0 = none). */
45353
- actions: number().int().nonnegative(),
45354
- /**
45355
- * Whether this kind renders a per-action ICON.
45356
- *
45357
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
45358
- * run on the addon cap path — three production failures in one day taught
45359
- * this repo that once. Absent is read as false by the degrade engine, which
45360
- * is the safe direction: an icon that is not rendered costs nothing, an icon
45361
- * assumed and dropped costs the operator's trust in the field.
45362
- */
45363
- actionIcons: boolean().optional(),
45364
- levels: array(TargetKindLevelSchema),
45365
- format: array(NotificationFormatSchema),
45366
- clickUrl: boolean(),
45367
- sound: boolean(),
45368
- ttl: boolean(),
45369
- bodyMaxLen: number().int().positive()
45575
+ "unreachable",
45576
+ "tls-error"
45577
+ ]);
45578
+ var BrokerInfoSchema$1 = object({
45579
+ id: string(),
45580
+ name: string(),
45581
+ url: string(),
45582
+ kind: BrokerKindSchema,
45583
+ status: BrokerStatusSchema$1,
45584
+ latencyMs: number().nullable(),
45585
+ error: string().optional(),
45586
+ /** Embedded brokers only: number of MQTT clients currently connected. */
45587
+ connectedClients: number().int().nonnegative().optional(),
45588
+ /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
45589
+ lastCheckedAt: number().optional()
45370
45590
  });
45371
45591
  /**
45372
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
45373
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
45374
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
45375
- * the union is large and not meant for runtime validation here; the exported
45376
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
45592
+ * Connection details — what a consumer needs to call
45593
+ * `mqtt.connect(url, options)`. We split URL + credentials so the
45594
+ * consumer can pass them as `mqtt.connect(url, { username, password })`
45595
+ * instead of stuffing creds into the URL (which leaks them into logs).
45377
45596
  */
45378
- var ConfigSchemaPassthrough$2 = unknown();
45379
- var TargetKindSchema = object({
45380
- kind: string(),
45381
- label: string(),
45382
- icon: string(),
45383
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
45384
- addonId: string(),
45385
- /**
45386
- * URL of the kind's bundled BRAND icon, served by the providing addon over
45387
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
45388
- * when the addon bundles no icon for that kind — the client then falls back
45389
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
45390
- *
45391
- * Root-relative on purpose: it resolves against whatever origin serves a web
45392
- * client, and a native client joins it onto its own hub base.
45393
- *
45394
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
45395
- * field that survived only because the runtime cap-router forwards provider
45396
- * output verbatim — so every consumer had to re-declare it by hand to stop
45397
- * its own Zod parse from stripping it, and the whole arrangement would have
45398
- * broken silently the moment output validation was tightened anywhere.
45399
- */
45400
- iconUrl: string().optional(),
45401
- /**
45402
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
45403
- *
45404
- * The server knows this and therefore says it, because the client cannot
45405
- * safely guess: a React-Native client renders SVG and raster through two
45406
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
45407
- * not decode SVG on iOS/Android), so without this it silently fell back to a
45408
- * placeholder glyph for every vector icon while the web build looked fine.
45409
- *
45410
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
45411
- * not been updated — a client that cannot determine the type should prefer
45412
- * its raster path, which is the safe default for an unknown image.
45413
- */
45414
- iconMediaType: string().optional(),
45415
- configSchema: ConfigSchemaPassthrough$2,
45416
- /**
45417
- * The config keys that decide WHERE this kind's secrets are sent (server,
45418
- * base URL, instance, chat, recipient). A save that changes any of them
45419
- * while leaving a secret redacted is refused by the provider (D634), so an
45420
- * editor uses this to ask for the secret on exactly those edits instead of
45421
- * on every edit.
45422
- *
45423
- * Optional: absent from a provider that holds no secret (the Home Assistant
45424
- * kinds) and from a provider older than this field — an editor that finds
45425
- * it absent must assume ANY edit may need the secret.
45426
- */
45427
- destinationKeys: array(string()).optional(),
45428
- supportsDiscovery: boolean(),
45597
+ var BrokerConnectionDetailsSchema = object({
45598
+ url: string(),
45599
+ username: string().optional(),
45600
+ password: string().optional(),
45429
45601
  /**
45430
- * Extra delivery parameters a rule may set for a target of this kind (D938).
45431
- * Absent = none; the rule editor then offers nothing.
45602
+ * Suggested prefix for `clientId`. Each consumer should suffix this
45603
+ * with its own discriminator (addon id, instance id) so reconnects
45604
+ * don't kick each other off (MQTT spec: clientId must be unique per
45605
+ * broker).
45432
45606
  */
45433
- deliveryParams: array(DeliveryParamSchema).optional(),
45434
- caps: TargetKindCapsSchema
45607
+ clientIdPrefix: string().optional()
45435
45608
  });
45436
- /**
45437
- * A persisted target. `config` holds secrets; providers REDACT secret fields
45438
- * (return a presence marker only) when serving `listTargets` — never
45439
- * round-trip a stored secret to the UI.
45440
- */
45441
- var TargetSchema = object({
45609
+ var AddBrokerInputSchema = object({
45610
+ name: string().min(1),
45611
+ url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
45612
+ username: string().optional(),
45613
+ password: string().optional(),
45614
+ clientIdPrefix: string().optional()
45615
+ });
45616
+ var AddBrokerResultSchema = object({ id: string() });
45617
+ var IdInputSchema = object({ id: string() });
45618
+ var TestResultSchema$1 = discriminatedUnion("ok", [object({
45619
+ ok: literal(true),
45620
+ latencyMs: number()
45621
+ }), object({
45622
+ ok: literal(false),
45623
+ error: string()
45624
+ })]);
45625
+ var StartEmbeddedInputSchema = object({
45626
+ port: number().int().min(1).max(65535).default(1883),
45627
+ /** Allow anonymous connect (no username/password). Default: false. */
45628
+ allowAnonymous: boolean().default(false),
45629
+ /** Optional shared username/password for clients. */
45630
+ username: string().optional(),
45631
+ password: string().optional()
45632
+ });
45633
+ var StartEmbeddedResultSchema = object({
45442
45634
  id: string(),
45443
- name: string(),
45444
- kind: string(),
45445
- addonId: string(),
45446
- enabled: boolean(),
45447
- config: record(string(), unknown())
45635
+ url: string()
45448
45636
  });
45449
- /** A discovery-surfaced candidate (config is partial + non-secret). */
45450
- var DiscoveredTargetSchema = object({
45451
- kind: string(),
45452
- suggestedName: string(),
45453
- config: record(string(), unknown())
45637
+ var StatusSchema = object({
45638
+ brokerCount: number(),
45639
+ embeddedRunning: boolean()
45454
45640
  });
45455
- /** The degrade engine's report — what was resolved / dropped / degraded. */
45456
- var RenderedAsSchema = object({
45457
- level: string(),
45458
- format: NotificationFormatSchema,
45459
- attachmentsSent: number().int().nonnegative(),
45460
- actionsSent: number().int().nonnegative(),
45461
- truncated: boolean(),
45462
- dropped: array(string())
45641
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
45642
+ kind: "mutation",
45643
+ auth: "admin"
45644
+ }), method(IdInputSchema, _void(), {
45645
+ kind: "mutation",
45646
+ auth: "admin"
45647
+ }), method(IdInputSchema, TestResultSchema$1, {
45648
+ kind: "mutation",
45649
+ auth: "admin"
45650
+ }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
45651
+ kind: "mutation",
45652
+ auth: "admin"
45653
+ }), method(IdInputSchema, _void(), {
45654
+ kind: "mutation",
45655
+ auth: "admin"
45656
+ }), method(_void(), StatusSchema);
45657
+ var NetworkEndpointSchema = object({
45658
+ url: string(),
45659
+ hostname: string(),
45660
+ port: number(),
45661
+ protocol: _enum(["http", "https"]),
45662
+ /**
45663
+ * Whether this address survives a restart of its provider. `false` = it is
45664
+ * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
45665
+ * hub never selects such an address AUTOMATICALLY for a role (D860), though
45666
+ * it stays visible and an operator may still flag it. Absent = stable: every
45667
+ * provider except a Quick tunnel owns a name that does not change.
45668
+ */
45669
+ stable: boolean().optional()
45463
45670
  });
45464
- var SendResultSchema = object({
45465
- success: boolean(),
45466
- error: string().optional(),
45467
- renderedAs: RenderedAsSchema.optional()
45671
+ object({
45672
+ url: string(),
45673
+ /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
45674
+ stable: boolean().optional(),
45675
+ updated: boolean().optional()
45676
+ });
45677
+ var NetworkAccessStatusSchema = object({
45678
+ connected: boolean(),
45679
+ endpoint: NetworkEndpointSchema.nullable(),
45680
+ error: string().optional()
45468
45681
  });
45469
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
45470
- var TestResultSchema = SendResultSchema;
45471
45682
  /**
45472
- * Pre-save credential probe against a partial (unsaved) target config.
45473
- * `resolvedUrl` never includes secrets (token stripped from path).
45683
+ * Optional, richer endpoint shape returned by providers that expose
45684
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
45685
+ * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
45686
+ * the originating provider config (mode + sourcePort) so the
45687
+ * orchestrator UI can label rows distinctly. Providers that expose only
45688
+ * one endpoint just omit `listEndpoints` from their provider impl.
45474
45689
  */
45475
- var ProbeTargetConfigResultSchema = object({
45476
- ok: boolean(),
45477
- resolvedUrl: string(),
45478
- httpStatus: number().int().optional(),
45479
- stateInstance: string().optional(),
45480
- error: string().optional()
45690
+ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
45691
+ /**
45692
+ * Stable id within the provider — typically `<mode>-<sourcePort>` so
45693
+ * the orchestrator can dedupe across `listEndpoints` polls.
45694
+ */
45695
+ id: string(),
45696
+ /** Operator-facing label (mirrors `MeshEndpoint.label`). */
45697
+ label: string(),
45698
+ /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
45699
+ mode: string().optional(),
45700
+ /** Originating local port the ingress fronts (informational). */
45701
+ sourcePort: number().optional()
45481
45702
  });
45482
- var notificationOutputCapability = {
45483
- name: "notification-output",
45484
- scope: "system",
45485
- mode: "collection",
45486
- methods: {
45487
- listTargetKinds: method(object({}), array(TargetKindSchema)),
45488
- listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
45489
- /**
45490
- * One target WITH secrets — admin edit / duplicate hydration only.
45491
- * `listTargets` stays redacted; the editor loads this so password fields
45492
- * are filled (masked in the input) and a save can round-trip the token
45493
- * when a destination changes (Duplicate with a new chatId, etc.).
45494
- */
45495
- getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
45496
- discoverTargets: method(object({
45497
- kind: string(),
45498
- config: record(string(), unknown()).optional(),
45499
- /**
45500
- * When editing / duplicating, the blank password field never round-trips
45501
- * the stored token. Pass the source target id so the provider can restore
45502
- * secrets server-side before dialling (same merge as upsert).
45503
- */
45504
- targetId: string().optional()
45505
- }), array(DiscoveredTargetSchema), { auth: "admin" }),
45506
- probeTargetConfig: method(object({
45507
- kind: string(),
45508
- config: record(string(), unknown()),
45509
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
45510
- targetId: string().optional()
45511
- }), ProbeTargetConfigResultSchema, {
45512
- kind: "mutation",
45513
- auth: "admin"
45514
- }),
45515
- send: method(object({
45516
- targetId: string(),
45517
- notification: NotificationSchema
45518
- }), SendResultSchema, {
45519
- kind: "mutation",
45520
- auth: "admin"
45521
- }),
45522
- testTarget: method(object({
45523
- targetId: string(),
45524
- sample: NotificationSchema.optional()
45525
- }), TestResultSchema, {
45526
- kind: "mutation",
45527
- auth: "admin"
45528
- }),
45529
- upsertTarget: method(object({
45530
- target: TargetSchema,
45531
- /**
45532
- * Duplicate flow: create a NEW target id while copying secrets from an
45533
- * existing row. The UI never holds the stored token; blank / redacted
45534
- * password fields are restored from this source (D634 still applies if
45535
- * destinations diverge without a re-typed secret).
45536
- */
45537
- cloneSecretsFrom: string().optional()
45538
- }), TargetSchema, {
45539
- kind: "mutation",
45540
- auth: "admin"
45541
- }),
45542
- deleteTarget: method(object({ targetId: string() }), _void(), {
45543
- kind: "mutation",
45544
- auth: "admin"
45545
- }),
45546
- setTargetEnabled: method(object({
45547
- targetId: string(),
45548
- enabled: boolean()
45549
- }), _void(), {
45550
- kind: "mutation",
45551
- auth: "admin"
45552
- })
45553
- }
45554
- };
45703
+ method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
45555
45704
  var AUDIO_MACRO_LABELS = [
45556
45705
  {
45557
45706
  id: "speech",
@@ -51996,14 +52145,6 @@ var EngineProvisioningSchema = object({
51996
52145
  */
51997
52146
  configIssues: array(string()).optional()
51998
52147
  });
51999
- var PipelineStepInputSchema = lazy(() => object({
52000
- addonId: string(),
52001
- modelId: string().optional(),
52002
- enabled: boolean().default(true),
52003
- children: array(PipelineStepInputSchema).optional(),
52004
- settings: record(string(), unknown()).optional(),
52005
- jumpDeviceKey: string().optional()
52006
- }));
52007
52148
  var ModelSubstitutionSchema = object({
52008
52149
  addonId: string(),
52009
52150
  chosen: string(),
@@ -52082,7 +52223,6 @@ var ConfigUISchemaBridge = custom();
52082
52223
  var ConfigUISchemaNullableBridge = custom();
52083
52224
  var InferenceCapabilitiesBridge = custom();
52084
52225
  var ModelAvailabilityListBridge = custom();
52085
- var PipelineRunResultBridge = custom();
52086
52226
  method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
52087
52227
  modelId: string(),
52088
52228
  settings: record(string(), unknown()).readonly()
@@ -55288,6 +55428,12 @@ var ServerUpdateActionResultSchema = object({
55288
55428
  */
55289
55429
  blockers: array(UpdateBlockerSchema).optional()
55290
55430
  });
55431
+ var LaunchOverridesSchema = object({
55432
+ /** The persisted heap ceiling in MB, or `null` when none is set. */
55433
+ heapMb: number().int().nullable() });
55434
+ var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
55435
+ /** Always true: the override is read at launch, not by the running process. */
55436
+ restartRequired: boolean() });
55291
55437
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
55292
55438
  kind: "mutation",
55293
55439
  auth: "admin"
@@ -55310,6 +55456,9 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
55310
55456
  }), method(_void(), ServerUpdateActionResultSchema, {
55311
55457
  kind: "mutation",
55312
55458
  auth: "admin"
55459
+ }), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
55460
+ kind: "mutation",
55461
+ auth: "admin"
55313
55462
  });
55314
55463
  /**
55315
55464
  * `smtp-provider` — pluggable email delivery surface.
@@ -60218,14 +60367,13 @@ reset: method(object({
60218
60367
  auth: "admin"
60219
60368
  }) },
60220
60369
  /**
60221
- * A composed consumable (item added to a device with no native consumables
60222
- * provider) refuses `reset` by name: "Only meaningful when the item's
60223
- * `resettable` is true" — a composed item is never resettable until
60224
- * commands (`forward`) arrive in slice 3 (D663).
60370
+ * A consumable ADDED to an existing device (a graft) refuses `reset` by
60371
+ * name: only a composed device of a NEW type forwards an item's reset to a
60372
+ * source button, and marks that item `resettable` (D967).
60225
60373
  */
60226
60374
  composedMethods: { reset: {
60227
60375
  kind: "refuse",
60228
- reason: "a composed consumable is not resettable until commands (forward) arrive — slice 3"
60376
+ reason: "a consumable added to an existing device is not resettable — only a new composed device forwards a reset"
60229
60377
  } },
60230
60378
  status: {
60231
60379
  schema: ConsumablesStatusSchema,
@@ -60819,7 +60967,7 @@ var DishwasherStatusSchema = object({
60819
60967
  /** Ms epoch of the last value received from the appliance; 0 = never. */
60820
60968
  lastFetchedAt: number()
60821
60969
  });
60822
- var DeviceIdSchema = number().int().nonnegative();
60970
+ var DeviceIdSchema$1 = number().int().nonnegative();
60823
60971
  /** A value a program option or setting can take. */
60824
60972
  var DishwasherValueSchema = union([
60825
60973
  boolean(),
@@ -60894,77 +61042,77 @@ var dishwasherCapability = {
60894
61042
  * option feature keys; each is checked against the program's constraints.
60895
61043
  */
60896
61044
  start: method(object({
60897
- deviceId: DeviceIdSchema,
61045
+ deviceId: DeviceIdSchema$1,
60898
61046
  program: string().min(1).optional(),
60899
61047
  options: record(string(), DishwasherValueSchema).optional()
60900
61048
  }), _void(), {
60901
61049
  kind: "mutation",
60902
61050
  auth: "protected"
60903
61051
  }),
60904
- pause: method(object({ deviceId: DeviceIdSchema }), _void(), {
61052
+ pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
60905
61053
  kind: "mutation",
60906
61054
  auth: "protected"
60907
61055
  }),
60908
- resume: method(object({ deviceId: DeviceIdSchema }), _void(), {
61056
+ resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
60909
61057
  kind: "mutation",
60910
61058
  auth: "protected"
60911
61059
  }),
60912
- stop: method(object({ deviceId: DeviceIdSchema }), _void(), {
61060
+ stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
60913
61061
  kind: "mutation",
60914
61062
  auth: "protected"
60915
61063
  }),
60916
61064
  /** Select (not start) a program; refused when the appliance does not offer it. */
60917
61065
  setProgram: method(object({
60918
- deviceId: DeviceIdSchema,
61066
+ deviceId: DeviceIdSchema$1,
60919
61067
  program: string().min(1)
60920
61068
  }), _void(), {
60921
61069
  kind: "mutation",
60922
61070
  auth: "protected"
60923
61071
  }),
60924
61072
  setPower: method(object({
60925
- deviceId: DeviceIdSchema,
61073
+ deviceId: DeviceIdSchema$1,
60926
61074
  on: boolean()
60927
61075
  }), _void(), {
60928
61076
  kind: "mutation",
60929
61077
  auth: "protected"
60930
61078
  }),
60931
61079
  setHalfLoad: method(object({
60932
- deviceId: DeviceIdSchema,
61080
+ deviceId: DeviceIdSchema$1,
60933
61081
  on: boolean()
60934
61082
  }), _void(), {
60935
61083
  kind: "mutation",
60936
61084
  auth: "protected"
60937
61085
  }),
60938
61086
  setHygienePlus: method(object({
60939
- deviceId: DeviceIdSchema,
61087
+ deviceId: DeviceIdSchema$1,
60940
61088
  on: boolean()
60941
61089
  }), _void(), {
60942
61090
  kind: "mutation",
60943
61091
  auth: "protected"
60944
61092
  }),
60945
61093
  setExtraDry: method(object({
60946
- deviceId: DeviceIdSchema,
61094
+ deviceId: DeviceIdSchema$1,
60947
61095
  on: boolean()
60948
61096
  }), _void(), {
60949
61097
  kind: "mutation",
60950
61098
  auth: "protected"
60951
61099
  }),
60952
61100
  setIntensiveZone: method(object({
60953
- deviceId: DeviceIdSchema,
61101
+ deviceId: DeviceIdSchema$1,
60954
61102
  on: boolean()
60955
61103
  }), _void(), {
60956
61104
  kind: "mutation",
60957
61105
  auth: "protected"
60958
61106
  }),
60959
61107
  setVarioSpeedPlus: method(object({
60960
- deviceId: DeviceIdSchema,
61108
+ deviceId: DeviceIdSchema$1,
60961
61109
  on: boolean()
60962
61110
  }), _void(), {
60963
61111
  kind: "mutation",
60964
61112
  auth: "protected"
60965
61113
  }),
60966
61114
  setChildLock: method(object({
60967
- deviceId: DeviceIdSchema,
61115
+ deviceId: DeviceIdSchema$1,
60968
61116
  on: boolean()
60969
61117
  }), _void(), {
60970
61118
  kind: "mutation",
@@ -60976,7 +61124,7 @@ var dishwasherCapability = {
60976
61124
  * its declared range / members.
60977
61125
  */
60978
61126
  setOption: method(object({
60979
- deviceId: DeviceIdSchema,
61127
+ deviceId: DeviceIdSchema$1,
60980
61128
  key: string().min(1),
60981
61129
  value: DishwasherValueSchema
60982
61130
  }), _void(), {
@@ -60984,7 +61132,7 @@ var dishwasherCapability = {
60984
61132
  auth: "protected"
60985
61133
  }),
60986
61134
  /** What this appliance can do, from its own description — the UI is derived from it. */
60987
- getOptions: method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" })
61135
+ getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
60988
61136
  },
60989
61137
  status: {
60990
61138
  schema: DishwasherStatusSchema,
@@ -64675,7 +64823,8 @@ var TopologyNodeSchema = object({
64675
64823
  isHub: boolean(),
64676
64824
  isOnline: boolean(),
64677
64825
  cpuPercent: number(),
64678
- memoryPercent: number(),
64826
+ /** `null` when the node's memory read failed or it has not reported one (D393). */
64827
+ memoryPercent: number().nullable(),
64679
64828
  uptime: number(),
64680
64829
  lastSeen: string(),
64681
64830
  localIps: array(string()).readonly(),
@@ -65293,9 +65442,10 @@ var PetFeederStatusSchema = object({
65293
65442
  * combined reading; per-hopper levels live in `food1`/`food2`. Null when
65294
65443
  * the device has not reported yet. */
65295
65444
  foodLevel: number().nullable().meta(readingMeta("none")),
65296
- /** Food in the BOWL as the feeder reports it (`state.bowl`), in the
65297
- * feeder's own units. Null when the model reports none. */
65298
- bowlFood: number().nullable().meta(readingMeta("none")),
65445
+ /** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
65446
+ * feeder with a bowl scale; null when the model has none or its reading is
65447
+ * of an unproven unit. */
65448
+ bowlFood: number().nullable().meta(readingMeta("mass")),
65299
65449
  /** True while the pet is eating (`state.eating`). */
65300
65450
  eating: boolean(),
65301
65451
  /** Whether `markFoodReplenished` does anything on this model. When false
@@ -65479,6 +65629,137 @@ var petFeederCapability = {
65479
65629
  }
65480
65630
  } }
65481
65631
  };
65632
+ /**
65633
+ * Pet water-fountain cap. Models the telemetry and the control surface of a
65634
+ * circulating drinking fountain: how much water is in the tank, how much the
65635
+ * pet drank, whether the pump runs and on what duty cycle.
65636
+ *
65637
+ * It has no native vendor provider of its own: a fountain reaches CamStack as
65638
+ * a container of loose entities (a pump switch, two number boxes, a "filter
65639
+ * changed" button), and an operator BUILDS the device from them — a composed
65640
+ * device of this type (D659, D967). Every method is therefore a forward to ONE
65641
+ * method of ONE source entity, and every reading is a field derived from a
65642
+ * source entity; the provider-shaped interface below exists so the composed
65643
+ * device and any future native provider answer the same contract.
65644
+ *
65645
+ * Quantities are stored in the base unit of their kind (D944): volumes in
65646
+ * LITRES, durations in SECONDS, the tank level in percent. A source that
65647
+ * reports millilitres or minutes is converted in the composition's expression,
65648
+ * never here. Every reading may be `null`: a source that is offline or does
65649
+ * not report is unknown, never zero.
65650
+ *
65651
+ * The filter is not a field of this cap: it is an item of the `consumables`
65652
+ * cap on the same device (kind `wear`), reset by the same forward mechanism.
65653
+ */
65654
+ var PetFountainStatusSchema = object({
65655
+ /** Water in the tank, 0..100 (%). Null while the source has not reported. */
65656
+ waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65657
+ /** Water in the tank, in litres. Null while the source has not reported. */
65658
+ waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
65659
+ /** True when the tank is empty. Null while unknown. */
65660
+ empty: boolean().nullable(),
65661
+ /** True while the pump is running. Null while unknown. */
65662
+ pumpRunning: boolean().nullable(),
65663
+ /** True when the pump works on an on/off duty cycle rather than continuously. */
65664
+ pumpIntervalEnabled: boolean().nullable(),
65665
+ /** How long the pump runs per cycle, in seconds. */
65666
+ pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
65667
+ /** How long the pump rests per cycle, in seconds. */
65668
+ pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
65669
+ /** Water drunk in the last drink, in litres. */
65670
+ lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
65671
+ /** Water drunk in the current hour, in litres. */
65672
+ currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
65673
+ /** Water drunk over the last 24 hours (rolling), in litres. */
65674
+ last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
65675
+ /** Water drunk since local midnight, in litres. */
65676
+ todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
65677
+ /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
65678
+ error: string().nullable(),
65679
+ /** Ms epoch when the slice was last refreshed. */
65680
+ lastFetchedAt: number()
65681
+ });
65682
+ var DeviceIdSchema = number().int().nonnegative();
65683
+ var MinutesSchema = number().int().min(0);
65684
+ var petFountainCapability = {
65685
+ name: "pet-fountain",
65686
+ scope: "device",
65687
+ deviceNative: true,
65688
+ mode: "singleton",
65689
+ deviceTypes: [DeviceType.PetFountain],
65690
+ methods: {
65691
+ /** Start or stop the pump. */
65692
+ setPump: method(object({
65693
+ deviceId: DeviceIdSchema,
65694
+ on: boolean()
65695
+ }), _void(), {
65696
+ kind: "mutation",
65697
+ auth: "protected"
65698
+ }),
65699
+ /** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
65700
+ setPumpInterval: method(object({
65701
+ deviceId: DeviceIdSchema,
65702
+ on: boolean()
65703
+ }), _void(), {
65704
+ kind: "mutation",
65705
+ auth: "protected"
65706
+ }),
65707
+ /** Set how many minutes the pump runs per cycle. */
65708
+ setPumpOnMinutes: method(object({
65709
+ deviceId: DeviceIdSchema,
65710
+ minutes: MinutesSchema
65711
+ }), _void(), {
65712
+ kind: "mutation",
65713
+ auth: "protected"
65714
+ }),
65715
+ /** Set how many minutes the pump rests per cycle. */
65716
+ setPumpOffMinutes: method(object({
65717
+ deviceId: DeviceIdSchema,
65718
+ minutes: MinutesSchema
65719
+ }), _void(), {
65720
+ kind: "mutation",
65721
+ auth: "protected"
65722
+ }),
65723
+ /** Clear the rolling daily consumption counters on the device. */
65724
+ resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
65725
+ kind: "mutation",
65726
+ auth: "protected"
65727
+ })
65728
+ },
65729
+ status: {
65730
+ schema: PetFountainStatusSchema,
65731
+ kind: "push"
65732
+ },
65733
+ /** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
65734
+ runtimeState: PetFountainStatusSchema,
65735
+ /**
65736
+ * Runtime-state durability: **session** — live appliance state re-published on connect.
65737
+ *
65738
+ * See `RuntimeStateDurability`. Enforced by
65739
+ * `scripts/check-runtime-state-durability.ts`.
65740
+ */
65741
+ durability: "session",
65742
+ /** History (D850): recorded series. */
65743
+ history: { fields: {
65744
+ waterLevel: {
65745
+ kind: "gauge",
65746
+ unit: "%",
65747
+ deadband: 1
65748
+ },
65749
+ waterVolume: {
65750
+ kind: "gauge",
65751
+ unit: "L"
65752
+ },
65753
+ todayDrink: {
65754
+ kind: "gauge",
65755
+ unit: "L"
65756
+ },
65757
+ last24hDrink: {
65758
+ kind: "gauge",
65759
+ unit: "L"
65760
+ }
65761
+ } }
65762
+ };
65482
65763
  var VehicleSchema = object({
65483
65764
  id: string(),
65484
65765
  name: string(),
@@ -71401,7 +71682,7 @@ discriminatedUnion("kind", [object({
71401
71682
  })]);
71402
71683
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
71403
71684
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
71404
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
71685
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
71405
71686
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
71406
71687
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
71407
71688
  new Set(CAP_NAMES);
@@ -71752,6 +72033,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
71752
72033
  notifier: notifierCapability,
71753
72034
  numericSensor: numericSensorCapability,
71754
72035
  petFeeder: petFeederCapability,
72036
+ petFountain: petFountainCapability,
71755
72037
  powerMeter: powerMeterCapability,
71756
72038
  presence: presenceCapability,
71757
72039
  pressureSensor: pressureSensorCapability,
@@ -73002,7 +73284,7 @@ var BaseDeviceProvider = class extends BaseAddon {
73002
73284
  return toDeviceSummary(device, this.addonId);
73003
73285
  }
73004
73286
  };
73005
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
73287
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
73006
73288
  new Set(Object.values(DeviceType));
73007
73289
  Object.freeze({
73008
73290
  "accessories.getStatus": {
@@ -74937,6 +75219,13 @@ Object.freeze({
74937
75219
  access: "create",
74938
75220
  role: "admin"
74939
75221
  },
75222
+ "deviceExport.exposeEntity": {
75223
+ capName: "device-export",
75224
+ capScope: "system",
75225
+ addonId: null,
75226
+ access: "create",
75227
+ role: "admin"
75228
+ },
74940
75229
  "deviceExport.getDeviceLiveContribution": {
74941
75230
  capName: "device-export",
74942
75231
  capScope: "system",
@@ -74965,6 +75254,13 @@ Object.freeze({
74965
75254
  access: "view",
74966
75255
  role: "admin"
74967
75256
  },
75257
+ "deviceExport.listExposedEntities": {
75258
+ capName: "device-export",
75259
+ capScope: "system",
75260
+ addonId: null,
75261
+ access: "view",
75262
+ role: "admin"
75263
+ },
74968
75264
  "deviceExport.listSupportedDeviceKinds": {
74969
75265
  capName: "device-export",
74970
75266
  capScope: "system",
@@ -74972,6 +75268,13 @@ Object.freeze({
74972
75268
  access: "view",
74973
75269
  role: "admin"
74974
75270
  },
75271
+ "deviceExport.listSupportedEntityKinds": {
75272
+ capName: "device-export",
75273
+ capScope: "system",
75274
+ addonId: null,
75275
+ access: "view",
75276
+ role: "admin"
75277
+ },
74975
75278
  "deviceExport.unexposeDevice": {
74976
75279
  capName: "device-export",
74977
75280
  capScope: "system",
@@ -74979,6 +75282,13 @@ Object.freeze({
74979
75282
  access: "create",
74980
75283
  role: "admin"
74981
75284
  },
75285
+ "deviceExport.unexposeEntity": {
75286
+ capName: "device-export",
75287
+ capScope: "system",
75288
+ addonId: null,
75289
+ access: "create",
75290
+ role: "admin"
75291
+ },
74982
75292
  "deviceExtension.listCandidates": {
74983
75293
  capName: "device-extension",
74984
75294
  capScope: "device",
@@ -78444,6 +78754,48 @@ Object.freeze({
78444
78754
  access: "create",
78445
78755
  role: "viewer"
78446
78756
  },
78757
+ "petFountain.getStatus": {
78758
+ capName: "pet-fountain",
78759
+ capScope: "device",
78760
+ addonId: null,
78761
+ access: "view",
78762
+ role: "viewer"
78763
+ },
78764
+ "petFountain.resetDailyConsumption": {
78765
+ capName: "pet-fountain",
78766
+ capScope: "device",
78767
+ addonId: null,
78768
+ access: "delete",
78769
+ role: "viewer"
78770
+ },
78771
+ "petFountain.setPump": {
78772
+ capName: "pet-fountain",
78773
+ capScope: "device",
78774
+ addonId: null,
78775
+ access: "create",
78776
+ role: "viewer"
78777
+ },
78778
+ "petFountain.setPumpInterval": {
78779
+ capName: "pet-fountain",
78780
+ capScope: "device",
78781
+ addonId: null,
78782
+ access: "create",
78783
+ role: "viewer"
78784
+ },
78785
+ "petFountain.setPumpOffMinutes": {
78786
+ capName: "pet-fountain",
78787
+ capScope: "device",
78788
+ addonId: null,
78789
+ access: "create",
78790
+ role: "viewer"
78791
+ },
78792
+ "petFountain.setPumpOnMinutes": {
78793
+ capName: "pet-fountain",
78794
+ capScope: "device",
78795
+ addonId: null,
78796
+ access: "create",
78797
+ role: "viewer"
78798
+ },
78447
78799
  "pipelineAnalytics.addRetrainFrameFromRecording": {
78448
78800
  capName: "pipeline-analytics",
78449
78801
  capScope: "device",
@@ -80845,6 +81197,13 @@ Object.freeze({
80845
81197
  access: "create",
80846
81198
  role: "admin"
80847
81199
  },
81200
+ "serverManagement.getLaunchOverrides": {
81201
+ capName: "server-management",
81202
+ capScope: "system",
81203
+ addonId: null,
81204
+ access: "view",
81205
+ role: "admin"
81206
+ },
80848
81207
  "serverManagement.getServerPackageStatus": {
80849
81208
  capName: "server-management",
80850
81209
  capScope: "system",
@@ -80866,6 +81225,13 @@ Object.freeze({
80866
81225
  access: "create",
80867
81226
  role: "admin"
80868
81227
  },
81228
+ "serverManagement.setLaunchOverrides": {
81229
+ capName: "server-management",
81230
+ capScope: "system",
81231
+ addonId: null,
81232
+ access: "create",
81233
+ role: "admin"
81234
+ },
80869
81235
  "settingsStore.aggregate": {
80870
81236
  capName: "settings-store",
80871
81237
  capScope: "system",
@@ -84926,6 +85292,100 @@ var APPLIANCE_CAP_VOCABULARY = {
84926
85292
  }
84927
85293
  ]
84928
85294
  },
85295
+ "pet-fountain": {
85296
+ scope: "device",
85297
+ noun: t("Pet fountain", "Fontana per animali"),
85298
+ actions: [
85299
+ {
85300
+ id: "petFountain.pumpOn",
85301
+ method: "setPump",
85302
+ label: t("Pump on", "Pompa accesa"),
85303
+ phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
85304
+ icon: "play",
85305
+ args: { on: true }
85306
+ },
85307
+ {
85308
+ id: "petFountain.pumpOff",
85309
+ method: "setPump",
85310
+ label: t("Pump off", "Pompa spenta"),
85311
+ phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
85312
+ icon: "square",
85313
+ args: { on: false }
85314
+ },
85315
+ {
85316
+ id: "petFountain.intervalOn",
85317
+ method: "setPumpInterval",
85318
+ label: t("Pump cycle on", "Ciclo pompa attivo"),
85319
+ phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
85320
+ icon: "timer",
85321
+ args: { on: true }
85322
+ },
85323
+ {
85324
+ id: "petFountain.intervalOff",
85325
+ method: "setPumpInterval",
85326
+ label: t("Pump cycle off", "Ciclo pompa spento"),
85327
+ phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
85328
+ icon: "timer-off",
85329
+ args: { on: false }
85330
+ },
85331
+ {
85332
+ id: "petFountain.pumpOnMinutes",
85333
+ method: "setPumpOnMinutes",
85334
+ label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
85335
+ phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
85336
+ icon: "timer",
85337
+ openArgs: [{
85338
+ key: "minutes",
85339
+ label: t("Minutes", "Minuti"),
85340
+ offer: {
85341
+ kind: "number",
85342
+ min: 0,
85343
+ step: 1,
85344
+ unit: "min"
85345
+ }
85346
+ }]
85347
+ },
85348
+ {
85349
+ id: "petFountain.pumpOffMinutes",
85350
+ method: "setPumpOffMinutes",
85351
+ label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
85352
+ phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
85353
+ icon: "timer-off",
85354
+ openArgs: [{
85355
+ key: "minutes",
85356
+ label: t("Minutes", "Minuti"),
85357
+ offer: {
85358
+ kind: "number",
85359
+ min: 0,
85360
+ step: 1,
85361
+ unit: "min"
85362
+ }
85363
+ }]
85364
+ },
85365
+ {
85366
+ id: "petFountain.resetDailyConsumption",
85367
+ method: "resetDailyConsumption",
85368
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
85369
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
85370
+ icon: "rotate-ccw"
85371
+ }
85372
+ ],
85373
+ state: [{
85374
+ kind: "boolean",
85375
+ field: "empty",
85376
+ label: t("Tank empty", "Serbatoio vuoto"),
85377
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
85378
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
85379
+ icon: "droplet"
85380
+ }, {
85381
+ kind: "numeric",
85382
+ field: "waterLevel",
85383
+ quantity: t("the water level", "il livello dell’acqua"),
85384
+ label: t("Water level", "Livello dell’acqua"),
85385
+ unit: "%",
85386
+ icon: "droplets"
85387
+ }]
85388
+ },
84929
85389
  "media-player": {
84930
85390
  scope: "device",
84931
85391
  noun: t("Media player", "Lettore multimediale"),
@@ -86863,7 +87323,7 @@ function projectAction(cap, action) {
86863
87323
  };
86864
87324
  }
86865
87325
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
86866
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
87326
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
86867
87327
  DeviceFeature.BatteryOperated;
86868
87328
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
86869
87329
  var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
@@ -88414,6 +88874,36 @@ Object.freeze({
88414
88874
  form: "single",
88415
88875
  optional: false
88416
88876
  }],
88877
+ "petFountain.getStatus": [{
88878
+ name: "deviceId",
88879
+ form: "single",
88880
+ optional: false
88881
+ }],
88882
+ "petFountain.resetDailyConsumption": [{
88883
+ name: "deviceId",
88884
+ form: "single",
88885
+ optional: false
88886
+ }],
88887
+ "petFountain.setPump": [{
88888
+ name: "deviceId",
88889
+ form: "single",
88890
+ optional: false
88891
+ }],
88892
+ "petFountain.setPumpInterval": [{
88893
+ name: "deviceId",
88894
+ form: "single",
88895
+ optional: false
88896
+ }],
88897
+ "petFountain.setPumpOffMinutes": [{
88898
+ name: "deviceId",
88899
+ form: "single",
88900
+ optional: false
88901
+ }],
88902
+ "petFountain.setPumpOnMinutes": [{
88903
+ name: "deviceId",
88904
+ form: "single",
88905
+ optional: false
88906
+ }],
88417
88907
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
88418
88908
  name: "deviceId",
88419
88909
  form: "single",
@@ -133567,6 +134057,38 @@ function alexaRegionOptions(amazonDomain) {
133567
134057
  };
133568
134058
  }
133569
134059
  //#endregion
134060
+ //#region src/alexa-url-params.ts
134061
+ var BASE = "http://placeholder.invalid";
134062
+ function describeUrlParams(url) {
134063
+ let parsed;
134064
+ try {
134065
+ parsed = new URL(url, BASE);
134066
+ } catch {
134067
+ return {
134068
+ path: "",
134069
+ names: [],
134070
+ oa2: false,
134071
+ returnTo: false,
134072
+ assocHandle: false,
134073
+ mode: false
134074
+ };
134075
+ }
134076
+ const names = [...new Set(parsed.searchParams.keys())].sort();
134077
+ return {
134078
+ path: parsed.pathname,
134079
+ names,
134080
+ oa2: names.some((n) => n.startsWith("openid.oa2.")),
134081
+ returnTo: names.includes("openid.return_to"),
134082
+ assocHandle: names.includes("openid.assoc_handle"),
134083
+ mode: names.includes("openid.mode")
134084
+ };
134085
+ }
134086
+ /** One-line, value-free rendering of {@link describeUrlParams}. */
134087
+ function formatUrlParams(url) {
134088
+ const d = describeUrlParams(url);
134089
+ return `names=[${d.names.join(",")}] oa2=${d.oa2} return_to=${d.returnTo} assoc_handle=${d.assocHandle} mode=${d.mode}`;
134090
+ }
134091
+ //#endregion
133570
134092
  //#region src/alexa-proxy-trace.ts
133571
134093
  /**
133572
134094
  * Sanitised one-line trace of the alexa-cookie2 proxy's own log lines.
@@ -133587,17 +134109,21 @@ function stripQuery(target) {
133587
134109
  const cut = target.search(/[?#]/);
133588
134110
  return cut === -1 ? target : target.slice(0, cut);
133589
134111
  }
134112
+ /** The target without its query, plus the value-free parameter summary. */
134113
+ function withParams(target) {
134114
+ return `${stripQuery(target)} ${formatUrlParams(target)}`;
134115
+ }
133590
134116
  function sanitizeProxyTraceLine(line) {
133591
134117
  const router = ROUTER_RE.exec(line);
133592
- if (router !== null) return `router ${router[2]} ${stripQuery(router[1] ?? "")}`;
134118
+ if (router !== null) return `router ${router[2]} ${withParams(router[1] ?? "")}`;
133593
134119
  const request = PROXY_REQUEST_RE.exec(line);
133594
- if (request !== null) return `proxy-request ${request[1]} ${stripQuery(request[2] ?? "")}`;
134120
+ if (request !== null) return `proxy-request ${request[1]} ${withParams(request[2] ?? "")}`;
133595
134121
  const response = PROXY_RESPONSE_RE.exec(line);
133596
134122
  if (response !== null) return `proxy-response host=${response[1]}`;
133597
134123
  const original = REDIRECT_ORIGINAL_RE.exec(line);
133598
- if (original !== null) return `redirect-original ${stripQuery(original[1] ?? "")}`;
134124
+ if (original !== null) return `redirect-original ${withParams(original[1] ?? "")}`;
133599
134125
  const final = REDIRECT_FINAL_RE.exec(line);
133600
- if (final !== null) return `redirect-final ${stripQuery(final[1] ?? "")}`;
134126
+ if (final !== null) return `redirect-final ${withParams(final[1] ?? "")}`;
133601
134127
  return null;
133602
134128
  }
133603
134129
  //#endregion
@@ -133713,7 +134239,7 @@ var AlexaProxyLoginSession = class {
133713
134239
  const line = args.map(String).join(" ");
133714
134240
  const trace = sanitizeProxyTraceLine(line);
133715
134241
  if (trace !== null) {
133716
- this.#logger.info(`alexa proxy-login: ${trace}`, { meta: { sessionId: this.#opts.sessionId } });
134242
+ this.#logger.info(`[alexa-login-trace] ${trace}`, { meta: { sessionId: this.#opts.sessionId } });
133717
134243
  return;
133718
134244
  }
133719
134245
  if (SECRET_LINE_RE.test(line)) return;
@@ -133891,6 +134417,13 @@ var AlexaProxyLoginManager = class {
133891
134417
  this.#getLoginProxy = deps.getLoginProxy;
133892
134418
  this.#captureWaitMs = deps.captureWaitMs ?? CAPTURE_WAIT_MS;
133893
134419
  }
134420
+ /** One value-free line per upstream hop the hub login proxy sees (names only). */
134421
+ #traceHop(sessionId, view) {
134422
+ const url = describeUrlParams(view.url);
134423
+ const landing = url.path.endsWith("/ap/maplanding") ? " maplanding" : "";
134424
+ const location = view.location === null ? "none" : `${describeUrlParams(view.location).path} ${formatUrlParams(view.location)}`;
134425
+ this.#logger.info(`[alexa-login-trace] hub-hop${landing} status=${view.status} url=${url.path} ${formatUrlParams(view.url)} location=${location}`, { meta: { sessionId } });
134426
+ }
133894
134427
  /**
133895
134428
  * Start a fresh proxy-login. Cancels any in-flight session first (single
133896
134429
  * fixed port). Returns the `loginUrl` for the onboarding UI to open.
@@ -133909,7 +134442,10 @@ var AlexaProxyLoginManager = class {
133909
134442
  host: upstreamHost,
133910
134443
  scheme: "http"
133911
134444
  }],
133912
- terminal: cookieSuccessTerminal,
134445
+ terminal: (view) => {
134446
+ this.#traceHop(sessionId, view);
134447
+ return cookieSuccessTerminal(view);
134448
+ },
133913
134449
  onDone: async (end) => {
133914
134450
  if (end === "no_authorization_code") {
133915
134451
  this.#logger.warn("alexa proxy-login: Amazon ended the login without an authorization code", { meta: { sessionId } });