@camstack/addon-notifiers 1.1.23 → 1.1.24

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.
Files changed (3) hide show
  1. package/dist/addon.js +1354 -51
  2. package/dist/addon.mjs +1354 -51
  3. package/package.json +4 -1
package/dist/addon.js CHANGED
@@ -2,6 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
+ let node_zlib = require("node:zlib");
5
6
  //#region ../../node_modules/zod/v4/core/core.js
6
7
  var _a$1;
7
8
  function $constructor(name, initializer, params) {
@@ -4290,6 +4291,14 @@ function object(shape, params) {
4290
4291
  ...normalizeParams(params)
4291
4292
  });
4292
4293
  }
4294
+ function looseObject(shape, params) {
4295
+ return new ZodObject({
4296
+ type: "object",
4297
+ shape,
4298
+ catchall: unknown(),
4299
+ ...normalizeParams(params)
4300
+ });
4301
+ }
4293
4302
  var ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
4294
4303
  $ZodUnion.init(inst, def);
4295
4304
  ZodType.init(inst, def);
@@ -4631,7 +4640,7 @@ function _instanceof(cls, params = {}) {
4631
4640
  return inst;
4632
4641
  }
4633
4642
  //#endregion
4634
- //#region ../types/dist/sleep-Baang_XW.mjs
4643
+ //#region ../types/dist/sleep-_sv7WKkq.mjs
4635
4644
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4636
4645
  EventCategory["SystemBoot"] = "system.boot";
4637
4646
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5041,10 +5050,51 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5041
5050
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
5042
5051
  EventCategory["EnrichmentSceneStateChanged"] = "enrichment.scene.state-changed";
5043
5052
  EventCategory["EnrichmentActivitySummary"] = "enrichment.activity.summary";
5053
+ /**
5054
+ * Unified track-lifecycle event: ONE category carrying `phase:
5055
+ * 'start' | 'update' | 'end'` with a rich, typed
5056
+ * `PipelineAnalyticsTrackLifecyclePayload`. Supersedes the thin
5057
+ * `PipelineAnalyticsTrackStarted` / `PipelineAnalyticsTrackEnded`
5058
+ * pair — a consumer subscribes once and branches on `phase`.
5059
+ */
5060
+ EventCategory["PipelineAnalyticsTrackLifecycle"] = "pipeline-analytics.track-lifecycle";
5061
+ /**
5062
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5063
+ * (`phase:'start'`). Kept as a soft alias — no known subscribers.
5064
+ */
5044
5065
  EventCategory["PipelineAnalyticsTrackStarted"] = "pipeline-analytics.track-started";
5066
+ /**
5067
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5068
+ * (`phase:'end'`). Kept as a soft alias — no known subscribers.
5069
+ */
5045
5070
  EventCategory["PipelineAnalyticsTrackEnded"] = "pipeline-analytics.track-ended";
5046
5071
  EventCategory["PipelineAnalyticsDetectionEvent"] = "pipeline-analytics.detection-event";
5047
5072
  EventCategory["PipelineAnalyticsFrameTracked"] = "pipeline-analytics.frame-tracked";
5073
+ /**
5074
+ * Fired by `addon-post-analysis` when a parked (stationary) object appears
5075
+ * (a track was promoted to a stationary-registry entry) or departs (the
5076
+ * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
5077
+ * the dedicated "Stationary" section — never the live event feed. Payload:
5078
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
5079
+ */
5080
+ EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
5081
+ /**
5082
+ * Fired by `addon-post-analysis` whenever a gallery face row changes:
5083
+ * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
5084
+ * `'unassigned'` its identity link changed, `'deleted'` the row was
5085
+ * removed. Telemetry semantics (D8): a lost event only delays a refresh,
5086
+ * never a correctness issue. Payload `PipelineAnalyticsFaceGalleryChangedPayload`.
5087
+ */
5088
+ EventCategory["PipelineAnalyticsFaceGalleryChanged"] = "pipeline-analytics.face-gallery-changed";
5089
+ /**
5090
+ * Fired by `addon-post-analysis` whenever a gallery plate row changes:
5091
+ * `kind:'buffered'` a new read was persisted (`PlateRecognizer.onTrackEnd`),
5092
+ * `'assigned'` / `'unassigned'` its vehicle link changed
5093
+ * (`PlateGalleryProvider`), `'deleted'` the row was removed. Telemetry
5094
+ * semantics (D8): a lost event only delays a refresh, never a correctness
5095
+ * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
5096
+ */
5097
+ EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
5048
5098
  EventCategory["FrigateLiveEvent"] = "frigate.live-event";
5049
5099
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
5050
5100
  /**
@@ -6873,7 +6923,8 @@ var MODEL_FORMATS = [
6873
6923
  "coreml",
6874
6924
  "openvino",
6875
6925
  "tflite",
6876
- "pt"
6926
+ "pt",
6927
+ "gguf"
6877
6928
  ];
6878
6929
  /**
6879
6930
  * Multi-file format payload.
@@ -6917,7 +6968,8 @@ var ModelFormatsSchema = object({
6917
6968
  coreml: ModelFormatEntrySchema.optional(),
6918
6969
  openvino: ModelFormatEntrySchema.optional(),
6919
6970
  tflite: ModelFormatEntrySchema.optional(),
6920
- pt: ModelFormatEntrySchema.optional()
6971
+ pt: ModelFormatEntrySchema.optional(),
6972
+ gguf: ModelFormatEntrySchema.optional()
6921
6973
  });
6922
6974
  /**
6923
6975
  * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
@@ -6958,6 +7010,15 @@ var ModelCatalogEntrySchema = object({
6958
7010
  width: number(),
6959
7011
  height: number()
6960
7012
  }),
7013
+ /**
7014
+ * Channel count of the model input tensor. Omit ⇒ 3 (RGB), the default for
7015
+ * every detector / classifier / embedder. Set to 1 for a grayscale CTC text
7016
+ * recognizer (EasyOCR VGG plate-OCR: input `[N,1,H,W]`) so the preprocess
7017
+ * feeds a single-channel, EasyOCR-normalized tensor instead of the default
7018
+ * 3-channel RGB one. Threaded through `PoolModelConfig.inputChannels` to the
7019
+ * Python inference pool.
7020
+ */
7021
+ inputChannels: number().int().positive().optional(),
6961
7022
  labels: array(LabelDefinitionSchema).readonly(),
