@camstack/types 1.1.15 → 1.1.17

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-BQzZ7joF.js");
2
+ const require_sleep = require("./sleep-BabrCASa.js");
3
3
  const require_err_msg = require("./err-msg-COpsHMw2.js");
4
4
  let zod = require("zod");
5
5
  //#region src/health/wiring-health.ts
@@ -832,7 +832,21 @@ var StorageLocationDeclarationSchema = zod.z.object({
832
832
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
833
833
  * configure the primary location.
834
834
  */
835
- defaultsTo: zod.z.string().optional()
835
+ defaultsTo: zod.z.string().optional(),
836
+ /**
837
+ * Which node root the seeded `<id>:default` instance is placed under on a
838
+ * FRESH install:
839
+ * - `'data'` (default) — the node's data dir (`CAMSTACK_DATA` / boot dir),
840
+ * the appData volume. Right for small/durable data (backups, logs, models).
841
+ * - `'media'` — the dedicated media volume (`CAMSTACK_MEDIA_ROOT`) when that
842
+ * env is set, else falls back to the data root. Right for bulky, hot media
843
+ * (recordings, event media) that should stay off the appData disk.
844
+ *
845
+ * Only affects the seeded default's `basePath`; operators can repoint any
846
+ * location afterwards, and a `defaultsTo` slot inherits its parent's root
847
+ * regardless of this field. Absent (the common case) is treated as `'data'`.
848
+ */
849
+ defaultRoot: zod.z.enum(["data", "media"]).optional()
836
850
  });
837
851
  //#endregion
838
852
  //#region src/interfaces/device-capabilities/camera.ts
@@ -2758,6 +2772,203 @@ function createRuntimeStateBridge(params) {
2758
2772
  };
2759
2773
  }
2760
2774
  //#endregion