6962
7023
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
6963
7024
  inputNormalization: _enum([
@@ -6967,6 +7028,16 @@ var ModelCatalogEntrySchema = object({
6967
7028
  ]).optional(),
6968
7029
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
6969
7030
  /**
7031
+ * Per-MODEL postprocessor override. Absent ⇒ the step's own
7032
+ * `StepDefinition.postprocessor` applies (the normal case — every model in a
7033
+ * step shares its decode). Set it when a step hosts models with DIFFERENT raw
7034
+ * output layouts under one slot: e.g. object-detection is `'yolo'` by default,
7035
+ * but a Coral SSD MobileNet build emits the `TFLite_Detection_PostProcess`
7036
+ * 4-tensor layout and needs `'ssd'`. Threaded into `PoolModelConfig.postprocessor`
7037
+ * by the engine factory (`modelEntry.postprocessor ?? def.postprocessor`).
7038
+ */
7039
+ postprocessor: custom().optional(),
7040
+ /**
6970
7041
  * When true, the executor produces a landmark-aligned crop (similarity warp
6971
7042
  * onto the canonical template) before this step runs, instead of a plain
6972
7043
  * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
@@ -7056,6 +7127,123 @@ var ConvertResultSchema = object({
7056
7127
  })).readonly()
7057
7128
  });
7058
7129
  /**
7130
+ * Build an `IAddonRouteProvider` from a list of routes. Implements
7131
+ * both the operator-facing `getRoutes` (returning route descriptors
7132
+ * minus the handlers, which can't cross JSON) and the framework-
7133
+ * private `invoke` method that the hub calls when this provider lives
7134
+ * in a forked worker.
7135
+ *
7136
+ * Co-located addons use the returned `getRoutes` directly because
7137
+ * their handlers don't need to cross any wire. The `invoke` method
7138
+ * is present anyway so the bridge code on the hub is uniform — it
7139
+ * doesn't need to switch on "local vs remote provider" at the call
7140
+ * site.
7141
+ *
7142
+ * Example:
7143
+ * const routes: IAddonHttpRoute[] = [
7144
+ * { method: 'GET', path: '/start', access: 'public', handler: this.handleStart },
7145
+ * ]
7146
+ * return [
7147
+ * {
7148
+ * capability: addonRoutesCapability,
7149
+ * provider: buildAddonRouteProvider('auth-oidc', routes),
7150
+ * },
7151
+ * ]
7152
+ */
7153
+ function buildAddonRouteProvider(id, routes) {
7154
+ return {
7155
+ id,
7156
+ getRoutes: () => routes,
7157
+ invoke: async (input) => {
7158
+ const match = matchRoute(routes, input.method, input.path);
7159
+ if (!match) return {
7160
+ status: 404,
7161
+ headers: {},
7162
+ redirectUrl: null,
7163
+ body: { error: `No route matches ${input.method} ${input.path}` }
7164
+ };
7165
+ const envelope = {
7166
+ status: 200,
7167
+ headers: {},
7168
+ redirectUrl: null
7169
+ };
7170
+ const reply = buildCapturingReply(envelope);
7171
+ const request = {
7172
+ params: {
7173
+ ...input.params,
7174
+ ...match.params
7175
+ },
7176
+ query: input.query,
7177
+ body: input.body,
7178
+ headers: input.headers,
7179
+ ...input.user ? { user: input.user } : {},
7180
+ ...input.scopedToken !== void 0 ? { scopedToken: input.scopedToken } : {}
7181
+ };
7182
+ await match.route.handler(request, reply);
7183
+ return envelope;
7184
+ }
7185
+ };
7186
+ }
7187
+ /**
7188
+ * Pattern matcher: same semantics as `AddonRouteRegistry.matchRoute`
7189
+ * but operating on a flat list and bypassing the `/addon/<id>/` prefix
7190
+ * — the bridge sends the post-prefix path directly so we don't need
7191
+ * to round-trip it through normalization.
7192
+ */
7193
+ function matchRoute(routes, method, path) {
7194
+ const normalizedMethod = method.toUpperCase();
7195
+ for (const route of routes) {
7196
+ if (route.method !== normalizedMethod) continue;
7197
+ const params = matchPath(route.path, path);
7198
+ if (params !== null) return {
7199
+ route,
7200
+ params
7201
+ };
7202
+ }
7203
+ return null;
7204
+ }
7205
+ function matchPath(pattern, p) {
7206
+ const patternParts = pattern.split("/").filter(Boolean);
7207
+ const pathParts = p.split("/").filter(Boolean);
7208
+ if (patternParts.length !== pathParts.length) return null;
7209
+ const params = {};
7210
+ for (let i = 0; i < patternParts.length; i++) {
7211
+ const a = patternParts[i];
7212
+ const b = pathParts[i];
7213
+ if (a.startsWith(":")) params[a.slice(1)] = b;
7214
+ else if (a !== b) return null;
7215
+ }
7216
+ return params;
7217
+ }
7218
+ function buildCapturingReply(envelope) {
7219
+ const wrapper = {
7220
+ status(code) {
7221
+ envelope.status = code;
7222
+ return wrapper;
7223
+ },
7224
+ code(code) {
7225
+ envelope.status = code;
7226
+ return wrapper;
7227
+ },
7228
+ send(data) {
7229
+ envelope.body = data;
7230
+ },
7231
+ redirect(url) {
7232
+ envelope.redirectUrl = url;
7233
+ if (envelope.status === 200) envelope.status = 302;
7234
+ },
7235
+ header(name, value) {
7236
+ envelope.headers[name.toLowerCase()] = value;
7237
+ return wrapper;
7238
+ },
7239
+ type(mime) {
7240
+ envelope.contentType = mime;
7241
+ return wrapper;
7242
+ }
7243
+ };
7244
+ return wrapper;
7245
+ }
7246
+ /**
7059
7247
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
7060
7248
  * Named `RecordingWeekday` to avoid collision with the string-union
7061
7249
  * `Weekday` exported from `interfaces/timezones.ts`.
@@ -10605,7 +10793,8 @@ var ModelFormatSchema$1 = _enum([
10605
10793
  "coreml",
10606
10794
  "openvino",
10607
10795
  "tflite",
10608
- "pt"
10796
+ "pt",
10797
+ "gguf"
10609
10798
  ]);
10610
10799
  var PipelineSlotSchema = _enum([
10611
10800
  "detector",
@@ -10699,7 +10888,8 @@ var EngineProvisioningSchema = object({
10699
10888
  runtimeId: _enum([
10700
10889
  "onnx",
10701
10890
  "openvino",
10702
- "coreml"
10891
+ "coreml",
10892
+ "edgetpu"
10703
10893
  ]).nullable(),
10704
10894
  device: string().nullable(),
10705
10895
  state: _enum([
@@ -11911,6 +12101,140 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
11911
12101
  deviceId: number(),
11912
12102
  status: PtzAutotrackStatusSchema
11913
12103
  });
12104
+ /**
12105
+ * scene-monitor — device-scoped reference-region state cap. An operator marks
12106
+ * a rect ROI on a camera frame and names one or more states; the engine
12107
+ * (pipeline-analytics, designated post-processing node) reports which state is
12108
+ * active on an ongoing basis. Two operator-selectable check modes share every-
12109
+ * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
12110
+ * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
12111
+ *
12112
+ * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) — the framework
12113
+ * derives the device-detail contribution; the provider carries NO hand-written
12114
+ * settings-contribution methods. `status.kind:'push'` — the engine pushes on
12115
+ * every hysteresis flip / availability change; consumers never poll.
12116
+ */
12117
+ /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
12118
+ * be added without a wire break (matching falls back to any-condition refs). */
12119
+ var SceneConditionSchema = string();
12120
+ /** One captured reference — condition-tagged, model-version-gated. `embedding`
12121
+ * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
12122
+ var SceneReferenceSchema = object({
12123
+ embedding: array(number()),
12124
+ modelId: string(),
12125
+ condition: SceneConditionSchema,
12126
+ capturedAt: number(),
12127
+ thumbnailMediaId: string().optional()
12128
+ });
12129
+ var SceneMonitorStateSchema = object({
12130
+ id: string(),
12131
+ label: string(),
12132
+ references: array(SceneReferenceSchema),
12133
+ textPrompts: array(string()).optional(),
12134
+ referenceCount: number(),
12135
+ currentlyMatched: boolean()
12136
+ });
12137
+ /** Per-mode comparator params in a discriminated union so an `llm` scene never
12138
+ * carries similarity knobs and vice-versa. */
12139
+ var SceneCheckSchema = discriminatedUnion("mode", [object({
12140
+ mode: literal("similarity"),
12141
+ threshold: number().min(0).max(1),
12142
+ hysteresisCount: number().int().positive()
12143
+ }), object({
12144
+ mode: literal("llm"),
12145
+ prompt: string(),
12146
+ profileId: string().optional(),
12147
+ hysteresisCount: number().int().positive()
12148
+ })]);
12149
+ var SceneMonitorSchema = object({
12150
+ id: string(),
12151
+ label: string(),
12152
+ roi: MaskRectShapeSchema,
12153
+ enabled: boolean(),
12154
+ triggerMode: _enum([
12155
+ "periodic",
12156
+ "on-motion",
12157
+ "both"
12158
+ ]),
12159
+ checkIntervalSec: number().optional(),
12160
+ check: SceneCheckSchema,
12161
+ states: array(SceneMonitorStateSchema),
12162
+ currentStateId: string().nullable(),
12163
+ lastCheckedAt: number().nullable(),
12164
+ lastConfidence: number().nullable(),
12165
+ currentCondition: SceneConditionSchema.nullable(),
12166
+ availability: _enum(["ok", "unavailable"]),
12167
+ unavailableReason: string().nullable()
12168
+ });
12169
+ var SceneMonitorStatusSchema = object({
12170
+ monitors: array(SceneMonitorSchema),
12171
+ lastFetchedAt: number()
12172
+ });
12173
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
12174
+ deviceId: number(),
12175
+ label: string(),
12176
+ roi: MaskRectShapeSchema,
12177
+ check: SceneCheckSchema,
12178
+ triggerMode: _enum([
12179
+ "periodic",
12180
+ "on-motion",
12181
+ "both"
12182
+ ]).optional(),
12183
+ checkIntervalSec: number().optional()
12184
+ }), SceneMonitorSchema, {
12185
+ kind: "mutation",
12186
+ auth: "admin"
12187
+ }), method(object({
12188
+ deviceId: number(),
12189
+ monitorId: string(),
12190
+ patch: object({
12191
+ label: string().optional(),
12192
+ roi: MaskRectShapeSchema.optional(),
12193
+ enabled: boolean().optional(),
12194
+ triggerMode: _enum([
12195
+ "periodic",
12196
+ "on-motion",
12197
+ "both"
12198
+ ]).optional(),
12199
+ checkIntervalSec: number().optional(),
12200
+ check: SceneCheckSchema.optional()
12201
+ })
12202
+ }), _void(), {
12203
+ kind: "mutation",
12204
+ auth: "admin"
12205
+ }), method(object({
12206
+ deviceId: number(),
12207
+ monitorId: string()
12208
+ }), _void(), {
12209
+ kind: "mutation",
12210
+ auth: "admin"
12211
+ }), method(object({
12212
+ deviceId: number(),
12213
+ monitorId: string(),
12214
+ stateId: string().optional(),
12215
+ label: string().optional(),
12216
+ condition: SceneConditionSchema.optional()
12217
+ }), object({
12218
+ stateId: string(),
12219
+ referenceCount: number()
12220
+ }), {
12221
+ kind: "mutation",
12222
+ auth: "admin"
12223
+ }), method(object({
12224
+ deviceId: number(),
12225
+ monitorId: string(),
12226
+ stateId: string(),
12227
+ index: number()
12228
+ }), _void(), {
12229
+ kind: "mutation",
12230
+ auth: "admin"
12231
+ }), method(object({
12232
+ deviceId: number(),
12233
+ monitorId: string()
12234
+ }), _void(), {
12235
+ kind: "mutation",
12236
+ auth: "admin"
12237
+ });
11914
12238
  object({
11915
12239
  /** Whether the script is currently executing. */
11916
12240
  isRunning: boolean(),
@@ -12318,6 +12642,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
12318
12642
  * with the per-track detail panel and live overlay. */
12319
12643
  trackIds: array(string()).readonly()
12320
12644
  });
12645
+ /**
12646
+ * A parked ("stationary") object surfaced alongside occupancy — an object that
12647
+ * settled and stopped moving. It is NO LONGER a tracked object (the tracker was
12648
+ * told to forget it so it stops re-spawning tracks/events), but it IS still
12649
+ * physically present, so it keeps counting toward `frame` occupancy and is
12650
+ * listed here so the UI can show it in a dedicated "Stationary" section instead
12651
+ * of flooding the live event feed.
12652
+ */
12653
+ var StationaryObjectSchema = object({
12654
+ id: string(),
12655
+ className: string(),
12656
+ bbox: object({
12657
+ x: number(),
12658
+ y: number(),
12659
+ w: number(),
12660
+ h: number()
12661
+ }),
12662
+ frameWidth: number().int().nonnegative(),
12663
+ frameHeight: number().int().nonnegative(),
12664
+ /** When the source track was first seen. */
12665
+ firstSeenAt: number().int(),
12666
+ /** When the object was recognised as parked (promotion time). */
12667
+ becameStationaryAt: number().int(),
12668
+ /** Last frame a detection confirmed the object is still there. */
12669
+ lastConfirmedAt: number().int(),
12670
+ /** Enrichment label carried from the source track (identity / plate). */
12671
+ label: string().optional(),
12672
+ /** Native-resolution key-frame media key for the parked object's best image. */
12673
+ keyFrameMediaKey: string().optional()
12674
+ });
12321
12675
  var CameraOccupancySnapshotSchema = object({
12322
12676
  /** Frame timestamp of the inference result that produced this snapshot. */
12323
12677
  ts: number().int(),
@@ -12326,10 +12680,15 @@ var CameraOccupancySnapshotSchema = object({
12326
12680
  frameHeight: number().int().nonnegative(),
12327
12681
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
12328
12682
  zones: array(ZoneScopeBreakdownSchema).readonly(),
12329
- /** Frame-wide aggregate (everywhere, regardless of zone membership). */
12683
+ /** Frame-wide aggregate (everywhere, regardless of zone membership).
12684
+ * INCLUDES currently-confirmed stationary objects (they are still present). */
12330
12685
  frame: PerScopeBreakdownSchema,
12331
12686
  /** Detections that landed outside every zone. Empty when no zones defined. */
12332
- unzoned: PerScopeBreakdownSchema
12687
+ unzoned: PerScopeBreakdownSchema,
12688
+ /** Parked objects on this camera (additive — absent on legacy snapshots).
12689
+ * Surfaced separately so the UI shows them in a dedicated section rather
12690
+ * than as repeated tracks/events. */
12691
+ stationaryObjects: array(StationaryObjectSchema).readonly().optional()
12333
12692
  });
12334
12693
  /**
12335
12694
  * Time-series resolution. The history methods return one bucket per
@@ -12699,7 +13058,26 @@ var InvokeReplyEnvelopeSchema = object({
12699
13058
  /** Set when the handler called `reply.type(mime)`. */
12700
13059
  contentType: string().optional()
12701
13060
  });
12702
- method(_void(), array(AddonHttpRouteSchema)), method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" });
13061
+ var addonRoutesCapability = {
13062
+ name: "addon-routes",
13063
+ scope: "system",
13064
+ mode: "collection",
13065
+ internal: true,
13066
+ methods: {
13067
+ getRoutes: method(_void(), array(AddonHttpRouteSchema)),
13068
+ /**
13069
+ * Cross-process dispatch entry point. Forked addons implement this
13070
+ * (via `buildAddonRouteProvider`) so the hub's Fastify catch-all
13071
+ * can route through Moleculer when the handler lives in a worker.
13072
+ *
13073
+ * Local addons can implement it for free with the same helper;
13074
+ * the hub bypasses the wire on co-located addons.
13075
+ */
13076
+ invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
13077
+ },
13078
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
13079
+ mount: { kind: "skip" }
13080
+ };
12703
13081
  var ConfigTabDeclarationSchema = object({
12704
13082
  id: string(),
12705
13083
  label: string(),
@@ -12932,7 +13310,12 @@ var NotificationRuleConditionsSchema = object({
12932
13310
  clipDescription: object({
12933
13311
  text: string().min(1),
12934
13312
  minSimilarity: number().min(0).max(1)
12935
- }).optional()
13313
+ }).optional(),
13314
+ /** Match events whose recognized-entity label (face identity name or plate
13315
+ * vehicle name, propagated onto `event.data.label`) is one of these values.
13316
+ * Empty/absent → unaffected (back-compat). Enables "notify me when <named
13317
+ * vehicle/person> is seen". */
13318
+ labels: array(string()).readonly().optional()
12936
13319
  });