2775
+ //#region src/notification/format-transcode.ts
2776
+ /** Strip a small, safe subset of Markdown down to plain text. */
2777
+ function markdownToText(md) {
2778
+ return md.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/(\*\*|__)(.*?)\1/g, "$2").replace(/(\*|_)(.*?)\1/g, "$2").replace(/`([^`]*)`/g, "$1").replace(/^\s{0,3}#{1,6}\s*/gm, "").replace(/^\s{0,3}>\s?/gm, "").replace(/^\s{0,3}[-*+]\s+/gm, "").replace(/^\s{0,3}\d+\.\s+/gm, "").trim();
2779
+ }
2780
+ var HTML_ENTITIES = [
2781
+ [/&nbsp;/g, " "],
2782
+ [/&amp;/g, "&"],
2783
+ [/&lt;/g, "<"],
2784
+ [/&gt;/g, ">"],
2785
+ [/&quot;/g, "\""],
2786
+ [/&#39;/g, "'"]
2787
+ ];
2788
+ /** Strip HTML tags, decode a few entities, map block/br elements to newlines. */
2789
+ function htmlToText(html) {
2790
+ let out = html.replace(/<\s*br\s*\/?\s*>/gi, "\n").replace(/<\s*\/\s*(p|div|li|h[1-6])\s*>/gi, "\n").replace(/<[^>]+>/g, "");
2791
+ for (const [re, rep] of HTML_ENTITIES) out = out.replace(re, rep);
2792
+ return out.replace(/\n{3,}/g, "\n\n").trim();
2793
+ }
2794
+ var HTML_ESCAPES = [
2795
+ [/&/g, "&amp;"],
2796
+ [/</g, "&lt;"],
2797
+ [/>/g, "&gt;"],
2798
+ [/"/g, "&quot;"],
2799
+ [/'/g, "&#39;"]
2800
+ ];
2801
+ function escapeHtml(text) {
2802
+ let out = text;
2803
+ for (const [re, rep] of HTML_ESCAPES) out = out.replace(re, rep);
2804
+ return out;
2805
+ }
2806
+ /** Plain text → HTML-lite (escape + newlines to <br>). */
2807
+ function textToHtml(text) {
2808
+ return escapeHtml(text).replace(/\n/g, "<br>");
2809
+ }
2810
+ /** Markdown → HTML-lite (bold/italic/code/links; used e.g. for Pushover). */
2811
+ function markdownToHtmlLite(md) {
2812
+ return escapeHtml(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
2813
+ }
2814
+ /**
2815
+ * Preference order when the source format is NOT supported — first supported
2816
+ * entry wins. Encodes the Apprise-style "degrade DOWN, but preserve rendering
2817
+ * where a richer target exists" policy:
2818
+ * - markdown → html (html-lite) before text (strip)
2819
+ * - html → text before markdown
2820
+ * - text → markdown before html (text is valid markdown)
2821
+ */
2822
+ var DEGRADE_ORDER = {
2823
+ text: [
2824
+ "text",
2825
+ "markdown",
2826
+ "html"
2827
+ ],
2828
+ markdown: [
2829
+ "markdown",
2830
+ "html",
2831
+ "text"
2832
+ ],
2833
+ html: [
2834
+ "html",
2835
+ "text",
2836
+ "markdown"
2837
+ ]
2838
+ };
2839
+ /** Pick the best target format the kind supports for a given source format. */
2840
+ function resolveFormat(source, supported) {
2841
+ for (const candidate of DEGRADE_ORDER[source]) if (supported.includes(candidate)) return candidate;
2842
+ return supported[0] ?? "text";
2843
+ }
2844
+ /** Transcode a body between formats. */
2845
+ function transcodeBody(body, from, to) {
2846
+ if (from === to) return body;
2847
+ if (to === "text") {
2848
+ if (from === "markdown") return markdownToText(body);
2849
+ if (from === "html") return htmlToText(body);
2850
+ }
2851
+ if (to === "html") {
2852
+ if (from === "text") return textToHtml(body);
2853
+ if (from === "markdown") return markdownToHtmlLite(body);
2854
+ }
2855
+ if (to === "markdown") {
2856
+ if (from === "text") return body;
2857
+ if (from === "html") return htmlToText(body);
2858
+ }
2859
+ return body;
2860
+ }
2861
+ //#endregion
2862
+ //#region src/notification/degrade-engine.ts
2863
+ function toResolvedLevel(level) {
2864
+ return {
2865
+ id: level.id,
2866
+ ordinal: level.ordinal,
2867
+ ...level.flags ? { flags: level.flags } : {},
2868
+ ...level.requires ? { requires: level.requires } : {}
2869
+ };
2870
+ }
2871
+ function resolveLevel(levels, n) {
2872
+ if (levels.length === 0) return null;
2873
+ if (n.level !== void 0) {
2874
+ const explicit = levels.find((l) => l.id === n.level);
2875
+ if (explicit) return toResolvedLevel(explicit);
2876
+ }
2877
+ const withOrdinal = levels.filter((l) => l.ordinal !== null);
2878
+ if (withOrdinal.length === 0) return toResolvedLevel(levels[0]);
2879
+ const priority = n.priority;
2880
+ return toResolvedLevel([...withOrdinal].sort((a, b) => {
2881
+ const da = Math.abs((a.ordinal ?? 0) - priority);
2882
+ const db = Math.abs((b.ordinal ?? 0) - priority);
2883
+ if (da !== db) return da - db;
2884
+ return (b.ordinal ?? 0) - (a.ordinal ?? 0);
2885
+ })[0]);
2886
+ }
2887
+ function splitBody(body, maxLen) {
2888
+ if (maxLen <= 0 || body.length <= maxLen) return [body];
2889
+ const parts = [];
2890
+ for (let i = 0; i < body.length; i += maxLen) parts.push(body.slice(i, i + maxLen));
2891
+ return parts;
2892
+ }
2893
+ /**
2894
+ * Prepare a canonical notification for one kind. Never throws — degradation is
2895
+ * always reported in `renderedAs`, never surfaced as an error.
2896
+ */
2897
+ function prepareNotification(caps, n) {
2898
+ const dropped = [];
2899
+ const sourceFormat = n.format;
2900
+ const format = resolveFormat(sourceFormat, caps.format);
2901
+ if (format !== sourceFormat) dropped.push("format");
2902
+ const transcoded = transcodeBody(n.body, sourceFormat, format);
2903
+ const level = resolveLevel(caps.levels, n);
2904
+ const inAttachments = n.attachments ?? [];
2905
+ const attachments = [];
2906
+ for (const att of inAttachments) {
2907
+ if (!caps.attachments.mediaTypes.includes(att.mediaType)) {
2908
+ dropped.push(`attachment:${att.mediaType}`);
2909
+ continue;
2910
+ }
2911
+ if (caps.attachments.mode === "url" && att.url === void 0) {
2912
+ dropped.push("attachment:noUrl");
2913
+ continue;
2914
+ }
2915
+ if (att.bytes !== void 0 && caps.attachments.maxBytes !== void 0 && att.bytes.length > caps.attachments.maxBytes) {
2916
+ dropped.push("attachment:maxBytes");
2917
+ continue;
2918
+ }
2919
+ if (attachments.length >= caps.attachments.max) {
2920
+ dropped.push("attachment:max");
2921
+ continue;
2922
+ }
2923
+ const needsFetch = caps.attachments.mode === "bytes" && att.bytes === void 0 && att.url !== void 0;
2924
+ attachments.push({
2925
+ mediaType: att.mediaType,
2926
+ ...att.url !== void 0 ? { url: att.url } : {},
2927
+ ...att.bytes !== void 0 ? { bytes: att.bytes } : {},
2928
+ ...att.mime !== void 0 ? { mime: att.mime } : {},
2929
+ ...att.name !== void 0 ? { name: att.name } : {},
2930
+ needsFetch
2931
+ });
2932
+ }
2933
+ const inActions = n.actions ?? [];
2934
+ const actions = inActions.slice(0, Math.max(0, caps.actions));
2935
+ if (inActions.length > actions.length) dropped.push("actions");
2936
+ let clickUrl = null;
2937
+ if (n.clickUrl !== void 0) if (caps.clickUrl) clickUrl = n.clickUrl;
2938
+ else dropped.push("clickUrl");
2939
+ let sound = null;
2940
+ if (n.sound !== void 0) if (caps.sound) sound = n.sound;
2941
+ else dropped.push("sound");
2942
+ let ttl = null;
2943
+ if (n.ttl !== void 0) if (caps.ttl) ttl = n.ttl;
2944
+ else dropped.push("ttl");
2945
+ const tag = n.tag ?? null;
2946
+ const bodyParts = splitBody(transcoded, caps.bodyMaxLen);
2947
+ const truncated = bodyParts.length > 1;
2948
+ const renderedAs = {
2949
+ level: level?.id ?? "",
2950
+ format,
2951
+ attachmentsSent: attachments.length,
2952
+ actionsSent: actions.length,
2953
+ truncated,
2954
+ dropped
2955
+ };
2956
+ return {
2957
+ body: bodyParts[0] ?? "",
2958
+ ...n.title !== void 0 ? { title: n.title } : {},
2959
+ format,
2960
+ level,
2961
+ attachments,
2962
+ actions,
2963
+ clickUrl,
2964
+ sound,
2965
+ ttl,
2966
+ tag,
2967
+ bodyParts,
2968
+ renderedAs
2969
+ };
2970
+ }
2971
+ //#endregion
2761
2972
  //#region src/capabilities/device-status.cap.ts
2762
2973
  /**
2763
2974
  * Generic device-level status snapshot. Auto-registered by `BaseDevice`
@@ -3504,6 +3715,10 @@ var RtspRestreamEntrySchema = zod.z.object({
3504
3715
  var BrokerRtspClientSchema = zod.z.object({
3505
3716
  sessionId: zod.z.string(),
3506
3717
  remoteAddr: zod.z.string(),
3718
+ /** Client `User-Agent` (e.g. `recorder` for the recorder's RTSP pull), or
3719
+ * null/absent when the client sent none. Lets the UI label a consumer by
3720
+ * purpose. Optional so a client built against an older schema stays valid. */
3721
+ userAgent: zod.z.string().nullish(),
3507
3722
  playing: zod.z.boolean(),
3508
3723
  muted: zod.z.boolean(),
3509
3724
  connectedAt: zod.z.number(),
@@ -10324,8 +10539,14 @@ function createSystemProxy(api) {
10324
10539
  executeQuery: (input) => dispatch("nodes", "executeQuery", "mutation", input)
10325
10540
  },
10326
10541
  notificationOutput: {
10542
+ listTargetKinds: (input) => dispatch("notificationOutput", "listTargetKinds", "query", input),
10543
+ listTargets: (input) => dispatch("notificationOutput", "listTargets", "query", input),
10544
+ discoverTargets: (input) => dispatch("notificationOutput", "discoverTargets", "query", input),
10327
10545
  send: (input) => dispatch("notificationOutput", "send", "mutation", input),
10328
- sendTest: (input) => dispatch("notificationOutput", "sendTest", "mutation", input)
10546
+ testTarget: (input) => dispatch("notificationOutput", "testTarget", "mutation", input),
10547
+ upsertTarget: (input) => dispatch("notificationOutput", "upsertTarget", "mutation", input),
10548
+ deleteTarget: (input) => dispatch("notificationOutput", "deleteTarget", "mutation", input),
10549
+ setTargetEnabled: (input) => dispatch("notificationOutput", "setTargetEnabled", "mutation", input)
10329
10550
  },
10330
10551
  pipelineExecutor: {
10331
10552
  getAvailableEngines: (input) => dispatch("pipelineExecutor", "getAvailableEngines", "query", input),
@@ -11888,6 +12109,7 @@ var audioCodecCapability = {
11888
12109
  name: "audio-codec",
11889
12110
  scope: "system",
11890
12111
  mode: "singleton",
12112
+ preferredProvider: "audio-codec-ffmpeg",
11891
12113
  methods: {
11892
12114
  /** Probe the local runtime and return the supported codec matrix. */
11893
12115
  listSupportedCodecs: require_sleep.method(zod.z.void(), zod.z.array(AudioCodecInfoSchema).readonly()),
@@ -14367,7 +14589,7 @@ var AddBrokerInputSchema = zod.z.object({
14367
14589
  });
14368
14590
  var AddBrokerResultSchema = zod.z.object({ id: zod.z.string() });
14369
14591
  var IdInputSchema = zod.z.object({ id: zod.z.string() });
14370
- var TestResultSchema = zod.z.discriminatedUnion("ok", [zod.z.object({
14592
+ var TestResultSchema$1 = zod.z.discriminatedUnion("ok", [zod.z.object({
14371
14593
  ok: zod.z.literal(true),
14372
14594
  latencyMs: zod.z.number()
14373
14595
  }), zod.z.object({
@@ -14404,7 +14626,7 @@ var mqttBrokerCapability = {
14404
14626
  getBrokerConfig: require_sleep.method(IdInputSchema, BrokerConnectionDetailsSchema),
14405
14627
  addBroker: require_sleep.method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }),
14406
14628
  removeBroker: require_sleep.method(IdInputSchema, zod.z.void(), { kind: "mutation" }),
14407
- testConnection: require_sleep.method(IdInputSchema, TestResultSchema, { kind: "mutation" }),
14629
+ testConnection: require_sleep.method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }),
14408
14630
  startEmbeddedBroker: require_sleep.method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }),
14409
14631
  stopEmbeddedBroker: require_sleep.method(IdInputSchema, zod.z.void(), { kind: "mutation" }),
14410
14632
  getStatus: require_sleep.method(zod.z.void(), StatusSchema)
@@ -14464,30 +14686,212 @@ var networkAccessCapability = {
14464
14686
  };
14465
14687
  //#endregion
14466
14688
  //#region src/capabilities/notification-output.cap.ts
14689
+ /**
14690
+ * notification-output — canonical, capability-gated notification delivery.
14691
+ *
14692
+ * Apprise-derived model (see
14693
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
14694
+ * callers emit ONE canonical `Notification`; each provider declares a
14695
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
14696
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
14697
+ * message to what the kind supports — callers never special-case a service.
14698
+ *
14699
+ * DESIGN DECISIONS (locked):
14700
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
14701
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
14702
+ * cap. Rationale: the admin UI needs one uniform surface across the
14703
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
14704
+ * alternative would fork the UI per addon and cannot host the
14705
+ * discovery→adopt flow.
14706
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
14707
+ * the generated cap-mount auto-`concatCollection`-fans them across every
14708
+ * registered provider (notifiers addon + HA addon) so one catalog is
14709
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
14710
+ * `addonId` the generated collection router extracts from the call input.
14711
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
14712
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
14713
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
14714
+ * base64 fallback needed.
14715
+ *
14716
+ * TODO (deferred, closed-set change — separate decision): add
14717
+ * `providerKind: 'notify'` so notification providers surface on the unified
14718
+ * admin "Integrations" page.
14719
+ */
14720
+ /**
14721
+ * Zentik-derived typed-media enum — the superset across every kind. Each
14722
+ * adapter picks what it supports and the degrade engine filters the rest.
14723
+ */
14724
+ var AttachmentMediaTypeSchema = zod.z.enum([
14725
+ "image",
14726
+ "video",
14727
+ "gif",
14728
+ "audio",
14729
+ "icon"
14730
+ ]);
14731
+ /**
14732
+ * A single attachment. Exactly one of `url` (remote source, most adapters
14733
+ * prefer this) or `bytes` (inline source; required for Pushover-style
14734
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
14735
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
14736
+ */
14737
+ var AttachmentSchema = zod.z.object({
14738
+ mediaType: AttachmentMediaTypeSchema,
14739
+ url: zod.z.string().optional(),
14740
+ bytes: zod.z.instanceof(Uint8Array).optional(),
14741
+ mime: zod.z.string().optional(),
14742
+ name: zod.z.string().optional()
14743
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
14744
+ var NotificationFormatSchema = zod.z.enum([
14745
+ "text",
14746
+ "markdown",
14747
+ "html"
14748
+ ]);
14749
+ /** A single tap-through action button. */
14750
+ var NotificationActionSchema = zod.z.object({
14751
+ id: zod.z.string(),
14752
+ label: zod.z.string(),
14753
+ url: zod.z.string().optional()
14754
+ });
14755
+ /**
14756
+ * The canonical notification. `body` is the only hard field (Apprise model).
14757
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
14758
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
14759
+ * the adapter maps this ordinal onto its native level. `level?` is an
14760
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
14761
+ * `priority` for that one target.
14762
+ */
14467
14763
  var NotificationSchema = zod.z.object({
14468
- title: zod.z.string(),
14469
14764
  body: zod.z.string(),
14470
- imageUrl: zod.z.string().optional(),
14765
+ title: zod.z.string().optional(),
14766
+ format: NotificationFormatSchema.default("text"),
14767
+ priority: zod.z.number().int().min(1).max(5).default(3),
14768
+ level: zod.z.string().optional(),
14769
+ attachments: zod.z.array(AttachmentSchema).optional(),
14770
+ clickUrl: zod.z.string().optional(),
14771
+ actions: zod.z.array(NotificationActionSchema).optional(),
14772
+ sound: zod.z.string().optional(),
14773
+ ttl: zod.z.number().optional(),
14774
+ tag: zod.z.string().optional(),
14471
14775
  deviceId: zod.z.number().optional(),
14472
14776
  eventId: zod.z.string().optional(),
14473
- priority: zod.z.enum([
14474
- "low",
14475
- "normal",
14476
- "high",
14477
- "critical"
14478
- ]).default("normal"),
14479
14777
  metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
14480
14778
  });
14779
+ /** One declared native severity/priority level for a kind. */
14780
+ var TargetKindLevelSchema = zod.z.object({
14781
+ id: zod.z.string(),
14782
+ label: zod.z.string(),
14783
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
14784
+ ordinal: zod.z.number().int().min(1).max(5).nullable(),
14785
+ flags: zod.z.object({
14786
+ critical: zod.z.boolean().optional(),
14787
+ silent: zod.z.boolean().optional(),
14788
+ noPush: zod.z.boolean().optional()
14789
+ }).optional(),
14790
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
14791
+ requires: zod.z.array(zod.z.string()).optional(),
14792
+ description: zod.z.string().optional()
14793
+ });
14794
+ /** Attachment capabilities for a kind (drives the degrade engine + test panel). */
14795
+ var TargetKindAttachmentsCapsSchema = zod.z.object({
14796
+ mediaTypes: zod.z.array(AttachmentMediaTypeSchema),
14797
+ mode: zod.z.enum([
14798
+ "url",
14799
+ "bytes",
14800
+ "both"
14801
+ ]),
14802
+ max: zod.z.number().int().nonnegative(),
14803
+ maxBytes: zod.z.number().int().positive().optional()
14804
+ });
14805
+ /** The full capability block consulted before dispatch. */
14806
+ var TargetKindCapsSchema = zod.z.object({
14807
+ attachments: TargetKindAttachmentsCapsSchema,
14808
+ /** Max action buttons (0 = none). */
14809
+ actions: zod.z.number().int().nonnegative(),
14810
+ levels: zod.z.array(TargetKindLevelSchema),
14811
+ format: zod.z.array(NotificationFormatSchema),
14812
+ clickUrl: zod.z.boolean(),
14813
+ sound: zod.z.boolean(),
14814
+ ttl: zod.z.boolean(),
14815
+ bodyMaxLen: zod.z.number().int().positive()
14816
+ });
14817
+ /**
14818
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
14819
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
14820
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
14821
+ * the union is large and not meant for runtime validation here; the exported
14822
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14823
+ */
14824
+ var ConfigSchemaPassthrough = zod.z.unknown();
14825
+ var TargetKindSchema = zod.z.object({
14826
+ kind: zod.z.string(),
14827
+ label: zod.z.string(),
14828
+ icon: zod.z.string(),
14829
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
14830
+ addonId: zod.z.string(),
14831
+ configSchema: ConfigSchemaPassthrough,
14832
+ supportsDiscovery: zod.z.boolean(),
14833
+ caps: TargetKindCapsSchema
14834
+ });
14835
+ /**
14836
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
14837
+ * (return a presence marker only) when serving `listTargets` — never
14838
+ * round-trip a stored secret to the UI.
14839
+ */
14840
+ var TargetSchema = zod.z.object({
14841
+ id: zod.z.string(),
14842
+ name: zod.z.string(),
14843
+ kind: zod.z.string(),
14844
+ addonId: zod.z.string(),
14845
+ enabled: zod.z.boolean(),
14846
+ config: zod.z.record(zod.z.string(), zod.z.unknown())
14847
+ });
14848
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
14849
+ var DiscoveredTargetSchema = zod.z.object({
14850
+ kind: zod.z.string(),
14851
+ suggestedName: zod.z.string(),
14852
+ config: zod.z.record(zod.z.string(), zod.z.unknown())
14853
+ });
14854
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
14855
+ var RenderedAsSchema = zod.z.object({
14856
+ level: zod.z.string(),
14857
+ format: NotificationFormatSchema,
14858
+ attachmentsSent: zod.z.number().int().nonnegative(),
14859
+ actionsSent: zod.z.number().int().nonnegative(),
14860
+ truncated: zod.z.boolean(),
14861
+ dropped: zod.z.array(zod.z.string())
14862
+ });
14863
+ var SendResultSchema = zod.z.object({
14864
+ success: zod.z.boolean(),
14865
+ error: zod.z.string().optional(),
14866
+ renderedAs: RenderedAsSchema.optional()
14867
+ });
14868
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
14869
+ var TestResultSchema = SendResultSchema;
14481
14870
  var notificationOutputCapability = {
14482
14871
  name: "notification-output",
14483
14872
  scope: "system",
14484
14873
  mode: "collection",
14485
14874
  methods: {
14486
- send: require_sleep.method(NotificationSchema, zod.z.void(), { kind: "mutation" }),
14487
- sendTest: require_sleep.method(zod.z.void(), zod.z.object({
14488
- success: zod.z.boolean(),
14489
- error: zod.z.string().optional()
14490
- }), { kind: "mutation" })
14875
+ listTargetKinds: require_sleep.method(zod.z.object({}), zod.z.array(TargetKindSchema)),
14876
+ listTargets: require_sleep.method(zod.z.object({}), zod.z.array(TargetSchema)),
14877
+ discoverTargets: require_sleep.method(zod.z.object({
14878
+ kind: zod.z.string(),
14879
+ config: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
14880
+ }), zod.z.array(DiscoveredTargetSchema)),
14881
+ send: require_sleep.method(zod.z.object({
14882
+ targetId: zod.z.string(),
14883
+ notification: NotificationSchema
14884
+ }), SendResultSchema, { kind: "mutation" }),
14885
+ testTarget: require_sleep.method(zod.z.object({
14886
+ targetId: zod.z.string(),
14887
+ sample: NotificationSchema.optional()
14888
+ }), TestResultSchema, { kind: "mutation" }),
14889
+ upsertTarget: require_sleep.method(zod.z.object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }),
14890
+ deleteTarget: require_sleep.method(zod.z.object({ targetId: zod.z.string() }), zod.z.void(), { kind: "mutation" }),
14891
+ setTargetEnabled: require_sleep.method(zod.z.object({
14892
+ targetId: zod.z.string(),
14893
+ enabled: zod.z.boolean()
14894
+ }), zod.z.void(), { kind: "mutation" })
14491
14895
  }
14492
14896
  };
14493
14897
  //#endregion
@@ -15359,10 +15763,11 @@ var pipelineOrchestratorCapability = {
15359
15763
  }))),
15360
15764
  /**
15361
15765
  * Get one camera's decoder placement (computed if not yet pinned).
15362
- * Consumed by `stream-broker.createBroker` so decoder provider
15363
- * selection is deterministic fixes the 2026-04-18 race where
15364
- * `capProviders[0]` silently picked ffmpeg-on-agent-0 for a
15365
- * hub-assigned camera.
15766
+ *
15767
+ * ADVISORY today: reports the orchestrator's decoder preference only.
15768
+ * Actual decode placement is broker-owned (local-node pin + frame-plane
15769
+ * co-location guard). Reserved to become the binding source/decoder-owner
15770
+ * control in the stream-LB epic (Phase 2).
15366
15771
  *
15367
15772
  * `pipelineNodeId` is the node already chosen to run inference for
15368
15773
  * this camera. When provided, the balancer prefers co-location with
@@ -23259,13 +23664,49 @@ var METHOD_ACCESS_MAP = Object.freeze({
23259
23664
  addonId: null,
23260
23665
  access: "create"
23261
23666
  },
23667
+ "notificationOutput.deleteTarget": {
23668
+ capName: "notification-output",
23669
+ capScope: "system",
23670
+ addonId: null,
23671
+ access: "delete"
23672
+ },
23673
+ "notificationOutput.discoverTargets": {
23674
+ capName: "notification-output",
23675
+ capScope: "system",
23676
+ addonId: null,
23677
+ access: "view"
23678
+ },
23679
+ "notificationOutput.listTargetKinds": {
23680
+ capName: "notification-output",
23681
+ capScope: "system",
23682
+ addonId: null,
23683
+ access: "view"
23684
+ },
23685
+ "notificationOutput.listTargets": {
23686
+ capName: "notification-output",
23687
+ capScope: "system",
23688
+ addonId: null,
23689
+ access: "view"
23690
+ },
23262
23691
  "notificationOutput.send": {
23263
23692
  capName: "notification-output",
23264
23693
  capScope: "system",
23265
23694
  addonId: null,
23266
23695
  access: "create"
23267
23696
  },
23268
- "notificationOutput.sendTest": {
23697
+ "notificationOutput.setTargetEnabled": {
23698
+ capName: "notification-output",
23699
+ capScope: "system",
23700
+ addonId: null,
23701
+ access: "create"
23702
+ },
23703
+ "notificationOutput.testTarget": {
23704
+ capName: "notification-output",
23705
+ capScope: "system",
23706
+ addonId: null,
23707
+ access: "create"
23708
+ },
23709
+ "notificationOutput.upsertTarget": {
23269
23710
  capName: "notification-output",
23270
23711
  capScope: "system",
23271
23712
  addonId: null,
@@ -26034,6 +26475,8 @@ exports.ApiKeyRecordSchema = ApiKeyRecordSchema;
26034
26475
  exports.ApiKeySummarySchema = ApiKeySummarySchema;
26035
26476
  exports.ArchiveEntrySchema = ArchiveEntrySchema;
26036
26477
  exports.ArchiveManifestSchema = ArchiveManifestSchema;
26478
+ exports.AttachmentMediaTypeSchema = AttachmentMediaTypeSchema;
26479
+ exports.AttachmentSchema = AttachmentSchema;
26037
26480
  exports.AudioAnalysisResultSchema = AudioAnalysisResultSchema;
26038
26481
  exports.AudioAnalysisSettingsSchema = AudioAnalysisSettingsSchema;
26039
26482
  exports.AudioChunkInputSchema = AudioChunkInputSchema;
@@ -26191,6 +26634,7 @@ exports.DeviceType = require_sleep.DeviceType;
26191
26634
  exports.DiscoveredChildDeviceSchema = DiscoveredChildDeviceSchema;
26192
26635
  exports.DiscoveredChildStatusSchema = DiscoveredChildStatusSchema;
26193
26636
  exports.DiscoveredDeviceSchema = DiscoveredDeviceSchema;
26637
+ exports.DiscoveredTargetSchema = DiscoveredTargetSchema;
26194
26638
  exports.DisposerChain = require_sleep.DisposerChain;
26195
26639
  exports.DoorbellPressEventSchema = DoorbellPressEventSchema;
26196
26640
  exports.DoorbellStatusSchema = DoorbellStatusSchema;
@@ -26297,6 +26741,8 @@ exports.NativeObjectDetectionStatusSchema = NativeObjectDetectionStatusSchema;
26297
26741
  exports.NetworkAccessStatusSchema = NetworkAccessStatusSchema;
26298
26742
  exports.NetworkAddressSchema = NetworkAddressSchema;
26299
26743
  exports.NetworkEndpointSchema = NetworkEndpointSchema;
26744
+ exports.NotificationActionSchema = NotificationActionSchema;
26745
+ exports.NotificationFormatSchema = NotificationFormatSchema;
26300
26746
  exports.NotificationHistoryEntrySchema = NotificationHistoryEntrySchema;
26301
26747
  exports.NotificationRuleSchema = NotificationRuleSchema;
26302
26748
  exports.NotificationSchema = NotificationSchema;
@@ -26377,6 +26823,7 @@ exports.RecordingStorageUsageSchema = RecordingStorageUsageSchema;
26377
26823
  exports.RecordingTriggersSchema = RecordingTriggersSchema;
26378
26824
  exports.RecordingWeekdaySchema = RecordingWeekdaySchema;
26379
26825
  exports.RegisteredStreamSchema = RegisteredStreamSchema;
26826
+ exports.RenderedAsSchema = RenderedAsSchema;
26380
26827
  exports.ReportMotionInputSchema = ReportMotionInputSchema;
26381
26828
  exports.RingBuffer = RingBuffer;
26382
26829
  exports.RtpSourceSchema = RtpSourceSchema;
@@ -26398,6 +26845,7 @@ exports.ScriptRunnerStatusSchema = ScriptRunnerStatusSchema;
26398
26845
  exports.SearchResultSchema = SearchResultSchema;
26399
26846
  exports.SendEmailInputSchema = SendEmailInputSchema;
26400
26847
  exports.SendEmailResultSchema = SendEmailResultSchema;
26848
+ exports.SendResultSchema = SendResultSchema;
26401
26849
  exports.SettingsPatchSchema = SettingsPatchSchema;
26402
26850
  exports.SettingsRecordSchema = SettingsRecordSchema;
26403
26851
  exports.SettingsSchemaWithValuesSchema = SettingsSchemaWithValuesSchema;
@@ -26447,8 +26895,13 @@ exports.SystemMirror = SystemMirror;
26447
26895
  exports.TIMEZONES = TIMEZONES;
26448
26896
  exports.TamperStatusSchema = TamperStatusSchema;
26449
26897
  exports.TankStatusSchema = TankStatusSchema;
26898
+ exports.TargetKindCapsSchema = TargetKindCapsSchema;
26899
+ exports.TargetKindLevelSchema = TargetKindLevelSchema;
26900
+ exports.TargetKindSchema = TargetKindSchema;
26901
+ exports.TargetSchema = TargetSchema;
26450
26902
  exports.TemperatureSensorStatusSchema = TemperatureSensorStatusSchema;
26451
26903
  exports.TestConnectionResultSchema = TestConnectionResultSchema$1;
26904
+ exports.TestResultSchema = TestResultSchema;
26452
26905
  exports.ToastSchema = ToastSchema;
26453
26906
  exports.TokenScopeSchema = TokenScopeSchema;
26454
26907
  exports.TopologyNodeSchema = TopologyNodeSchema;
@@ -26594,6 +27047,7 @@ exports.getAudioMacroClassIds = getAudioMacroClassIds;
26594
27047
  exports.getByPath = getByPath;
26595
27048
  exports.getCapsByProviderKind = getCapsByProviderKind;
26596
27049
  exports.hfModelUrl = hfModelUrl;
27050
+ exports.htmlToText = htmlToText;
26597
27051
  exports.humidifierCapability = humidifierCapability;
26598
27052
  exports.humiditySensorCapability = humiditySensorCapability;
26599
27053
  exports.hydrateSchema = require_sleep.hydrateSchema;
@@ -26617,6 +27071,8 @@ exports.logDestinationCapability = logDestinationCapability;
26617
27071
  exports.makeProfileBrokerId = require_sleep.makeProfileBrokerId;
26618
27072
  exports.makeSourceBrokerId = require_sleep.makeSourceBrokerId;
26619
27073
  exports.mapAudioLabelToMacro = mapAudioLabelToMacro;
27074
+ exports.markdownToHtmlLite = markdownToHtmlLite;
27075
+ exports.markdownToText = markdownToText;
26620
27076
  exports.maskUrlCredentials = maskUrlCredentials;
26621
27077
  exports.mediaPlayerCapability = mediaPlayerCapability;
26622
27078
  exports.mergeSourceInfo = mergeSourceInfo;
@@ -26659,6 +27115,7 @@ exports.pipelineRunnerCapability = pipelineRunnerCapability;
26659
27115
  exports.plateGalleryCapability = plateGalleryCapability;
26660
27116
  exports.platformProbeCapability = platformProbeCapability;
26661
27117
  exports.powerMeterCapability = powerMeterCapability;
27118
+ exports.prepareNotification = prepareNotification;
26662
27119
  exports.presenceCapability = presenceCapability;
26663
27120
  exports.pressureSensorCapability = pressureSensorCapability;
26664
27121
  exports.privacyMaskCapability = privacyMaskCapability;
@@ -26678,6 +27135,7 @@ exports.resolveAddonRuntime = resolveAddonRuntime;
26678
27135
  exports.resolveCapMount = require_sleep.resolveCapMount;
26679
27136
  exports.resolveDetectionRuntime = resolveDetectionRuntime;
26680
27137
  exports.resolveDeviceProfile = resolveDeviceProfile;
27138
+ exports.resolveFormat = resolveFormat;
26681
27139
  exports.resolveModelFormat = resolveModelFormat;
26682
27140
  exports.resolveRunnerId = resolveRunnerId;
26683
27141
  exports.restreamerCapability = restreamerCapability;
@@ -26714,9 +27172,11 @@ exports.taskLogEntrySchema = taskLogEntrySchema;
26714
27172
  exports.taskPhaseSchema = taskPhaseSchema;
26715
27173
  exports.taskTargetSchema = taskTargetSchema;
26716
27174
  exports.temperatureSensorCapability = temperatureSensorCapability;
27175
+ exports.textToHtml = textToHtml;
26717
27176
  exports.toDeviceSummary = toDeviceSummary;
26718
27177
  exports.toStreamSourceEntry = toStreamSourceEntry;
26719
27178
  exports.toastCapability = toastCapability;
27179
+ exports.transcodeBody = transcodeBody;
26720
27180
  exports.turnProviderCapability = turnProviderCapability;
26721
27181
  exports.updateCapability = updateCapability;
26722
27182
  exports.userManagementCapability = userManagementCapability;