12937
13320
  var NotificationRuleTemplateSchema = object({
12938
13321
  title: string(),
@@ -13190,11 +13573,19 @@ method(object({
13190
13573
  * login page needs NO change.
13191
13574
  *
13192
13575
  * - `widget` — a Module-Federation widget the login page mounts (via
13193
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13194
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13195
- * addon bundle. The referenced widget also declares `preAuth: true` in
13196
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13197
- * stamps a public `bundleUrl` from `addonId` + `bundle`.
13576
+ * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
13577
+ * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
13578
+ * mechanism kept for future use; no shipped addon uses it on the login
13579
+ * page (the passkey ceremony below runs natively in the shell instead).
13580
+ *
13581
+ * - `passkey` — a declarative WebAuthn ceremony the shell renders
13582
+ * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
13583
+ * a remotely-loaded bundle). Carries the addon's effective `rpId` /
13584
+ * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
13585
+ * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
13586
+ * fetching any remote code pre-auth. Contribution stays unconditional —
13587
+ * enrollment state is never leaked pre-auth; visibility is a shell
13588
+ * decision.
13198
13589
  *
13199
13590
  * Every contribution carries a `stage`:
13200
13591
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -13208,30 +13599,45 @@ method(object({
13208
13599
  */
13209
13600
  /** When a login method renders in the two-phase login flow. */
13210
13601
  var LoginStageEnum = _enum(["primary", "second-factor"]);
13211
- /** One login-method contribution — redirect button OR pre-auth widget. */
13212
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13213
- kind: literal("redirect"),
13214
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13215
- id: string(),
13216
- /** Operator-facing button label. */
13217
- label: string(),
13218
- /** lucide-react icon name. */
13219
- icon: string().optional(),
13220
- /** Addon-owned HTTP route the button navigates to (GET). */
13221
- startUrl: string(),
13222
- stage: LoginStageEnum
13223
- }), object({
13224
- kind: literal("widget"),
13225
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13226
- id: string(),
13227
- /** Owning addon id drives the public bundle URL + the MF namespace. */
13228
- addonId: string(),
13229
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13230
- bundle: string(),
13231
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
13232
- remote: WidgetRemoteSchema,
13233
- stage: LoginStageEnum
13234
- })]);
13602
+ /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
13603
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [
13604
+ object({
13605
+ kind: literal("redirect"),
13606
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13607
+ id: string(),
13608
+ /** Operator-facing button label. */
13609
+ label: string(),
13610
+ /** lucide-react icon name. */
13611
+ icon: string().optional(),
13612
+ /** Addon-owned HTTP route the button navigates to (GET). */
13613
+ startUrl: string(),
13614
+ stage: LoginStageEnum
13615
+ }),
13616
+ object({
13617
+ kind: literal("widget"),
13618
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13619
+ id: string(),
13620
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13621
+ addonId: string(),
13622
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13623
+ bundle: string(),
13624
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13625
+ remote: WidgetRemoteSchema,
13626
+ stage: LoginStageEnum
13627
+ }),
13628
+ object({
13629
+ kind: literal("passkey"),
13630
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
13631
+ id: string(),
13632
+ /** Operator-facing button label. */
13633
+ label: string(),
13634
+ stage: LoginStageEnum,
13635
+ /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
13636
+ rpId: string(),
13637
+ /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
13638
+ origin: string().nullable()
13639
+ })
13640
+ ]);
13235
13641
  method(_void(), array(LoginMethodContributionSchema).readonly());
13236
13642
  /**
13237
13643
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -15059,14 +15465,14 @@ var TargetKindCapsSchema = object({
15059
15465
  * the union is large and not meant for runtime validation here; the exported
15060
15466
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
15061
15467
  */
15062
- var ConfigSchemaPassthrough = unknown();
15468
+ var ConfigSchemaPassthrough$1 = unknown();
15063
15469
  var TargetKindSchema = object({
15064
15470
  kind: string(),
15065
15471
  label: string(),
15066
15472
  icon: string(),
15067
15473
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
15068
15474
  addonId: string(),
15069
- configSchema: ConfigSchemaPassthrough,
15475
+ configSchema: ConfigSchemaPassthrough$1,
15070
15476
  supportsDiscovery: boolean(),
15071
15477
  caps: TargetKindCapsSchema
15072
15478
  });
@@ -15133,6 +15539,298 @@ var notificationOutputCapability = {
15133
15539
  }
15134
15540
  };
15135
15541
  /**
15542
+ * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15543
+ * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15544
+ * caps stay wire-compatible without a circular cap→cap import.
15545
+ *
15546
+ * Errors are a discriminated-union RESULT, never thrown: the shape survives
15547
+ * every transport tier structurally, and failed calls still write usage rows.
15548
+ * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15549
+ */
15550
+ var LlmUsageSchema = object({
15551
+ inputTokens: number(),
15552
+ outputTokens: number()
15553
+ });
15554
+ var LlmErrorCodeSchema = _enum([
15555
+ "timeout",
15556
+ "rate-limited",
15557
+ "auth",
15558
+ "refusal",
15559
+ "bad-request",
15560
+ "unavailable",
15561
+ "no-profile",
15562
+ "budget-exceeded",
15563
+ "adapter-error"
15564
+ ]);
15565
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15566
+ ok: literal(true),
15567
+ text: string(),
15568
+ model: string(),
15569
+ usage: LlmUsageSchema,
15570
+ truncated: boolean(),
15571
+ latencyMs: number()
15572
+ }), object({
15573
+ ok: literal(false),
15574
+ code: LlmErrorCodeSchema,
15575
+ message: string(),
15576
+ retryAfterMs: number().optional()
15577
+ })]);
15578
+ /**
15579
+ * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
15580
+ * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15581
+ * notification-output.cap.ts:27-31 precedents).
15582
+ */
15583
+ var LlmImageSchema = object({
15584
+ bytes: _instanceof(Uint8Array),
15585
+ mimeType: string()
15586
+ });
15587
+ var LlmGenerateBaseInputSchema = object({
15588
+ /** Collection routing (the notification-output posture). */
15589
+ addonId: string().optional(),
15590
+ /** Explicit profile; else the resolution chain (spec §3). */
15591
+ profileId: string().optional(),
15592
+ /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15593
+ consumer: string(),
15594
+ system: string().optional(),
15595
+ /** v1: single-turn. `messages[]` is a v2 additive field. */
15596
+ prompt: string(),
15597
+ /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15598
+ jsonSchema: record(string(), unknown()).optional(),
15599
+ /** Per-call override of the profile default. */
15600
+ maxTokens: number().int().positive().optional(),
15601
+ temperature: number().optional()
15602
+ });
15603
+ /**
15604
+ * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
15605
+ * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15606
+ * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
15607
+ * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15608
+ * this only through the `llm` cap's methods.
15609
+ *
15610
+ * One running llama-server child per node in v1 (models are RAM-heavy).
15611
+ * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15612
+ * watchdog — operator decision #3).
15613
+ */
15614
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
15615
+ object({
15616
+ kind: literal("catalog"),
15617
+ catalogId: string()
15618
+ }),
15619
+ object({
15620
+ kind: literal("url"),
15621
+ url: string(),
15622
+ sha256: string().optional()
15623
+ }),
15624
+ object({
15625
+ kind: literal("path"),
15626
+ path: string()
15627
+ })
15628
+ ]);
15629
+ var ManagedRuntimeConfigSchema = object({
15630
+ /** WHERE the runtime lives — hub or any agent. */
15631
+ nodeId: string(),
15632
+ /** Closed for v1; 'ollama' is a v2 candidate. */
15633
+ engine: _enum(["llama-cpp"]),
15634
+ model: ManagedModelRefSchema,
15635
+ contextSize: number().int().default(4096),
15636
+ /** 0 = CPU-only. */
15637
+ gpuLayers: number().int().default(0),
15638
+ /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15639
+ threads: number().int().optional(),
15640
+ /** Concurrent slots. */
15641
+ parallel: number().int().default(1),
15642
+ /** Else lazy: first generate boots it. */
15643
+ autoStart: boolean().default(false),
15644
+ /** 0 = never; frees RAM after quiet periods. */
15645
+ idleStopMinutes: number().int().default(30)
15646
+ });
15647
+ var LlmRuntimeStatusSchema = object({
15648
+ /** Status is ALWAYS node-qualified. */
15649
+ nodeId: string(),
15650
+ state: _enum([
15651
+ "stopped",
15652
+ "downloading",
15653
+ "starting",
15654
+ "ready",
15655
+ "crashed",
15656
+ "failed"
15657
+ ]),
15658
+ pid: number().optional(),
15659
+ port: number().optional(),
15660
+ modelPath: string().optional(),
15661
+ modelId: string().optional(),
15662
+ downloadProgress: number().min(0).max(1).optional(),
15663
+ lastError: string().optional(),
15664
+ crashesInWindow: number(),
15665
+ /** Child RSS (sampled best-effort). */
15666
+ memoryBytes: number().optional(),
15667
+ vramBytes: number().optional()
15668
+ });
15669
+ var LlmNodeModelSchema = object({
15670
+ file: string(),
15671
+ sizeBytes: number(),
15672
+ catalogId: string().optional(),
15673
+ installedAt: number().optional()
15674
+ });
15675
+ var LlmRuntimeDiskUsageSchema = object({
15676
+ nodeId: string(),
15677
+ modelsBytes: number(),
15678
+ freeBytes: number().optional()
15679
+ });
15680
+ method(LlmGenerateBaseInputSchema.extend({
15681
+ images: array(LlmImageSchema).optional(),
15682
+ runtime: ManagedRuntimeConfigSchema,
15683
+ /** The managed profile's timeout, threaded by the hub provider. */
15684
+ timeoutMs: number().int().positive().optional()
15685
+ }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15686
+ kind: "mutation",
15687
+ auth: "admin"
15688
+ }), method(object({}), _void(), {
15689
+ kind: "mutation",
15690
+ auth: "admin"
15691
+ }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15692
+ kind: "mutation",
15693
+ auth: "admin"
15694
+ }), method(object({ file: string() }), _void(), {
15695
+ kind: "mutation",
15696
+ auth: "admin"
15697
+ }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15698
+ /**
15699
+ * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15700
+ * methods concat-fan across providers; single-row methods route to ONE
15701
+ * provider by the `addonId` in the call input (the notification-output
15702
+ * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15703
+ * (hub-placed); the cap stays open for future providers.
15704
+ *
15705
+ * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15706
+ * `apiKey` is a password field — providers REDACT it on read and merge on
15707
+ * write; a stored key NEVER round-trips to a client.
15708
+ */
15709
+ var LlmProfileKindSchema = _enum([
15710
+ "openai-compatible",
15711
+ "openai",
15712
+ "anthropic",
15713
+ "google",
15714
+ "managed-local"
15715
+ ]);
15716
+ var LlmProfileSchema = object({
15717
+ id: string(),
15718
+ name: string(),
15719
+ kind: LlmProfileKindSchema,
15720
+ /** Stamped by the provider — keeps the fanned catalog routable. */
15721
+ addonId: string(),
15722
+ enabled: boolean(),
15723
+ /** Vendor model id, or the managed runtime's loaded model. */
15724
+ model: string(),
15725
+ /** Required for openai-compatible; override for cloud kinds. */
15726
+ baseUrl: string().optional(),
15727
+ /** ConfigUISchema type:'password' — never round-trips (spec §5). */
15728
+ apiKey: string().optional(),
15729
+ supportsVision: boolean(),
15730
+ temperature: number().min(0).max(2).optional(),
15731
+ maxTokens: number().int().positive().optional(),
15732
+ timeoutMs: number().int().positive().default(6e4),
15733
+ extraHeaders: record(string(), string()).optional(),
15734
+ /** kind === 'managed-local' only (spec §4). */
15735
+ runtime: ManagedRuntimeConfigSchema.optional()
15736
+ });
15737
+ /** ConfigUISchema tree passed through untyped on the wire (the
15738
+ * notification-output `ConfigSchemaPassthrough` precedent at
15739
+ * notification-output.cap.ts:151); the exported TS type re-tightens it. */
15740
+ var ConfigSchemaPassthrough = unknown();
15741
+ var LlmProfileKindDescriptorSchema = object({
15742
+ kind: LlmProfileKindSchema,
15743
+ label: string(),
15744
+ icon: string(),
15745
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
15746
+ addonId: string(),
15747
+ configSchema: ConfigSchemaPassthrough
15748
+ });
15749
+ var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
15750
+ var LlmDefaultSchema = object({
15751
+ selector: LlmDefaultSelectorSchema,
15752
+ profileId: string()
15753
+ });
15754
+ /** Server-side rollup row — getUsage never dumps raw call rows (spec §6). */
15755
+ var LlmUsageRollupSchema = object({
15756
+ day: string(),
15757
+ consumer: string(),
15758
+ profileId: string(),
15759
+ calls: number(),
15760
+ okCalls: number(),
15761
+ errorCalls: number(),
15762
+ inputTokens: number(),
15763
+ outputTokens: number(),
15764
+ avgLatencyMs: number()
15765
+ });
15766
+ /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
15767
+ var ManagedModelCatalogEntrySchema = object({
15768
+ id: string(),
15769
+ label: string(),
15770
+ family: string(),
15771
+ purpose: _enum(["text", "vision"]),
15772
+ url: string(),
15773
+ sha256: string(),
15774
+ sizeBytes: number(),
15775
+ quantization: string(),
15776
+ /** Load-time guidance shown in the picker. */
15777
+ minRamBytes: number(),
15778
+ contextSizeDefault: number().int(),
15779
+ /** Vision models: companion projector file. */
15780
+ mmprojUrl: string().optional()
15781
+ });
15782
+ var LlmRuntimeNodeSchema = object({
15783
+ nodeId: string(),
15784
+ reachable: boolean(),
15785
+ status: LlmRuntimeStatusSchema.optional(),
15786
+ disk: LlmRuntimeDiskUsageSchema.optional(),
15787
+ error: string().optional()
15788
+ });
15789
+ var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15790
+ var ProfileRefInputSchema = object({
15791
+ addonId: string(),
15792
+ profileId: string()
15793
+ });
15794
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
15795
+ kind: "mutation",
15796
+ auth: "admin"
15797
+ }), method(ProfileRefInputSchema, _void(), {
15798
+ kind: "mutation",
15799
+ auth: "admin"
15800
+ }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
15801
+ kind: "mutation",
15802
+ auth: "admin"
15803
+ }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
15804
+ selector: LlmDefaultSelectorSchema,
15805
+ profileId: string().nullable()
15806
+ }), _void(), {
15807
+ kind: "mutation",
15808
+ auth: "admin"
15809
+ }), method(object({
15810
+ since: number().optional(),
15811
+ until: number().optional(),
15812
+ consumer: string().optional(),
15813
+ profileId: string().optional()
15814
+ }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
15815
+ nodeId: string(),
15816
+ model: ManagedModelRefSchema
15817
+ }), _void(), {
15818
+ kind: "mutation",
15819
+ auth: "admin"
15820
+ }), method(object({
15821
+ nodeId: string(),
15822
+ file: string()
15823
+ }), _void(), {
15824
+ kind: "mutation",
15825
+ auth: "admin"
15826
+ }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
15827
+ kind: "mutation",
15828
+ auth: "admin"
15829
+ }), method(ProfileRefInputSchema, _void(), {
15830
+ kind: "mutation",
15831
+ auth: "admin"
15832
+ });
15833
+ /**
15136
15834
  * Zod schemas for persisted record types.
15137
15835
  *
15138
15836
  * These schemas serve as the single source of truth for types that are
@@ -15334,6 +16032,22 @@ var TrackSnapshotSchema = object({
15334
16032
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
15335
16033
  mediaKey: string()
15336
16034
  });
16035
+ /**
16036
+ * One audio-classification label heard on the track's camera while the
16037
+ * track was alive, aggregated per label. An "episode" is one persisted
16038
+ * audio event (the confident-classification path: score ≥ the device's
16039
+ * `classificationMinScore`, class-change-or-heartbeat coalesced) — NOT
16040
+ * one 32 ms inference chunk, so counts stay human-scaled.
16041
+ */
16042
+ var TrackAudioLabelSchema = object({
16043
+ label: string(),
16044
+ /** Highest classification score observed across the label's episodes. */
16045
+ peakScore: number(),
16046
+ /** Number of coalesced audio-event episodes carrying this label. */
16047
+ count: number(),
16048
+ firstAt: number(),
16049
+ lastAt: number()
16050
+ });
15337
16051
  var TrackSchema = object({
15338
16052
  trackId: string(),
15339
16053
  deviceId: number(),
@@ -15348,6 +16062,10 @@ var TrackSchema = object({
15348
16062
  snapshots: array(TrackSnapshotSchema).readonly(),
15349
16063
  /** Deduplicated zones the track has entered at least once. */
15350
16064
  zonesVisited: array(string()).readonly(),
16065
+ /** Deduplicated set of detector classes observed for this track over its
16066
+ * life (a track may be reclassified, e.g. person→vehicle). Absent on
16067
+ * legacy rows written before class accumulation shipped. */
16068
+ classes: array(string()).readonly().optional(),
15351
16069
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15352
16070
  totalDistance: number(),
15353
16071
  state: TrackStateSchema,
@@ -15361,7 +16079,11 @@ var TrackSchema = object({
15361
16079
  bestEventId: string().optional(),
15362
16080
  /** Tag of the importance sub-signal that dominated the score
15363
16081
  * (identity|dwell|proximity|class|confidence|travel|zone). */
15364
- importanceReason: string().optional()
16082
+ importanceReason: string().optional(),
16083
+ /** Audio-classification labels heard on the camera during the track's
16084
+ * life (score ≥ device `classificationMinScore`), aggregated per label.
16085
+ * Absent on legacy rows / tracks with no confident audio. */
16086
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15365
16087
  });
15366
16088
  var BaseEventFields = {
15367
16089
  id: string(),
@@ -15509,7 +16231,7 @@ var KeyEventSchema = object({
15509
16231
  /** Track lifetime in ms (lastSeen - firstSeen). */
15510
16232
  windowMs: number().optional()
15511
16233
  });
15512
- var TrackedDetectionSchema = object({
16234
+ object({
15513
16235
  trackId: string(),
15514
16236
  className: string(),
15515
16237
  confidence: number(),
@@ -15517,7 +16239,24 @@ var TrackedDetectionSchema = object({
15517
16239
  zones: array(string()).readonly(),
15518
16240
  state: TrackStateSchema
15519
16241
  });
15520
- var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
16242
+ var OverlayDetectionSchema = looseObject({
16243
+ id: string(),
16244
+ kind: _enum(["first-level", "detail"]),
16245
+ macroClass: string(),
16246
+ score: number(),
16247
+ bbox: object({
16248
+ x: number(),
16249
+ y: number(),
16250
+ width: number(),
16251
+ height: number()
16252
+ }),
16253
+ labels: array(looseObject({
16254
+ label: string(),
16255
+ score: number()
16256
+ })).readonly(),
16257
+ parentId: string().optional()
16258
+ });
16259
+ var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
15521
16260
  var SearchObjectEventsInput = object({
15522
16261
  text: string(),
15523
16262
  deviceId: number().optional(),
@@ -15527,6 +16266,20 @@ var SearchObjectEventsInput = object({
15527
16266
  limit: number().default(50),
15528
16267
  minScore: number().min(0).max(1).default(.2)
15529
16268
  });
16269
+ var TrackCascadeCountsSchema = object({
16270
+ /** Persisted track roots deleted (authoritative). */
16271
+ tracks: number().int(),
16272
+ /** Object events removed with their tracks (best-effort; see note above). */
16273
+ events: number().int(),
16274
+ /** Track/face/plate-owned media removed (best-effort). Never identity media. */
16275
+ media: number().int(),
16276
+ /** Unassigned (non-enrolled) face reads removed (best-effort). */
16277
+ faces: number().int(),
16278
+ /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16279
+ plates: number().int(),
16280
+ /** Per-track CLIP search vectors removed (best-effort). */
16281
+ embeddings: number().int()
16282
+ });
15530
16283
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
15531
16284
  deviceId: number(),
15532
16285
  trackId: string()
@@ -15558,6 +16311,24 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15558
16311
  }), {
15559
16312
  kind: "mutation",
15560
16313
  auth: "admin"
16314
+ }), method(object({
16315
+ deviceId: number(),
16316
+ cutoffMs: number()
16317
+ }), TrackCascadeCountsSchema, {
16318
+ kind: "mutation",
16319
+ auth: "admin"
16320
+ }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
16321
+ kind: "mutation",
16322
+ auth: "admin"
16323
+ }), method(object({
16324
+ deviceId: number(),
16325
+ trackIds: array(string()).min(1)
16326
+ }), object({
16327
+ deleted: number().int(),
16328
+ failed: array(string()).readonly()
16329
+ }), {
16330
+ kind: "mutation",
16331
+ auth: "admin"
15561
16332
  }), method(object({
15562
16333
  eventId: string(),
15563
16334
  kind: MediaFileKindEnum.optional()
@@ -15566,7 +16337,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15566
16337
  timestamp: number(),
15567
16338
  frameWidth: number(),
15568
16339
  frameHeight: number(),
15569
- detections: array(TrackedDetectionSchema).readonly()
16340
+ detections: array(OverlayDetectionSchema).readonly()
15570
16341
  }), object({
15571
16342
  deviceId: number(),
15572
16343
  trackId: string(),
@@ -18335,6 +19106,24 @@ DeviceType.Camera, method(object({
18335
19106
  deviceId: number(),
18336
19107
  status: OsdStatusSchema
18337
19108
  });
19109
+ var VehicleSchema = object({
19110
+ id: string(),
19111
+ name: string(),
19112
+ sampleCount: number().int().nonnegative(),
19113
+ coverMediaKey: string().optional(),
19114
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
19115
+ coverBase64: string().optional()
19116
+ });
19117
+ var VehicleSampleInfoSchema = object({
19118
+ id: string(),
19119
+ /** The plate text this sample enrolled (self-labeling — no embedding). */
19120
+ text: string(),
19121
+ /** OCR confidence of the enrolled read (0..1). */
19122
+ score: number(),
19123
+ addedAt: number().int(),
19124
+ deviceId: number().int().optional(),
19125
+ base64: string().optional()
19126
+ });
18338
19127
  var PlateInfoSchema = object({
18339
19128
  plateId: string(),
18340
19129
  deviceId: number().int(),
@@ -18346,6 +19135,16 @@ var PlateInfoSchema = object({
18346
19135
  score: number(),
18347
19136
  /** True when the text was manually corrected (exempt from retention). */
18348
19137
  corrected: boolean(),
19138
+ /** Recognized vehicle id when a matcher lookup named this read (SQL NULL → absent). */
19139
+ recognizedVehicleId: string().optional(),
19140
+ /** Resolved vehicle name for `recognizedVehicleId` (UI convenience). */
19141
+ vehicleName: string().optional(),
19142
+ /** True once the read was assigned to a vehicle (enrolled as a sample). */
19143
+ assigned: boolean(),
19144
+ /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
19145
+ plateBbox: BoundingBoxSchema.optional(),
19146
+ /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
19147
+ keyFrameMediaKey: string().optional(),
18349
19148
  base64: string().optional()
18350
19149
  });
18351
19150
  var MediaFileLiteSchema = object({
@@ -18385,6 +19184,48 @@ method(object({
18385
19184
  }), method(object({ plateId: string() }), _void(), {
18386
19185
  kind: "mutation",
18387
19186
  auth: "admin"
19187
+ }), method(_void(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
19188
+ kind: "mutation",
19189
+ auth: "admin"
19190
+ }), method(object({
19191
+ id: string(),
19192
+ name: string().min(1)
19193
+ }), _void(), {
19194
+ kind: "mutation",
19195
+ auth: "admin"
19196
+ }), method(object({ id: string() }), _void(), {
19197
+ kind: "mutation",
19198
+ auth: "admin"
19199
+ }), method(object({ vehicleId: string() }), array(VehicleSampleInfoSchema).readonly()), method(object({
19200
+ vehicleId: string(),
19201
+ sampleId: string()
19202
+ }), _void(), {
19203
+ kind: "mutation",
19204
+ auth: "admin"
19205
+ }), method(object({
19206
+ plateId: string(),
19207
+ vehicleId: string()
19208
+ }), _void(), {
19209
+ kind: "mutation",
19210
+ auth: "admin"
19211
+ }), method(object({ plateId: string() }), _void(), {
19212
+ kind: "mutation",
19213
+ auth: "admin"
19214
+ }), method(object({
19215
+ plateIds: array(string()).min(1),
19216
+ vehicleId: string()
19217
+ }), object({
19218
+ assigned: number().int(),
19219
+ failed: array(string()).readonly()
19220
+ }), {
19221
+ kind: "mutation",
19222
+ auth: "admin"
19223
+ }), method(object({ plateIds: array(string()).min(1) }), object({
19224
+ unassigned: number().int(),
19225
+ failed: array(string()).readonly()
19226
+ }), {
19227
+ kind: "mutation",
19228
+ auth: "admin"
18388
19229
  });
18389
19230
  var ModelFormatSchema = _enum(MODEL_FORMATS);
18390
19231
  var HwAccelBackendInputSchema = _enum([
@@ -18463,6 +19304,10 @@ var GpuInfoSchema = object({
18463
19304
  memoryMB: number().optional()
18464
19305
  });
18465
19306
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
19307
+ var CoralInfoSchema = object({
19308
+ type: literal("coral-edgetpu"),
19309
+ bus: string().optional()
19310
+ });
18466
19311
  var HardwareInfoSchema = object({
18467
19312
  platform: HardwarePlatformSchema,
18468
19313
  arch: HardwareArchSchema,
@@ -18471,7 +19316,8 @@ var HardwareInfoSchema = object({
18471
19316
  totalRAM_MB: number(),
18472
19317
  availableRAM_MB: number(),
18473
19318
  gpu: GpuInfoSchema.nullable(),
18474
- npu: NpuInfoSchema.nullable()
19319
+ npu: NpuInfoSchema.nullable(),
19320
+ coral: CoralInfoSchema.nullable().optional()
18475
19321
  });
18476
19322
  var PlatformScoreSchema = object({
18477
19323
  runtime: _enum(["node", "python"]),
@@ -20930,6 +21776,168 @@ Object.freeze({
20930
21776
  addonId: null,
20931
21777
  access: "create"
20932
21778
  },
21779
+ "llm.deleteModel": {
21780
+ capName: "llm",
21781
+ capScope: "system",
21782
+ addonId: null,
21783
+ access: "delete"
21784
+ },
21785
+ "llm.deleteProfile": {
21786
+ capName: "llm",
21787
+ capScope: "system",
21788
+ addonId: null,
21789
+ access: "delete"
21790
+ },
21791
+ "llm.generate": {
21792
+ capName: "llm",
21793
+ capScope: "system",
21794
+ addonId: null,
21795
+ access: "create"
21796
+ },
21797
+ "llm.generateVision": {
21798
+ capName: "llm",
21799
+ capScope: "system",
21800
+ addonId: null,
21801
+ access: "create"
21802
+ },
21803
+ "llm.getDefaults": {
21804
+ capName: "llm",
21805
+ capScope: "system",
21806
+ addonId: null,
21807
+ access: "view"
21808
+ },
21809
+ "llm.getRuntimeStatus": {
21810
+ capName: "llm",
21811
+ capScope: "system",
21812
+ addonId: null,
21813
+ access: "view"
21814
+ },
21815
+ "llm.getUsage": {
21816
+ capName: "llm",
21817
+ capScope: "system",
21818
+ addonId: null,
21819
+ access: "view"
21820
+ },
21821
+ "llm.installModel": {
21822
+ capName: "llm",
21823
+ capScope: "system",
21824
+ addonId: null,
21825
+ access: "create"
21826
+ },
21827
+ "llm.listModelCatalog": {
21828
+ capName: "llm",
21829
+ capScope: "system",
21830
+ addonId: null,
21831
+ access: "view"
21832
+ },
21833
+ "llm.listModels": {
21834
+ capName: "llm",
21835
+ capScope: "system",
21836
+ addonId: null,
21837
+ access: "view"
21838
+ },
21839
+ "llm.listNodeModels": {
21840
+ capName: "llm",
21841
+ capScope: "system",
21842
+ addonId: null,
21843
+ access: "view"
21844
+ },
21845
+ "llm.listProfileKinds": {
21846
+ capName: "llm",
21847
+ capScope: "system",
21848
+ addonId: null,
21849
+ access: "view"
21850
+ },
21851
+ "llm.listProfiles": {
21852
+ capName: "llm",
21853
+ capScope: "system",
21854
+ addonId: null,
21855
+ access: "view"
21856
+ },
21857
+ "llm.listRuntimeNodes": {
21858
+ capName: "llm",
21859
+ capScope: "system",
21860
+ addonId: null,
21861
+ access: "view"
21862
+ },
21863
+ "llm.setDefault": {
21864
+ capName: "llm",
21865
+ capScope: "system",
21866
+ addonId: null,
21867
+ access: "create"
21868
+ },
21869
+ "llm.startRuntime": {
21870
+ capName: "llm",
21871
+ capScope: "system",
21872
+ addonId: null,
21873
+ access: "create"
21874
+ },
21875
+ "llm.stopRuntime": {
21876
+ capName: "llm",
21877
+ capScope: "system",
21878
+ addonId: null,
21879
+ access: "create"
21880
+ },
21881
+ "llm.testProfile": {
21882
+ capName: "llm",
21883
+ capScope: "system",
21884
+ addonId: null,
21885
+ access: "create"
21886
+ },
21887
+ "llm.upsertProfile": {
21888
+ capName: "llm",
21889
+ capScope: "system",
21890
+ addonId: null,
21891
+ access: "create"
21892
+ },
21893
+ "llmRuntime.complete": {
21894
+ capName: "llm-runtime",
21895
+ capScope: "system",
21896
+ addonId: null,
21897
+ access: "create"
21898
+ },
21899
+ "llmRuntime.deleteModel": {
21900
+ capName: "llm-runtime",
21901
+ capScope: "system",
21902
+ addonId: null,
21903
+ access: "delete"
21904
+ },
21905
+ "llmRuntime.ensureStarted": {
21906
+ capName: "llm-runtime",
21907
+ capScope: "system",
21908
+ addonId: null,
21909
+ access: "create"
21910
+ },
21911
+ "llmRuntime.getDiskUsage": {
21912
+ capName: "llm-runtime",
21913
+ capScope: "system",
21914
+ addonId: null,
21915
+ access: "view"
21916
+ },
21917
+ "llmRuntime.installModel": {
21918
+ capName: "llm-runtime",
21919
+ capScope: "system",
21920
+ addonId: null,
21921
+ access: "create"
21922
+ },
21923
+ "llmRuntime.listLocalModels": {
21924
+ capName: "llm-runtime",
21925
+ capScope: "system",
21926
+ addonId: null,
21927
+ access: "view"
21928
+ },
21929
+ "llmRuntime.status": {
21930
+ capName: "llm-runtime",
21931
+ capScope: "system",
21932
+ addonId: null,
21933
+ access: "view"
21934
+ },
21935
+ "llmRuntime.stop": {
21936
+ capName: "llm-runtime",
21937
+ capScope: "system",
21938
+ addonId: null,
21939
+ access: "create"
21940
+ },
20933
21941
  "localNetwork.getAllowedAddresses": {
20934
21942
  capName: "local-network",
20935
21943
  capScope: "system",
@@ -21560,6 +22568,12 @@ Object.freeze({
21560
22568
  addonId: null,
21561
22569
  access: "delete"
21562
22570
  },
22571
+ "pipelineAnalytics.deleteTracks": {
22572
+ capName: "pipeline-analytics",
22573
+ capScope: "device",
22574
+ addonId: null,
22575
+ access: "delete"
22576
+ },
21563
22577
  "pipelineAnalytics.getActiveTracks": {
21564
22578
  capName: "pipeline-analytics",
21565
22579
  capScope: "device",
@@ -21626,12 +22640,24 @@ Object.freeze({
21626
22640
  addonId: null,
21627
22641
  access: "create"
21628
22642
  },
22643
+ "pipelineAnalytics.pruneTracksBefore": {
22644
+ capName: "pipeline-analytics",
22645
+ capScope: "device",
22646
+ addonId: null,
22647
+ access: "create"
22648
+ },
21629
22649
  "pipelineAnalytics.searchObjectEvents": {
21630
22650
  capName: "pipeline-analytics",
21631
22651
  capScope: "device",
21632
22652
  addonId: null,
21633
22653
  access: "view"
21634
22654
  },
22655
+ "pipelineAnalytics.wipeAllAnalytics": {
22656
+ capName: "pipeline-analytics",
22657
+ capScope: "device",
22658
+ addonId: null,
22659
+ access: "delete"
22660
+ },
21635
22661
  "pipelineExecutor.cacheFrameInPool": {
21636
22662
  capName: "pipeline-executor",
21637
22663
  capScope: "system",
@@ -22136,18 +23162,42 @@ Object.freeze({
22136
23162
  addonId: null,
22137
23163
  access: "create"
22138
23164
  },
23165
+ "plateGallery.assignPlate": {
23166
+ capName: "plate-gallery",
23167
+ capScope: "system",
23168
+ addonId: null,
23169
+ access: "create"
23170
+ },
23171
+ "plateGallery.assignPlates": {
23172
+ capName: "plate-gallery",
23173
+ capScope: "system",
23174
+ addonId: null,
23175
+ access: "create"
23176
+ },
22139
23177
  "plateGallery.correctPlateText": {
22140
23178
  capName: "plate-gallery",
22141
23179
  capScope: "system",
22142
23180
  addonId: null,
22143
23181
  access: "create"
22144
23182
  },
23183
+ "plateGallery.createVehicle": {
23184
+ capName: "plate-gallery",
23185
+ capScope: "system",
23186
+ addonId: null,
23187
+ access: "create"
23188
+ },
22145
23189
  "plateGallery.deletePlate": {
22146
23190
  capName: "plate-gallery",
22147
23191
  capScope: "system",
22148
23192
  addonId: null,
22149
23193
  access: "delete"
22150
23194
  },
23195
+ "plateGallery.deleteVehicle": {
23196
+ capName: "plate-gallery",
23197
+ capScope: "system",
23198
+ addonId: null,
23199
+ access: "delete"
23200
+ },
22151
23201
  "plateGallery.getPlateByTrack": {
22152
23202
  capName: "plate-gallery",
22153
23203
  capScope: "system",
@@ -22166,6 +23216,30 @@ Object.freeze({
22166
23216
  addonId: null,
22167
23217
  access: "view"
22168
23218
  },
23219
+ "plateGallery.listVehicles": {
23220
+ capName: "plate-gallery",
23221
+ capScope: "system",
23222
+ addonId: null,
23223
+ access: "view"
23224
+ },
23225
+ "plateGallery.listVehicleSamples": {
23226
+ capName: "plate-gallery",
23227
+ capScope: "system",
23228
+ addonId: null,
23229
+ access: "view"
23230
+ },
23231
+ "plateGallery.removeVehicleSample": {
23232
+ capName: "plate-gallery",
23233
+ capScope: "system",
23234
+ addonId: null,
23235
+ access: "delete"
23236
+ },
23237
+ "plateGallery.renameVehicle": {
23238
+ capName: "plate-gallery",
23239
+ capScope: "system",
23240
+ addonId: null,
23241
+ access: "create"
23242
+ },
22169
23243
  "plateGallery.searchPlates": {
22170
23244
  capName: "plate-gallery",
22171
23245
  capScope: "system",
@@ -22178,6 +23252,18 @@ Object.freeze({
22178
23252
  addonId: null,
22179
23253
  access: "view"
22180
23254
  },
23255
+ "plateGallery.unassignPlate": {
23256
+ capName: "plate-gallery",
23257
+ capScope: "system",
23258
+ addonId: null,
23259
+ access: "create"
23260
+ },
23261
+ "plateGallery.unassignPlates": {
23262
+ capName: "plate-gallery",
23263
+ capScope: "system",
23264
+ addonId: null,
23265
+ access: "create"
23266
+ },
22181
23267
  "platformProbe.getCapabilities": {
22182
23268
  capName: "platform-probe",
22183
23269
  capScope: "system",
@@ -22370,6 +23456,48 @@ Object.freeze({
22370
23456
  addonId: null,
22371
23457
  access: "create"
22372
23458
  },
23459
+ "sceneMonitor.captureReference": {
23460
+ capName: "scene-monitor",
23461
+ capScope: "device",
23462
+ addonId: null,
23463
+ access: "create"
23464
+ },
23465
+ "sceneMonitor.createScene": {
23466
+ capName: "scene-monitor",
23467
+ capScope: "device",
23468
+ addonId: null,
23469
+ access: "create"
23470
+ },
23471
+ "sceneMonitor.deleteReference": {
23472
+ capName: "scene-monitor",
23473
+ capScope: "device",
23474
+ addonId: null,
23475
+ access: "delete"
23476
+ },
23477
+ "sceneMonitor.deleteScene": {
23478
+ capName: "scene-monitor",
23479
+ capScope: "device",
23480
+ addonId: null,
23481
+ access: "delete"
23482
+ },
23483
+ "sceneMonitor.listScenes": {
23484
+ capName: "scene-monitor",
23485
+ capScope: "device",
23486
+ addonId: null,
23487
+ access: "view"
23488
+ },
23489
+ "sceneMonitor.recheckNow": {
23490
+ capName: "scene-monitor",
23491
+ capScope: "device",
23492
+ addonId: null,
23493
+ access: "create"
23494
+ },
23495
+ "sceneMonitor.updateScene": {
23496
+ capName: "scene-monitor",
23497
+ capScope: "device",
23498
+ addonId: null,
23499
+ access: "create"
23500
+ },
22373
23501
  "scriptRunner.run": {
22374
23502
  capName: "script-runner",
22375
23503
  capScope: "device",
@@ -23454,6 +24582,7 @@ Object.freeze({
23454
24582
  Object.freeze({
23455
24583
  "broker": "broker",
23456
24584
  "device-export": "device-export",
24585
+ "llm": "ai",
23457
24586
  "mesh-network": "mesh",
23458
24587
  "mqtt-broker": "broker",
23459
24588
  "network-access": "ingress",
@@ -23485,6 +24614,143 @@ object({
23485
24614
  bootAttempts: number(),
23486
24615
  schemaVersion: literal(1)
23487
24616
  });
24617
+ var NOTIFIER_ICONS = {
24618
+ telegram: {
24619
+ contentType: "image/svg+xml",
24620
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"#26A5E4\"><path d=\"M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z\"/></svg>"
24621
+ },
24622
+ discord: {
24623
+ contentType: "image/svg+xml",
24624
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"#5865F2\"><path d=\"M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z\"/></svg>"
24625
+ },
24626
+ whatsapp: {
24627
+ contentType: "image/svg+xml",
24628
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"#25D366\"><path d=\"M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z\"/></svg>"
24629
+ },
24630
+ ntfy: {
24631
+ contentType: "image/svg+xml",
24632
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"#317F6F\"><path d=\"M12.597 13.693v2.156h6.205v-2.156ZM5.183 6.549v2.363l3.591 1.901.023.01-.023.009-3.591 1.901v2.35l.386-.211 5.456-2.969V9.729ZM3.659 2.037C1.915 2.037.42 3.41.42 5.154v.002L.438 18.73 0 21.963l5.956-1.583h14.806c1.744 0 3.238-1.374 3.238-3.118V5.154c0-1.744-1.493-3.116-3.237-3.117h-.001zm0 2.2h17.104c.613.001 1.037.447 1.037.917v12.108c0 .47-.424.916-1.038.916H5.633l-3.026.915.031-.179-.017-13.76c0-.47.424-.917 1.038-.917z\"/></svg>"
24633
+ },
24634
+ pushover: {
24635
+ contentType: "image/svg+xml",
24636
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#249DF1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6.16 10.985c-.83 -1.935 1.53 -7.985 8.195 -7.985c3.333 0 4.645 1.382 4.645 3.9c0 2.597 -2.612 6.1 -9 6.1\" /><path d=\"M12.5 6l-5.5 15\" /></svg>"
24637
+ },
24638
+ webhook: {
24639
+ contentType: "image/svg+xml",
24640
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"#7C3AED\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4.876 13.61a4 4 0 1 0 6.124 3.39h6\" /><path d=\"M15.066 20.502a4 4 0 1 0 1.934 -7.502c-.706 0 -1.424 .179 -2 .5l-3 -5.5\" /><path d=\"M16 8a4 4 0 1 0 -8 0c0 1.506 .77 2.818 2 3.5l-3 5.5\" /></svg>"
24641
+ },
24642
+ gotify: {
24643
+ contentType: "image/svg+xml",
24644
+ body: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 500 500\" width=\"500\" height=\"500\"><style type=\"text/css\" id=\"style1\"> .st0{fill:#DDCBA2;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st1{fill:#71CAEE;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st2{fill:#FFFFFF;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st3{fill:#888E93;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st4{fill:#F0F0F0;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st5{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;} .st6{fill:url(#SVGID_1_);} .st7{fill:url(#SVGID_2_);} .st8{fill:#FFFFFF;} .st9{fill:url(#SVGID_3_);} .st10{fill:url(#SVGID_4_);} .st11{fill:url(#SVGID_5_);} .st12{fill:none;stroke:#FFFFFF;stroke-width:63;stroke-linecap:round;stroke-miterlimit:10;} .st13{fill:none;stroke:#FFFFFF;stroke-width:56;stroke-linecap:round;stroke-miterlimit:10;} .st14{fill:none;stroke:#A4C639;stroke-width:45.1385;stroke-linecap:round;stroke-miterlimit:10;} .st15{fill:none;stroke:#FFFFFF;stroke-width:16;} .st16{fill:#A4C639;} .st17{fill:#E6E6E6;stroke:#FFFFFF;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} .st18{fill:none;stroke:#FFFFFF;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} .st19{fill:url(#SVGID_6_);} .st20{fill:url(#SVGID_7_);} .st21{fill:url(#SVGID_8_);} </style><g transform=\"matrix(2.3327641,0,0,2.3327641,-431.80457,-323.21807)\"><g id=\"g48\" transform=\"translate(-24.881692,25.94805)\"><g id=\"g47\"><path class=\"st1\" d=\"m 258.9,119.7 c -3,-0.9 -6,-1.8 -9,-2.7 -4.6,-1.4 -9.2,-2.8 -14,-2.5 -2.8,0.2 -6.1,1.3 -6.9,4 -0.6,2 -1.6,7.3 -1.3,7.9 1.5,3.4 13.9,6.7 18.3,6.7\" id=\"path2\" /><path d=\"m 392.6,177.9 c -1.4,1.4 -2.2,3.5 -2.5,5.5 -0.2,1.4 -0.1,3 0.5,4.3 0.6,1.3 1.8,2.3 3.1,3 1.3,0.6 2.8,0.9 4.3,0.9 1.1,0 2.3,-0.1 3.1,-0.9 0.6,-0.7 0.8,-1.6 0.9,-2.5 0.2,-2.3 -0.1,-4.7 -0.9,-6.9 -0.4,-1.1 -0.9,-2.3 -1.8,-3.1 -1.7,-1.8 -4.5,-2.2 -6.4,-0.5 -0.1,0 -0.2,0.1 -0.3,0.2 z\" id=\"path3\" /><path class=\"st2\" d=\"m 358.5,164.2 c -1,-1 0,-2.7 1,-3.7 5.8,-5.2 15.1,-4.6 21.8,-0.6 10.9,6.6 15.6,19.9 17.2,32.5 0.6,5.2 0.9,10.6 -0.5,15.7 -1.4,5.1 -4.6,9.9 -9.3,12.1 -1.1,0.5 -2.3,0.9 -3.4,0.5 -1.1,-0.4 -1.9,-1.8 -1.2,-2.8 -9.4,-13.6 -19,-26.8 -20.9,-43.2 -0.5,-4.1 -1.8,-7.4 -4.7,-10.5 z\" id=\"path4\" /><path class=\"st1\" d=\"m 220.05771,133 c 34.58546,-18 79.30862,-19.6 112.16964,-8.7 23.69194,7.9 41.3449,26.7 49.4515,50 7.11442,20.6 7.1441,43.6 3.04307,65.7 -7.4942,40.2 -26.19597,77.9 -48.96683,112.6 -12.56244,19 -24.57093,36 -44.23305,48.5 -38.69347,24.6 -88.90104,22.1 -129.28516,11.5 -19.50745,-5.1 -38.36842,-17.3 -44.32901,-37.3 -3.81479,-12.8 -2.07563,-27.6 4.5788,-40 13.50886,-24.8 46.15565,-38.4 50.82627,-67.9 1.36964,-8.7 -0.26842,-17.3 -1.62187,-25.7 -3.7989,-23.4 -5.37477,-45.8 6.68784,-68.7 9.46237,-17.7 24.28607,-31 41.6788,-40 z\" id=\"path6\" /><path class=\"st2\" d=\"m 264.5,174.9 c -0.5,0.5 -0.9,1 -1.3,1.6 -9,11.6 -12,27.9 -9.3,42.1 1.7,9 5.9,17.9 13.2,23.4 19.3,14.6 51.5,13.5 68.4,-1.5 24.4,-21.7 13,-67.6 -14,-78.8 -17.6,-7.2 -43.7,-1.6 -57,13.2 z\" id=\"path9\" /><g id=\"g11\"><path class=\"st2\" d=\"m 382.1,237.1 c 1.4,-0.1 2.9,-0.1 4.3,0.1 0.3,0 0.7,0.1 1,0.4 0.2,0.3 0.4,0.7 0.5,1.1 1,3.9 0.5,8.2 0.1,12.4 -0.1,0.9 -0.2,1.8 -0.6,2.6 -1,2.1 -3.1,2.7 -4.7,2.7 -0.1,0 -0.2,0 -0.3,-0.1 -0.3,-0.2 -0.3,-0.7 -0.2,-1.2 0.3,-5.9 -0.1,-11.9 -0.1,-18 0,0.1 0,0.1 0,0 z\" id=\"path10\" /><path class=\"st2\" d=\"m 378.7,236.8 c -1.4,0.4 -2.5,2 -2.8,4.4 -0.5,4.4 -0.7,8.9 -0.5,13.4 0,0.9 0.1,1.9 0.5,2.4 0.2,0.3 0.5,0.4 0.8,0.4 1.6,0.3 4.1,-0.6 5.6,-1 0,0 0,-5.2 -0.1,-8 -0.1,-2.8 -0.1,-6.1 -0.2,-8.9 0,-0.6 0,-1.5 0,-2.2 0.1,-0.7 -2.6,-0.7 -3.3,-0.5 z\" id=\"path11\" /></g><path class=\"st0\" d=\"m 358.3,231.8 c -0.3,2.2 0.1,4.7 1.7,7.4 2.6,4.4 7,6.1 11.9,5.8 8.9,-0.6 25.3,-5.4 27.5,-15.7 0.6,-3 -0.3,-6.1 -2.2,-8.5 -6.2,-7.8 -17.8,-5.7 -25.6,-2 -5.9,2.7 -12.4,7 -13.3,13 z\" id=\"path12\" /><path class=\"st3\" d=\"m 386.4,208.6 c 2.2,1.4 3.7,3.8 4,7 0.3,3.6 -1.4,7.5 -5,8.8 -2.9,1.1 -6.2,0.6 -9.1,-0.4 -2.9,-1 -5.8,-2.8 -6.8,-5.7 -0.7,-2 -0.3,-4.3 0.7,-6.1 1.1,-1.8 2.8,-3.2 4.7,-4.1 3.9,-1.8 8.4,-1.6 11.5,0.5 z\" id=\"path13\" /><path class=\"st0\" d=\"m 414.7,262.6 c 2.4,0.6 4.8,2.1 5.6,4.4 0.8,2.3 0.1,4.9 -1.6,6.7 -1.7,1.8 -4.2,2.5 -6.6,2.5 -0.8,0 -1.7,-0.1 -2.4,-0.5 -2.5,-1.1 -3.5,-4 -4.2,-6.6 -1.8,-6.8 3.6,-7.8 9.2,-6.5 z\" id=\"path14\" /><g id=\"g17\"><path class=\"st4\" d=\"m 267.1,284.7 c 2.3,-4.5 141.3,-36.2 144.7,-31.6 3.4,4.5 15.8,88.2 9,90.4 -6.8,2.3 -119.8,37.3 -126.6,35 -6.8,-2.3 -29.4,-89.3 -27.1,-93.8 z\" id=\"path15\" /><path class=\"st5\" d=\"m 294.2,378.5 c 0,0 54.3,-74.6 59.9,-76.9 5.7,-2.3 67.3,41.3 67.3,41.3\" id=\"path16\" /><path class=\"st4\" d=\"m 267,287.7 c 0,0 86,38.8 91.6,36.6 5.7,-2.3 53.1,-71.2 53.1,-71.2\" id=\"path17\" /></g><g id=\"g33\"><linearGradient id=\"SVGID_2_\" gradientUnits=\"userSpaceOnUse\" x1=\"265.03339\" y1=\"280.2951\" x2=\"275.3009\" y2=\"302.31369\"><stop offset=\"5.152266e-07\" style=\"stop-color:#71CAEE\" id=\"stop25\" /><stop offset=\"4.104413e-02\" style=\"stop-color:#83CAE2\" id=\"stop26\" /><stop offset=\"0.1206\" style=\"stop-color:#9FCACE\" id=\"stop27\" /><stop offset=\"0.2107\" style=\"stop-color:#B6CBBE\" id=\"stop28\" /><stop offset=\"0.3139\" style=\"stop-color:#C7CBB1\" id=\"stop29\" /><stop offset=\"0.4384\" style=\"stop-color:#D4CBA8\" id=\"stop30\" /><stop offset=\"0.6058\" style=\"stop-color:#DBCBA3\" id=\"stop31\" /><stop offset=\"1\" style=\"stop-color:#DDCBA2\" id=\"stop32\" /></linearGradient><path class=\"st7\" d=\"m 261.9,283.5 c -0.1,4.2 4.3,7.3 8.4,7.6 4.1,0.3 8.2,-1.3 12.2,-2.6 1.4,-0.4 2.9,-0.8 4.2,-0.2 1.8,0.9 2.7,4.1 1.8,5.9 -0.9,1.8 -3.4,3.5 -5.3,4.4 -6.5,3 -12.9,3.6 -19.9,2 -5.3,-1.2 -11.3,-4.3 -13,-13.5\" id=\"path32\" style=\"fill:url(#SVGID_2_)\" /><path class=\"st5\" d=\"m 261.9,283.5 c -0.1,4.2 4.3,7.3 8.4,7.6 4.1,0.3 8.2,-1.3 12.2,-2.6 1.4,-0.4 2.9,-0.8 4.2,-0.2 1.8,0.9 2.7,4.1 1.8,5.9 -0.9,1.8 -3.4,3.5 -5.3,4.4 -6.5,3 -12.9,3.6 -19.9,2 -5.3,-1.2 -11.3,-4.3 -13,-13.5\" id=\"path33\" /></g><path d=\"m 318.4,198.4 c -2,-0.3 -4.1,0.1 -5.9,1.3 -3.2,2.1 -4.7,6.2 -4.7,9.9 0,1.9 0.4,3.8 1.4,5.3 1.2,1.7 3.1,2.9 5.2,3.4 3.4,0.8 8.2,0.7 10.5,-2.5 1,-1.5 1.4,-3.3 1.5,-5.1 0.5,-5.7 -1.8,-11.4 -8,-12.3 z\" id=\"path34\" /><path class=\"st8\" d=\"m 320.4,203.3 c 0.9,0.3 1.7,0.8 2.1,1.7 0.4,0.8 0.4,1.7 0.3,2.5 -0.1,1 -0.6,2 -1.5,2.7 -0.7,0.5 -1.7,0.7 -2.6,0.5 -0.9,-0.2 -1.7,-0.8 -2.2,-1.6 -1.1,-1.6 -0.9,-4.4 0.9,-5.5 0.9,-0.4 2,-0.6 3,-0.3 z\" id=\"path35\" /></g></g></g></svg>"
24645
+ },
24646
+ zentik: {
24647
+ contentType: "image/png",
24648
+ body: Uint8Array.from(Buffer.from("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", "base64"))
24649
+ }
24650
+ };
24651
+ //#endregion
24652
+ //#region src/icon-routes.ts
24653
+ /**
24654
+ * Serves the bundled per-kind brand icons (`icons-data.ts`) over the addon's
24655
+ * own HTTP surface via the `addon-routes` cap. One public GET route per kind:
24656
+ *
24657
+ * GET /addon/notifiers/icons/<kind> → the bundled brand icon
24658
+ *
24659
+ * Each route sets the icon's OWN content-type (`image/svg+xml` for the vector
24660
+ * icons, `image/png` for the raster zentik logo) so a browser `<img src>`
24661
+ * renders it — an SVG that wraps a raster `data:` URI is blocked by the strict
24662
+ * `<img>` SVG mode, so zentik ships as its real PNG bytes instead.
24663
+ *
24664
+ * The bytes ship INSIDE the addon — no external CDN / hotlink — so the icons are
24665
+ * self-contained and CSP-safe. `listTargetKinds` stamps each kind with the
24666
+ * matching `iconUrl` (see `iconUrlFor`), letting the admin UI render
24667
+ * `<img src=iconUrl>` instead of a text label.
24668
+ *
24669
+ * We register a STATIC route per kind rather than one `/:kind` param route: the
24670
+ * forked-addon route bridge re-matches the pattern against itself, which would
24671
+ * clobber a `:kind` path param with the literal token `':kind'`. Static paths
24672
+ * carry no param, so they route correctly across the process boundary.
24673
+ *
24674
+ * ── Why the addon pre-encodes and owns `content-encoding` ────────────────────
24675
+ * The hub serves an addon route by piping the response THROUGH its global
24676
+ * `@fastify/compress`. For a forked addon, our reply body crosses the UDS route
24677
+ * bridge as opaque bytes and the hub then re-encodes them — and on that bridged
24678
+ * path the compressor emits an EMPTY brotli stream for any compressible body
24679
+ * over its 1 KiB threshold (`content-encoding: br` + `content-length: 0`), so a
24680
+ * browser `<img>` decodes nothing and shows a broken icon. Small icons (< 1 KiB)
24681
+ * and the raster PNG (a non-compressible type) slip under the compressor and
24682
+ * render, which is why only the larger SVGs broke.
24683
+ *
24684
+ * The fix is to make the compressor SKIP us: it leaves a response untouched when
24685
+ * `content-encoding` is already set to a non-identity coding. So each route
24686
+ * negotiates the request's `Accept-Encoding`, serves a pre-computed br/gzip/
24687
+ * deflate variant (or identity when the client accepts none), and stamps the
24688
+ * matching `content-encoding` itself. Bytes we own, correctly framed, reach the
24689
+ * browser intact. Mirrors the `compress: false` treatment the hub's own binary
24690
+ * pull route already uses for the same class of bug.
24691
+ */
24692
+ /** The addon id — also the `/addon/<id>/…` route prefix. Mirrors `NOTIFIERS_ADDON_ID`. */
24693
+ var NOTIFIERS_ROUTE_ID = "notifiers";
24694
+ /** One day; icons are immutable per addon version. */
24695
+ var CACHE_CONTROL = "public, max-age=86400, immutable";
24696
+ var ICON_KINDS = new Set(Object.keys(NOTIFIER_ICONS));
24697
+ /** `true` when a bundled brand icon exists for `kind`. */
24698
+ function hasIcon(kind) {
24699
+ return ICON_KINDS.has(kind);
24700
+ }
24701
+ /**
24702
+ * The stable per-kind icon URL, or `undefined` when no icon is bundled. Stamped
24703
+ * onto each descriptor by `listTargetKinds`. Root-relative so it resolves
24704
+ * against whatever origin the admin UI is served from.
24705
+ */
24706
+ function iconUrlFor(kind) {
24707
+ return hasIcon(kind) ? `/addon/${NOTIFIERS_ROUTE_ID}/icons/${kind}` : void 0;
24708
+ }
24709
+ /** Pre-encode one bundled icon into every content coding. */
24710
+ function encodeIcon(icon) {
24711
+ const identity = typeof icon.body === "string" ? Buffer.from(icon.body, "utf8") : Buffer.from(icon.body);
24712
+ return {
24713
+ contentType: icon.contentType,
24714
+ identity,
24715
+ br: (0, node_zlib.brotliCompressSync)(identity),
24716
+ gzip: (0, node_zlib.gzipSync)(identity),
24717
+ deflate: (0, node_zlib.deflateSync)(identity)
24718
+ };
24719
+ }
24720
+ /** Pick the best coding the client accepts (br › gzip › deflate), or `null` for
24721
+ * identity when the header names none of them. Token-matched so `gzipx` or a
24722
+ * `q=0` refusal don't false-positive. */
24723
+ function negotiateEncoding(acceptEncoding) {
24724
+ const tokens = acceptEncoding.toLowerCase().split(",").map((part) => part.trim().split(";")[0]?.trim());
24725
+ for (const coding of [
24726
+ "br",
24727
+ "gzip",
24728
+ "deflate"
24729
+ ]) if (tokens.includes(coding)) return coding;
24730
+ return null;
24731
+ }
24732
+ /** One static GET route serving the bundled icon for a single kind, with the
24733
+ * icon's own content-type (svg vs png) and a client-negotiated content-coding
24734
+ * the addon owns end-to-end (see the file header for why). */
24735
+ function iconRoute(kind) {
24736
+ const encoded = encodeIcon(NOTIFIER_ICONS[kind]);
24737
+ return {
24738
+ method: "GET",
24739
+ path: `/icons/${kind}`,
24740
+ access: "public",
24741
+ description: `Official brand icon (${encoded.contentType}) for the '${kind}' notifier kind.`,
24742
+ handler: async (request, reply) => {
24743
+ const coding = negotiateEncoding(String(request.headers["accept-encoding"] ?? ""));
24744
+ reply.code(200).type(encoded.contentType).header("cache-control", CACHE_CONTROL).header("vary", "accept-encoding");
24745
+ if (coding !== null) reply.header("content-encoding", coding);
24746
+ reply.send(coding !== null ? encoded[coding] : encoded.identity);
24747
+ }
24748
+ };
24749
+ }
24750
+ /** Build the `addon-routes` provider that serves the bundled icons. */
24751
+ function createIconRouteProvider() {
24752
+ return buildAddonRouteProvider(NOTIFIERS_ROUTE_ID, Object.keys(NOTIFIER_ICONS).map(iconRoute));
24753
+ }
23488
24754
  //#endregion
23489
24755
  //#region src/adapters/discord.ts
23490
24756
  /** Discord message `content` hard limit. */
@@ -25925,11 +27191,19 @@ function createNotificationOutputProvider(deps) {
25925
27191
  renderedAs: prepared.renderedAs
25926
27192
  };
25927
27193
  }
27194
+ const iconUrlFor = deps.iconUrlFor;
25928
27195
  return {
25929
- listTargetKinds: async () => deps.adapters.map((a) => ({
25930
- ...a.descriptor,
25931
- addonId
25932
- })),
27196
+ listTargetKinds: async () => deps.adapters.map((a) => {
27197
+ const descriptor = {
27198
+ ...a.descriptor,
27199
+ addonId
27200
+ };
27201
+ const iconUrl = iconUrlFor?.(a.descriptor.kind);
27202
+ return iconUrl !== void 0 ? {
27203
+ ...descriptor,
27204
+ iconUrl
27205
+ } : descriptor;
27206
+ }),
25933
27207
  listTargets: async () => {
25934
27208
  return (await store.list()).map((target) => {
25935
27209
  const adapter = adapterByKind.get(target.kind);
@@ -26026,6 +27300,12 @@ function createApiSettingsStorePort(api) {
26026
27300
  collection,
26027
27301
  key: id
26028
27302
  });
27303
+ },
27304
+ declareCollection: async (collection, columns) => {
27305
+ await api.settingsStore.declareCollection.mutate({
27306
+ collection,
27307
+ columns: columns.map((c) => ({ ...c }))
27308
+ });
26029
27309
  }
26030
27310
  };
26031
27311
  }
@@ -26056,6 +27336,9 @@ function createMemorySettingsStorePort() {
26056
27336
  },
26057
27337
  remove: async (collection, id) => {
26058
27338
  bucket(collection).delete(id);
27339
+ },
27340
+ declareCollection: async (collection) => {
27341
+ bucket(collection);
26059
27342
  }
26060
27343
  };
26061
27344
  }
@@ -26076,6 +27359,13 @@ function createMemorySettingsStorePort() {
26076
27359
  */
26077
27360
  /** The default collection name (see `settings-store` addonId-prefix note). */
26078
27361
  var TARGETS_COLLECTION = "notification-targets";
27362
+ /** KV JSON-blob shape — a single `data` column routes the row through the
27363
+ * settings backend's canonical key/value path (id TEXT PK, data TEXT). */
27364
+ var KV_BLOB_COLUMNS = [{
27365
+ name: "data",
27366
+ type: "TEXT",
27367
+ notNull: true
27368
+ }];
26079
27369
  function rowToTarget(row) {
26080
27370
  const candidate = {
26081
27371
  id: row.id,
@@ -26100,6 +27390,12 @@ var TargetStore = class {
26100
27390
  this.store = store;
26101
27391
  this.collection = collection;
26102
27392
  }
27393
+ /** Declare the backing collection before any read/write. The SQLite settings
27394
+ * backend rejects undeclared collections (fail-fast), so this MUST run before
27395
+ * `list`/`getById`/`upsert`/`delete` — mirrors addon-ai's ProfileStore.init. */
27396
+ async init() {
27397
+ await this.store.declareCollection(this.collection, KV_BLOB_COLUMNS);
27398
+ }
26103
27399
  async list() {
26104
27400
  const rows = await this.store.query(this.collection);
26105
27401
  const targets = [];
@@ -26171,15 +27467,22 @@ var NotifiersAddon = class extends BaseAddon {
26171
27467
  const api = this.ctx.api;
26172
27468
  const port = api ? createApiSettingsStorePort(api) : createMemorySettingsStorePort();
26173
27469
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
27470
+ const store = new TargetStore(port);
27471
+ await store.init();
26174
27472
  const provider = createNotificationOutputProvider({
26175
- store: new TargetStore(port),
27473
+ store,
26176
27474
  adapters: ADAPTERS,
26177
- logger: this.ctx.logger.child("notification-output")
27475
+ logger: this.ctx.logger.child("notification-output"),
27476
+ iconUrlFor
26178
27477
  });
27478
+ const iconRouteProvider = createIconRouteProvider();
26179
27479
  this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: ADAPTERS.length } });
26180
27480
  return [{
26181
27481
  capability: notificationOutputCapability,
26182
27482
  provider
27483
+ }, {
27484
+ capability: addonRoutesCapability,
27485
+ provider: iconRouteProvider
26183
27486
  }];
26184
27487
  }
26185
27488
  async onShutdown() {