@camstack/addon-notifiers 1.1.22 → 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 +1478 -133
  2. package/dist/addon.mjs +1478 -133
  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-BC9Yqte7.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([
@@ -10860,7 +11050,15 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10860
11050
  image: _instanceof(Uint8Array).optional(),
10861
11051
  referenceImage: string().optional(),
10862
11052
  deviceId: number().optional(),
10863
- sessionId: string().optional()
11053
+ sessionId: string().optional(),
11054
+ /**
11055
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
11056
+ * reference-image, and detail-subtree calls. 'frame' is the live
11057
+ * per-frame dispatch: ONLY root-plane steps run; crop children
11058
+ * (inputClasses ≠ null) are skipped and served per-track via
11059
+ * pipelineRunner.runDetailSubtree (two-plane design).
11060
+ */
11061
+ plane: _enum(["full", "frame"]).optional()
10864
11062
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
10865
11063
  engine: PipelineEngineChoiceSchema.optional(),
10866
11064
  steps: array(PipelineStepInputSchema).min(1),
@@ -11003,6 +11201,28 @@ var NativeCropResultSchema = object({
11003
11201
  width: number().int().positive(),
11004
11202
  height: number().int().positive()
11005
11203
  });
11204
+ /** Parent detection context passed to `runDetailSubtree` — the crop's
11205
+ * originating detection, in FRAME-space coordinates. Reuses
11206
+ * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
11207
+ * the coordinates are frame-space rather than getNativeCrop's
11208
+ * normalized [0,1] convention). */
11209
+ var DetailParentSchema = object({
11210
+ bbox: NativeCropBboxSchema,
11211
+ className: string()
11212
+ });
11213
+ /** One child-step result from `runDetailSubtree` — an embedding, label,
11214
+ * or refined detection produced by running the crop-subtree on a
11215
+ * single tracked detection. */
11216
+ var DetailResultSchema = object({
11217
+ stepId: string(),
11218
+ className: string(),
11219
+ score: number(),
11220
+ /** FRAME-space bbox (already mapped back from crop space). */
11221
+ bbox: NativeCropBboxSchema.optional(),
11222
+ embedding: string().optional(),
11223
+ label: string().optional(),
11224
+ alignedCropJpeg: string().optional()
11225
+ });
11006
11226
  /**
11007
11227
  * Per-camera tunable ranges + defaults. Single source of truth used
11008
11228
  * by both the Zod data schema (validation + default fallback) and
@@ -11263,7 +11483,13 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
11263
11483
  handle: FrameHandleSchema,
11264
11484
  bbox: NativeCropBboxSchema,
11265
11485
  maxWidth: number().int().positive().optional()
11266
- }), NativeCropResultSchema.nullable());
11486
+ }), NativeCropResultSchema.nullable()), method(object({
11487
+ deviceId: number(),
11488
+ frameHandle: FrameHandleSchema.optional(),
11489
+ cropJpeg: string().optional(),
11490
+ parent: DetailParentSchema,
11491
+ steps: array(string()).optional()
11492
+ }), object({ details: array(DetailResultSchema) }).nullable(), { kind: "mutation" });
11267
11493
  object({
11268
11494
  detected: boolean(),
11269
11495
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -11875,6 +12101,140 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
11875
12101
  deviceId: number(),
11876
12102
  status: PtzAutotrackStatusSchema
11877
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
+ });
11878
12238
  object({
11879
12239
  /** Whether the script is currently executing. */
11880
12240
  isRunning: boolean(),
@@ -12282,6 +12642,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
12282
12642
  * with the per-track detail panel and live overlay. */
12283
12643
  trackIds: array(string()).readonly()
12284
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
+ });
12285
12675
  var CameraOccupancySnapshotSchema = object({
12286
12676
  /** Frame timestamp of the inference result that produced this snapshot. */
12287
12677
  ts: number().int(),
@@ -12290,10 +12680,15 @@ var CameraOccupancySnapshotSchema = object({
12290
12680
  frameHeight: number().int().nonnegative(),
12291
12681
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
12292
12682
  zones: array(ZoneScopeBreakdownSchema).readonly(),
12293
- /** 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). */
12294
12685
  frame: PerScopeBreakdownSchema,
12295
12686
  /** Detections that landed outside every zone. Empty when no zones defined. */
12296
- 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()
12297
12692
  });
12298
12693
  /**
12299
12694
  * Time-series resolution. The history methods return one bucket per
@@ -12663,7 +13058,26 @@ var InvokeReplyEnvelopeSchema = object({
12663
13058
  /** Set when the handler called `reply.type(mime)`. */
12664
13059
  contentType: string().optional()
12665
13060
  });
12666
- 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
+ };
12667
13081
  var ConfigTabDeclarationSchema = object({
12668
13082
  id: string(),
12669
13083
  label: string(),
@@ -12896,7 +13310,12 @@ var NotificationRuleConditionsSchema = object({
12896
13310
  clipDescription: object({
12897
13311
  text: string().min(1),
12898
13312
  minSimilarity: number().min(0).max(1)
12899
- }).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()
12900
13319
  });
12901
13320
  var NotificationRuleTemplateSchema = object({
12902
13321
  title: string(),
@@ -13154,11 +13573,19 @@ method(object({
13154
13573
  * login page needs NO change.
13155
13574
  *
13156
13575
  * - `widget` — a Module-Federation widget the login page mounts (via
13157
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13158
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13159
- * addon bundle. The referenced widget also declares `preAuth: true` in
13160
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13161
- * 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.
13162
13589
  *
13163
13590
  * Every contribution carries a `stage`:
13164
13591
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -13172,30 +13599,45 @@ method(object({
13172
13599
  */
13173
13600
  /** When a login method renders in the two-phase login flow. */
13174
13601
  var LoginStageEnum = _enum(["primary", "second-factor"]);
13175
- /** One login-method contribution — redirect button OR pre-auth widget. */
13176
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13177
- kind: literal("redirect"),
13178
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13179
- id: string(),
13180
- /** Operator-facing button label. */
13181
- label: string(),
13182
- /** lucide-react icon name. */
13183
- icon: string().optional(),
13184
- /** Addon-owned HTTP route the button navigates to (GET). */
13185
- startUrl: string(),
13186
- stage: LoginStageEnum
13187
- }), object({
13188
- kind: literal("widget"),
13189
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13190
- id: string(),
13191
- /** Owning addon id drives the public bundle URL + the MF namespace. */
13192
- addonId: string(),
13193
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13194
- bundle: string(),
13195
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
13196
- remote: WidgetRemoteSchema,
13197
- stage: LoginStageEnum
13198
- })]);
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
+ ]);
13199
13641
  method(_void(), array(LoginMethodContributionSchema).readonly());
13200
13642
  /**
13201
13643
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -15023,14 +15465,14 @@ var TargetKindCapsSchema = object({
15023
15465
  * the union is large and not meant for runtime validation here; the exported
15024
15466
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
15025
15467
  */
15026
- var ConfigSchemaPassthrough = unknown();
15468
+ var ConfigSchemaPassthrough$1 = unknown();
15027
15469
  var TargetKindSchema = object({
15028
15470
  kind: string(),
15029
15471
  label: string(),
15030
15472
  icon: string(),
15031
15473
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
15032
15474
  addonId: string(),
15033
- configSchema: ConfigSchemaPassthrough,
15475
+ configSchema: ConfigSchemaPassthrough$1,
15034
15476
  supportsDiscovery: boolean(),
15035
15477
  caps: TargetKindCapsSchema
15036
15478
  });
@@ -15097,101 +15539,393 @@ var notificationOutputCapability = {
15097
15539
  }
15098
15540
  };
15099
15541
  /**
15100
- * Zod schemas for persisted record types.
15101
- *
15102
- * These schemas serve as the single source of truth for types that are
15103
- * stored in the settings-store backend and retrieved via tRPC. They
15104
- * replace the manual TypeScript interfaces with schema-derived types,
15105
- * enabling runtime validation when reading from the backend.
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.
15106
15545
  *
15107
- * Convention: each schema is named `<Type>Schema`, the inferred TS type
15108
- * keeps the original name for backward compatibility.
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).
15109
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
+ })]);
15110
15578
  /**
15111
- * Auth model (v22026-05-12 caps-only):
15112
- *
15113
- * Two principals:
15114
- * - `isAdmin: true` — full bypass. Admin scope set is ignored.
15115
- * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
15116
- *
15117
- * No role enum. There used to be `viewer / agent / scoped` and earlier
15118
- * `super_admin` — collapsed onto the single boolean because every
15119
- * non-admin caller is governed by the same scope-check rules; the
15120
- * distinction was only ever used to route role-specific procedures
15121
- * which we no longer have. Agents are admin sessions issued via
15122
- * `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
15123
- * with `isAdmin: false` + an explicit scope list.
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).
15124
15582
  */
15125
- /** Per-method access flavour. Materialised on every cap method via codegen. */
15126
- var MethodAccessSchema = _enum([
15127
- "view",
15128
- "create",
15129
- "delete"
15130
- ]);
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
+ });
15131
15603
  /**
15132
- * A single scope grant. Three forms checked in matcher with OR
15133
- * semantics; the first match decides whether the call passes:
15134
- *
15135
- * - `category` broadest. `target: 'device' | 'system'` selects every
15136
- * cap with the matching `scope` declaration. New caps inherit grants
15137
- * automatically — adding a device cap to the codebase makes it
15138
- * reachable to every user with `category:device [...]` without a
15139
- * re-grant. Use this for "family viewer" / "operator" tiers.
15140
- *
15141
- * - `addon` — every cap that ships inside a given addon id. Niche;
15142
- * useful when an integration needs to interact with one specific
15143
- * addon's surface regardless of how many caps it exports.
15144
- *
15145
- * - `capability` — the most precise form. Single cap name. Use for
15146
- * integration tokens where you want to grant `addons.installPackage`
15147
- * and nothing else.
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.
15148
15609
  *
15149
- * `access` is REQUIRED with min 1 element. `admin` bypass is the
15150
- * "give me everything" escape; there is no wildcard scope.
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).
15151
15613
  */
15152
- var AllowedProviderSchema = union([literal("*"), array(string())]);
15153
- var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
15154
- var CapScopeSchema = _enum(["device", "system"]);
15155
- var TokenScopeSchema = discriminatedUnion("type", [
15614
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
15156
15615
  object({
15157
- type: literal("category"),
15158
- target: CapScopeSchema,
15159
- access: array(MethodAccessSchema).min(1)
15160
- }),
15161
- object({
15162
- type: literal("capability"),
15163
- target: string(),
15164
- access: array(MethodAccessSchema).min(1)
15616
+ kind: literal("catalog"),
15617
+ catalogId: string()
15165
15618
  }),
15166
15619
  object({
15167
- type: literal("addon"),
15168
- target: string(),
15169
- access: array(MethodAccessSchema).min(1)
15620
+ kind: literal("url"),
15621
+ url: string(),
15622
+ sha256: string().optional()
15170
15623
  }),
15171
15624
  object({
15172
- type: literal("device"),
15173
- /**
15174
- * One or more deviceIds (serialised as strings for wire-format
15175
- * consistency with the rest of the union). Matcher accepts if
15176
- * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
15177
- * of one scope-per-device when granting access to a set of cameras.
15178
- */
15179
- targets: array(string()).min(1),
15180
- access: array(MethodAccessSchema).min(1)
15625
+ kind: literal("path"),
15626
+ path: string()
15181
15627
  })
15182
15628
  ]);
15183
- object({
15184
- id: string(),
15185
- username: string(),
15186
- passwordHash: string(),
15187
- /**
15188
- * Admin bypass. When true, the middleware skips the scope-access
15189
- * check entirely. There is no other axis of privilege; the legacy
15190
- * role enum collapsed onto this boolean in v2.
15191
- */
15192
- isAdmin: boolean().default(false),
15193
- allowedProviders: AllowedProviderSchema,
15194
- allowedDevices: AllowedDevicesSchema,
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
+ /**
15834
+ * Zod schemas for persisted record types.
15835
+ *
15836
+ * These schemas serve as the single source of truth for types that are
15837
+ * stored in the settings-store backend and retrieved via tRPC. They
15838
+ * replace the manual TypeScript interfaces with schema-derived types,
15839
+ * enabling runtime validation when reading from the backend.
15840
+ *
15841
+ * Convention: each schema is named `<Type>Schema`, the inferred TS type
15842
+ * keeps the original name for backward compatibility.
15843
+ */
15844
+ /**
15845
+ * Auth model (v2 — 2026-05-12 caps-only):
15846
+ *
15847
+ * Two principals:
15848
+ * - `isAdmin: true` — full bypass. Admin scope set is ignored.
15849
+ * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
15850
+ *
15851
+ * No role enum. There used to be `viewer / agent / scoped` and earlier
15852
+ * `super_admin` — collapsed onto the single boolean because every
15853
+ * non-admin caller is governed by the same scope-check rules; the
15854
+ * distinction was only ever used to route role-specific procedures
15855
+ * which we no longer have. Agents are admin sessions issued via
15856
+ * `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
15857
+ * with `isAdmin: false` + an explicit scope list.
15858
+ */
15859
+ /** Per-method access flavour. Materialised on every cap method via codegen. */
15860
+ var MethodAccessSchema = _enum([
15861
+ "view",
15862
+ "create",
15863
+ "delete"
15864
+ ]);
15865
+ /**
15866
+ * A single scope grant. Three forms — checked in matcher with OR
15867
+ * semantics; the first match decides whether the call passes:
15868
+ *
15869
+ * - `category` — broadest. `target: 'device' | 'system'` selects every
15870
+ * cap with the matching `scope` declaration. New caps inherit grants
15871
+ * automatically — adding a device cap to the codebase makes it
15872
+ * reachable to every user with `category:device [...]` without a
15873
+ * re-grant. Use this for "family viewer" / "operator" tiers.
15874
+ *
15875
+ * - `addon` — every cap that ships inside a given addon id. Niche;
15876
+ * useful when an integration needs to interact with one specific
15877
+ * addon's surface regardless of how many caps it exports.
15878
+ *
15879
+ * - `capability` — the most precise form. Single cap name. Use for
15880
+ * integration tokens where you want to grant `addons.installPackage`
15881
+ * and nothing else.
15882
+ *
15883
+ * `access` is REQUIRED with min 1 element. `admin` bypass is the
15884
+ * "give me everything" escape; there is no wildcard scope.
15885
+ */
15886
+ var AllowedProviderSchema = union([literal("*"), array(string())]);
15887
+ var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
15888
+ var CapScopeSchema = _enum(["device", "system"]);
15889
+ var TokenScopeSchema = discriminatedUnion("type", [
15890
+ object({
15891
+ type: literal("category"),
15892
+ target: CapScopeSchema,
15893
+ access: array(MethodAccessSchema).min(1)
15894
+ }),
15895
+ object({
15896
+ type: literal("capability"),
15897
+ target: string(),
15898
+ access: array(MethodAccessSchema).min(1)
15899
+ }),
15900
+ object({
15901
+ type: literal("addon"),
15902
+ target: string(),
15903
+ access: array(MethodAccessSchema).min(1)
15904
+ }),
15905
+ object({
15906
+ type: literal("device"),
15907
+ /**
15908
+ * One or more deviceIds (serialised as strings for wire-format
15909
+ * consistency with the rest of the union). Matcher accepts if
15910
+ * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
15911
+ * of one scope-per-device when granting access to a set of cameras.
15912
+ */
15913
+ targets: array(string()).min(1),
15914
+ access: array(MethodAccessSchema).min(1)
15915
+ })
15916
+ ]);
15917
+ object({
15918
+ id: string(),
15919
+ username: string(),
15920
+ passwordHash: string(),
15921
+ /**
15922
+ * Admin bypass. When true, the middleware skips the scope-access
15923
+ * check entirely. There is no other axis of privilege; the legacy
15924
+ * role enum collapsed onto this boolean in v2.
15925
+ */
15926
+ isAdmin: boolean().default(false),
15927
+ allowedProviders: AllowedProviderSchema,
15928
+ allowedDevices: AllowedDevicesSchema,
15195
15929
  /**
15196
15930
  * Scopes granted to this user. Admins bypass; their `scopes` is
15197
15931
  * ignored. Non-admins without scopes are locked out of every
@@ -15298,6 +16032,22 @@ var TrackSnapshotSchema = object({
15298
16032
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
15299
16033
  mediaKey: string()
15300
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
+ });
15301
16051
  var TrackSchema = object({
15302
16052
  trackId: string(),
15303
16053
  deviceId: number(),
@@ -15312,6 +16062,10 @@ var TrackSchema = object({
15312
16062
  snapshots: array(TrackSnapshotSchema).readonly(),
15313
16063
  /** Deduplicated zones the track has entered at least once. */
15314
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(),
15315
16069
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15316
16070
  totalDistance: number(),
15317
16071
  state: TrackStateSchema,
@@ -15325,7 +16079,11 @@ var TrackSchema = object({
15325
16079
  bestEventId: string().optional(),
15326
16080
  /** Tag of the importance sub-signal that dominated the score
15327
16081
  * (identity|dwell|proximity|class|confidence|travel|zone). */
15328
- 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()
15329
16087
  });
15330
16088
  var BaseEventFields = {
15331
16089
  id: string(),
@@ -15473,7 +16231,7 @@ var KeyEventSchema = object({
15473
16231
  /** Track lifetime in ms (lastSeen - firstSeen). */
15474
16232
  windowMs: number().optional()
15475
16233
  });
15476
- var TrackedDetectionSchema = object({
16234
+ object({
15477
16235
  trackId: string(),
15478
16236
  className: string(),
15479
16237
  confidence: number(),
@@ -15481,6 +16239,23 @@ var TrackedDetectionSchema = object({
15481
16239
  zones: array(string()).readonly(),
15482
16240
  state: TrackStateSchema
15483
16241
  });
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
+ });
15484
16259
  var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
15485
16260
  var SearchObjectEventsInput = object({
15486
16261
  text: string(),
@@ -15491,6 +16266,20 @@ var SearchObjectEventsInput = object({
15491
16266
  limit: number().default(50),
15492
16267
  minScore: number().min(0).max(1).default(.2)
15493
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
+ });
15494
16283
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
15495
16284
  deviceId: number(),
15496
16285
  trackId: string()
@@ -15522,6 +16311,24 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15522
16311
  }), {
15523
16312
  kind: "mutation",
15524
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"
15525
16332
  }), method(object({
15526
16333
  eventId: string(),
15527
16334
  kind: MediaFileKindEnum.optional()
@@ -15530,7 +16337,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15530
16337
  timestamp: number(),
15531
16338
  frameWidth: number(),
15532
16339
  frameHeight: number(),
15533
- detections: array(TrackedDetectionSchema).readonly()
16340
+ detections: array(OverlayDetectionSchema).readonly()
15534
16341
  }), object({
15535
16342
  deviceId: number(),
15536
16343
  trackId: string(),
@@ -18299,6 +19106,24 @@ DeviceType.Camera, method(object({
18299
19106
  deviceId: number(),
18300
19107
  status: OsdStatusSchema
18301
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
+ });
18302
19127
  var PlateInfoSchema = object({
18303
19128
  plateId: string(),
18304
19129
  deviceId: number().int(),
@@ -18310,6 +19135,16 @@ var PlateInfoSchema = object({
18310
19135
  score: number(),
18311
19136
  /** True when the text was manually corrected (exempt from retention). */
18312
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(),
18313
19148
  base64: string().optional()
18314
19149
  });
18315
19150
  var MediaFileLiteSchema = object({
@@ -18349,6 +19184,48 @@ method(object({
18349
19184
  }), method(object({ plateId: string() }), _void(), {
18350
19185
  kind: "mutation",
18351
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"
18352
19229
  });
18353
19230
  var ModelFormatSchema = _enum(MODEL_FORMATS);
18354
19231
  var HwAccelBackendInputSchema = _enum([
@@ -18427,6 +19304,10 @@ var GpuInfoSchema = object({
18427
19304
  memoryMB: number().optional()
18428
19305
  });
18429
19306
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
19307
+ var CoralInfoSchema = object({
19308
+ type: literal("coral-edgetpu"),
19309
+ bus: string().optional()
19310
+ });
18430
19311
  var HardwareInfoSchema = object({
18431
19312
  platform: HardwarePlatformSchema,
18432
19313
  arch: HardwareArchSchema,
@@ -18435,7 +19316,8 @@ var HardwareInfoSchema = object({
18435
19316
  totalRAM_MB: number(),
18436
19317
  availableRAM_MB: number(),
18437
19318
  gpu: GpuInfoSchema.nullable(),
18438
- npu: NpuInfoSchema.nullable()
19319
+ npu: NpuInfoSchema.nullable(),
19320
+ coral: CoralInfoSchema.nullable().optional()
18439
19321
  });
18440
19322
  var PlatformScoreSchema = object({
18441
19323
  runtime: _enum(["node", "python"]),
@@ -20894,6 +21776,168 @@ Object.freeze({
20894
21776
  addonId: null,
20895
21777
  access: "create"
20896
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
+ },
20897
21941
  "localNetwork.getAllowedAddresses": {
20898
21942
  capName: "local-network",
20899
21943
  capScope: "system",
@@ -21524,6 +22568,12 @@ Object.freeze({
21524
22568
  addonId: null,
21525
22569
  access: "delete"
21526
22570
  },
22571
+ "pipelineAnalytics.deleteTracks": {
22572
+ capName: "pipeline-analytics",
22573
+ capScope: "device",
22574
+ addonId: null,
22575
+ access: "delete"
22576
+ },
21527
22577
  "pipelineAnalytics.getActiveTracks": {
21528
22578
  capName: "pipeline-analytics",
21529
22579
  capScope: "device",
@@ -21590,12 +22640,24 @@ Object.freeze({
21590
22640
  addonId: null,
21591
22641
  access: "create"
21592
22642
  },
22643
+ "pipelineAnalytics.pruneTracksBefore": {
22644
+ capName: "pipeline-analytics",
22645
+ capScope: "device",
22646
+ addonId: null,
22647
+ access: "create"
22648
+ },
21593
22649
  "pipelineAnalytics.searchObjectEvents": {
21594
22650
  capName: "pipeline-analytics",
21595
22651
  capScope: "device",
21596
22652
  addonId: null,
21597
22653
  access: "view"
21598
22654
  },
22655
+ "pipelineAnalytics.wipeAllAnalytics": {
22656
+ capName: "pipeline-analytics",
22657
+ capScope: "device",
22658
+ addonId: null,
22659
+ access: "delete"
22660
+ },
21599
22661
  "pipelineExecutor.cacheFrameInPool": {
21600
22662
  capName: "pipeline-executor",
21601
22663
  capScope: "system",
@@ -22094,18 +23156,48 @@ Object.freeze({
22094
23156
  addonId: null,
22095
23157
  access: "create"
22096
23158
  },
23159
+ "pipelineRunner.runDetailSubtree": {
23160
+ capName: "pipeline-runner",
23161
+ capScope: "system",
23162
+ addonId: null,
23163
+ access: "create"
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
+ },
22097
23177
  "plateGallery.correctPlateText": {
22098
23178
  capName: "plate-gallery",
22099
23179
  capScope: "system",
22100
23180
  addonId: null,
22101
23181
  access: "create"
22102
23182
  },
23183
+ "plateGallery.createVehicle": {
23184
+ capName: "plate-gallery",
23185
+ capScope: "system",
23186
+ addonId: null,
23187
+ access: "create"
23188
+ },
22103
23189
  "plateGallery.deletePlate": {
22104
23190
  capName: "plate-gallery",
22105
23191
  capScope: "system",
22106
23192
  addonId: null,
22107
23193
  access: "delete"
22108
23194
  },
23195
+ "plateGallery.deleteVehicle": {
23196
+ capName: "plate-gallery",
23197
+ capScope: "system",
23198
+ addonId: null,
23199
+ access: "delete"
23200
+ },
22109
23201
  "plateGallery.getPlateByTrack": {
22110
23202
  capName: "plate-gallery",
22111
23203
  capScope: "system",
@@ -22124,6 +23216,30 @@ Object.freeze({
22124
23216
  addonId: null,
22125
23217
  access: "view"
22126
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
+ },
22127
23243
  "plateGallery.searchPlates": {
22128
23244
  capName: "plate-gallery",
22129
23245
  capScope: "system",
@@ -22136,6 +23252,18 @@ Object.freeze({
22136
23252
  addonId: null,
22137
23253
  access: "view"
22138
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
+ },
22139
23267
  "platformProbe.getCapabilities": {
22140
23268
  capName: "platform-probe",
22141
23269
  capScope: "system",
@@ -22328,6 +23456,48 @@ Object.freeze({
22328
23456
  addonId: null,
22329
23457
  access: "create"
22330
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
+ },
22331
23501
  "scriptRunner.run": {
22332
23502
  capName: "script-runner",
22333
23503
  capScope: "device",
@@ -23412,6 +24582,7 @@ Object.freeze({
23412
24582
  Object.freeze({
23413
24583
  "broker": "broker",
23414
24584
  "device-export": "device-export",
24585
+ "llm": "ai",
23415
24586
  "mesh-network": "mesh",
23416
24587
  "mqtt-broker": "broker",
23417
24588
  "network-access": "ingress",
@@ -23443,6 +24614,143 @@ object({
23443
24614
  bootAttempts: number(),
23444
24615
  schemaVersion: literal(1)
23445
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("iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAgAElEQVR4nO2dZ3RU15qmZeZPT3fPv+m1ZtbMz+t7+zpiAwZMkIhC5BxMtn0dAAlEzgIRJYHIGRMEylIpknMGkSQRlHMq5awqVXpmfftUCYExCBvb9HTXWo9OPmfv99357FI5ObXxc+kSapnWgNONItrFptFOtqNytP0m+KcnNfwloZTBN4rwuJyP74VcAs5mc+J0FjdOZpAUn052bBr66FRqdU+xRD6B8CcQ9hhCH0FIMgQnQ1ASBCbCsYcQ8ACO3IdD9+Cnu3AgAfbfgb23Yc8t2HUTdtyAbddhy1XYfAX8LoPvJdh4Adafh7XnwPssrDoNK0/B8pOwNB4Wx8HCWJgfg2VeNLWeUeg9dWR76kjy1HFjjo4Tc6II8IzC1zMKD89ohsyL4S9LTvNPEmePKC3uM8No5xFJu/mx2raXXau38gGcwp5oN7xZTLvTWbRzD9O2sxr5XwmlTLuST+DpLNKjU7CEP4WQFDj+FA48gJ23wf8a+FyCdedg9SlYEQ9LY2BxtMaiqGcsFHSwwM78SI15QgTMFcLB086cMJgthIJHKLiHaMwKhpl2ZgTBj0Ig/BAI3x/X+O4Y/MPOd7Itx4LhxxCYEQazIsEjGubEwZwYcI+EWWFY3MNJ94gkcLaOaXNj+V+ixZSDOM0K14yQ7TF2jX7T5x7aTZ7U4HQlj3brz2jbyVV0ulbI0VOZVEVlwOFkLbWJeLOCsX0XgGX6YcxTf8Iy5aDCOvmAwjZJ2A9vnQPPmPzrsdlxhNcRfhWXaYewfX1EM8k9WjNnVjjVHpEcnRNNJ9HmmwCc3CNoN8+eG77b9yuNuGQvWu6W8t6FXM3VJzX89WoBYSezsRx/DN6nYWYQ1mmHME86gHXifpj4gjC/QQzeYax2U6zfHrObocPiHkGYZwx/Fa3EBA8d7/2qIskh/h097x24rd3kdgnu5/OoDXoKy+KwfnsUi6Rmh+ASsCmtmPyfB5vkkq+PYpUc4aGj1iOCmSr1H+e92XYTPNtaJOXYi52EUi3VF5v5l+uFBJ0uAJ+LWL8PUMI/J/o7IALvAmLEd4FYPePAI4KgBfH8i2g4W6dp+dqc4Ej5CXrtgtRq/uf1Iq5FZ6vK0jztJ638/i/heRVST5il4p6t4+rsSP6nMiHyNSY4Ktx7pVqWeVrJv10vJiEsDTzDMLe4/OdHkP8ISL3oEQOzo0jwCOff7DnhvZdWzNLUlM/DCu2ErGr+9UYJV0X82aH/Jf7k32JCtDLhqkcY/0O0nROlaezk1MqEkxk45RtwOvBQO3hLT6AUO5Lyf2Oz7j890w5hnhOrTAgUbX8M5z3psLVUyo4e7uUCrYy6V4bHqTzVKXqusv1d2u77/z+mlWbTDmOZrZkwWzR2b91ZuwTSxFQp/1EVfz+fT/WGc6pGt03cp93A0b7/L/hVTNqP7RvpL+io9tDxd7sJ76m6ILkap9AMzYCbekKOPQZp40/Yq1381b43Q0xTD973ChzH9/9uEf4ZE9scfhuT9tnU8hk/j8Ob6jJpP5YZOnCPIkS09ojnvdkRODndLNGyQ3I1XeMysMp4zPi92kUThL1tw/Gg8Xtg7O5XM2bXzxndmp0v2W7FqDYycsczxu1uQxz2wti9MFrYA2PsjBXk2B4Yt1fTpzXP38P283vbdZzyE/wYjtVdR1fRfEY47ZzCUzUDrpdwWMZ0ZMxj3B7tgvFtRM6Va0buhMn7rXxz2M4hK98etrTwjyNWxfdHLYofjpr5McDEjAAT7sc1ZgeZmR1sVkuPINk2MSeoWeEZbFTMDWlmfphZsUAIN7NQYVIsitBYrDCzJNLMtINWZZ4kkBZeiIOI/cO+Eubve4zn/nTc9+Xw494ivtldzvTdlUzbU8vEXY1M2Glk/E4T43aaGbfTwtidVsY4TJcEs/sXnrEPyw8RMDOSI8oAewfNKbOJ/3Mii4rl8UpIm1w4ro04zpWUve+qgZRSAznVzWRXOTDaaVb7hbxqo6KgppnCumaK6popqX+Gvt6EXi2fp7zRpNFkosJgVlQaTK+l1mTkcmYj4/fYlAmSG1rHYcIeGyN3w+z9GTy+vJKyR14U3FtJ3t2VZCd4k3rTm5Trq3l6bTXJl715cNmHG+f9uXxqE5dObeF0/A7i4vahiz5MWPhBZuwvUYlx3G7bi3rZphyG78Mon6Xj/7Y0QxPKmBaaCt8GYJXiQAnaRsSAYdtg2wUjzbZmwAoYgeY2YAQMb0iTncY3wExmVSNT9lsYsV0r8hzFoZgxRpa7jJyN3YStZA2WB99iTpiG+d50LPe/wfrwHxrJP2J9NAPb45mQ4gEp7pA6B9I81Xbzwxlkn5nO7P1ZDN0BY3fZnhW99kQ6fh/W7yNgRiRTWwy4XMjxvfe1LOII3Jg24Dhv9E4b17NEFCsWWyU2SrChf0NK3irYl81UU2NpYseFJgZuglE7tKJC6haV2HbbGLoTth2Joj7bG1Pij9RE9aIuri+1L6WfRnx/O67UnnCl8tRwSs5NYt+xQAZtgVHbbc89p5Vmlm9DVS4IcvSC/3t8Jqlep9XJ1pdWir+A3HDEDukhW0ktk9RpVmLyjmCiEgNGTj9tYOhmG0P9YcQ2rVIWccbssjFsJ8zanUpmwmpsuSuUqPUnXRV1J/pT35qTL8OVmpNulF+YyM2YhXy1rYLB/jCy1XNe0M065Sh8G0zK7Fj+2elxHX8Je4LZPVS7oC3CtxiwC4ZuhZnHzZQ3aUWKlvocKbq4TVjfEs9Svx4LpRhoIrWiga8PmHD1geFbYbgIs91eYYo4O4ycjvXHVrKe+oujqIvvR90JV2rj+72G/sogoeLMGPLOTsP7wDn6+cLwLTb1LMdzWpsg6+P3wdfHMc+I4n2n22UMPpYMXx/BJuVjW5t4jpsO8odVMUaaLGagoaX4sVLyFilWWBRFL+GZASK+FT0G6ig3NbE2ppHe61Cpf+gWzQAVzx02Bm2HLYd1NGSvpfnBj9TE9qHupIjfn5q4flTHvoS4fuqYw4DKk4MpvTCZuLD1DPYzMWiT/TlbteeoZnDrprHkvN1Yvw6G78Jwc7pShPuhJPhqP5bhcsEL7edXITccsAn2XjFgw4KVml9Z/utfixihGVCMmSJMtiLMQosBz55roIp6jITfr6f/BiuD/GBIKwNGbrcxeDvM2CVFjze2nJVUx2lleU18fyWyiF0V25eqmFY4DJCy/4Qr1SdcKTs7gZST3zFrRxJ9fWCIFHVbtIaJGDDiBc1ke9QurNOD4dtQ3J3O5uKz76FyxSwGyAltRW44cDNEJUoFbMFkq8Bik+z/drG9YIKZYmWAMkGZIrlEO6fZVkYDBh4U1TNuezP9NsDgzXYD7EXQMInnViOnYvyxFW+g7sJolaprRNS4/kroSiGmLxUxfdVStjUD+lNjr3jLTo2g6PwUDgYcoM9GGORnU89yGKD0fFFTbdsyLRi+DsLP6UQmR7fcUinf4riorQxVFY2N27laC8hAA0aV9uoxvAWMLVSpMl1EFrFbDFBmlLQYYLKV0kAD+Q2NzA9qxGUNDN4Eg8SALZoBI7bZGLAVNh3SUZe1FsODmSp115wYQHV8f6ri+iuxRfiKaDsxmiFyrDreVRlVGT8Q/blJ3I72ZMLmYvqKAZtsqgKW5wyzG/0L2pmnBsG04wQ4xaQRv/6y2qkMaCtyIyn/ped7K6+eR6VNPCwRGt8aD4obSdI3YrBaMCsT9JhV66aYZltxKwO0/Q22GqqsBvZcqsNljZWBfqimp4giBgzbasNtK/ywI4W0W6ux5qyiMs5NiV8V70pli/j9KI/uqxGjbVfGijmuygBBf3osOWems3ZfPM7rYaCfVZX/DgOGvlo/y6RjMOUYMU66FG54nVUXWB0XtpUh9opm4l4r43dZVLd8zHYzY7aZGL2t+TcxZlszwzcbCb9Xo2oXEzVKbBFaxNcM0LbFgEZrBbUYOZNSh5uPiX4bbLi9YICEd+gWIyei/bEWbaTmwlglelX8ACrjXKmI7a8EL4/uR1l0X8qi+2kGxPa3n6eJX3ZiCEXnpxIfuhq3jU3034h6luS0wY7nvFpLy4QjMDmAi07hT0laekJdZJUL3xTJBVIRu/pBf1/o5wP9Nmr03fDr6O8D3VfDsogmaozN2DDTbCvVxKcEozKgBJNK+aU0WcuotTXxpLyeqXubcFljw83X9kwUVQHb6OcPvgd11Geto+mBuxK36oRbK/H7K9FL7WgG9FfHKuJclVEV8W4UnZnIE6l4t9+j5zpw87U+b4DdhFdgHXcIJh3lllPII7Lnx6gLbXLxr0WMkABIpSyp7tfgZi+vpTydfsBEZqUMI9gw2CqV2M3oMdpKMFpLaLZJUVSK0VZKjbWeYkMTXrp6uq+yMsDHxgBf7X4SniFbbPT3h++2PyX9zhos2aspi3WjMt6NirgBlMe6UhbTn9JooR/6KLsBYkhMf3VczquMH0DxiZHkn5vGwaN7cVkHrhstLc8SDdqol3XMAZh4hGSnwGT0cyLVxS0GDHoTRHh/GChs/u0M8JMUa+N8Wr0S32itw2gX32AtUYgJsi3UWqqptBo5dqsWZ28z/TZYVadL7uMQRcI2eLPBXvT4UnlunCZqvBvlsQMoi3FV4uuFKIcB/SmNcaUs1pVyJb4bpXGDKDg7lVvRcxjnV6AZ4GN9ZrZdizboZhslQ9SHSXcKSKR2Zii4bX4m5pughH9LBgzyRzXn9l1txKyatUaabJLqSzFY9TRZSmgSE2QfpdSZy6myGrmeU8cQPwMuayz032jD1ddugCp+bPTZBBsPRlGXuZ6Ge+5K6Ir4gZTFDlAi66NdKYnuT0mUHWWGq92AAZTHuVEW50bByfFknvmaNfti6LYG+m8wq+LSYbaKR9s0s42QgblD6J2OPMDyfaAW2LeVit8UN7v4vTbAgjAj5U1GGdajwVquxG+y6mm0lChkXfY1WEqptDSSXt3AD4ca6OYl4ltV3aMMUILY6LsJvt6aQspNb8xZayiJHUh5/EBKY93Qxwywi+9KcVT/FmRbL7kiZoA6TwwoihtO3tnpxIZ447qunl7r5Fk2Ve85DFAatpFhO2D8T5icfroHMr9xgP0GfwYD/VG9yPF7zCSVNKiip8FSpcQWGswlikaLbJfSKOKbailpNuB3spauK0z0XW+h7wabSpHSIFANAzHBz0CsbjPmQj/Kz01AL6k+biD6GDdKogdQHO1KUZQrRbr+FCkDXNW+EjEnVlL+QEpiB5F3ajLJ8d/zw5Z7fOkN/dabn5ltf96bxHnIdhj3EzgduAvfBDwL9J+Bq2pB2YhOlrEkK02WBhoskupLlfD1pmcGKPGbKym3GIlKrMXF24jLGjN91mupX1Kk3M9tkw0XX1i/X0dtxgbq73sqoaUcL4lxozjajaLoAWpfoc6OGBElpgxQ5+hjByry4seQfeZr9h/eQ/fVNvqsMymzpcXneN6bxnnwNhh7EJz2JcDXAdDfkWr8/ljcNoHzOvA700iTxaRe6tSatSKm3qynzlSikHXZV91cRpnJwN3COkZvbaSbl4hvoU8rQQb42ejlC9P8n5Jyay2mrHUUxgxGHzeI4tiBFMW4URjtRmHUAAp0ri3ItlAU7UZxjIg/iILYoUr8K7q5jNxYQHdv6LPOrJrLrQ1oM3aNxQBpCTntuQPTj6KGUVVK/AMZYBf/xwAjhXUyoGelxlRhF15PbXOJhklPvbmUmuZSSo0NZNc1Mi+wli+WmZQYvddZnxNExcXHQIzOH0vhZvRnv6IoZiDFsYPUUsQviHIjXzeA/MgB2lI3QO2TYy3nxg4i+8QkUk59y/JdJ+jsBb3XNtN7vVU1lR3P+zVxH7QVRosBu27B1CNaGaxu9gfgMLv3RhkesHArVyv3a0w1SmyhxlhCtbGYGrsBNc169IYaiowGdl+soctyKXpM9Fprofd6W4sgrr42em6Edft11GX6UJ3gSb7OjeLYwRRGD6IgeiD5UW7k6dzIixxAbuQAtZRzZL8cL4zRxM+JHUXmmW8ID1yHy+pGenqbf/a8fr8y/gPlrdl+cNpxE+RFsRggB/5IXNbDsTv1WLFSb26iqlmELqXaWEKVoZgqMUHt01PaVKEq3dNPa+i7tpHuXiK+WbVG+kgPWsp/HxvOG2HKZmn1rMWYsZ686CEUxQ6hIGYw+dGDyIsaSK7OjdxIN3IiByhkPU8nxgykIHqQKq7yYoaSeXI6d2NnMM0vkS5e0GuNkV7rtOKuxYBf0s3nFcd9wG0LjNwHTttuwORDWvtbTPgjkFTQzRtWRjdS09yM0SqzHKR811NpKKGiqZhKwaAZUNZUSpGhkaTSOibtquOLZc0q9bvYU6PDAIlDvw0GdBH+mAq2UHhmkhK9IGYIeVGDyI0aRI5uIDmRbmRH2FFGiCkD1Tn50YPJjxlMRtwEUk59h9++Y3yxApxXN+O8xkKvddB7g/asVyHhUfH9BV0HyOtRMWDLdZj0k3ZTRyR+T8T9Hmtgyv5mMiobsWKjzFBBpUFPhUFPeWOxoqKpRO0rb9JT2FBLdn0jKyNq6LDEiLO3iGHGZZ2V3usdYbfRfT2s2RtJdboPlQnzyY4cSH7MUHKjBpOjG0S2bhBZkQPJihhIZoSbQtblPDku50mOyYkeSsbJ6VwM92Tw6kw6L2uip1cdzqsbcfE2aDlhrVGrD15Cn/XNOK+3qVFSSRwv08F1MwzfC06br8JXB7VOkBzo/Tsi95dix9XXwrnUOlXuVxhrKWsqUUKXNRZT2lCsluVqXwkF9ZUUGAwE3Kyi6/Imuq9qpqe3yZ4abfRSkbTRYz1M8kvh8Y01NGVsJCtquCpGcqKHkK0bTFbkIDKFiIFkCOEasi375bicl2s3ID1+MsnnFhMbuQldxDaiIrcRrdtOtG4H0VE7iHkpO4mO2kmsbgsHjv3EEJ9a1cjovcGmEolCtNigGTBsDzj5XoYJB1ARaTnpd0JSgxQ9ey7X0WwzU2syUNJQQlmjHn1DMfr6IvT1YoLsK6GwrpT8xiYuZ9bgtqGezsuM9FwtBphxWWtVBkgOEFN7rzMQGbGF5oJt5J2eSnaUCDmMLN0QMiMHkxEhDCI9XBhIWvhAtZRt2S/nyLnZUWLaUDKiRlBw8UfqEpdQn7T0GclLaXgJjv11DxdTemcRT05+zTT/FLp6o1pNom+vVlr032Q3YMMlGLdfi0Sv3xERv+sq8AxupLzJgMFqpqi+VIldUl9McV2RQtb1DSUU1ZWQW1fP44o6vjtQw+eLDPRcbaTHahM911hwWWtTg2ESua5rYdVuKXr8KLuzgPSIwWRHDydTN5SMyCFqW0gLH6RIDRMGqqW2Tzsu58o1WVHDyI4eRoZuKGmRw0nTaaTrRrySlMgRPIqZTnL8t2w5eITuqy32sFpxsedWB/38YOhucFp3Acbu09rjcsDld0BSqaR8mQKSWFyPBRuF9RUU15dQXFdCYW0RhTVFFNWKEZr42TXVZNU14BNfyeeLmujuZaT7KhM9vM04r7HivNamWkBfroWvfJ7w5OZ6GtJ9SYscQVb0cDJ0w0iPHEpaxBBSw4XBpIQJgxRP7UtBjglyrlwjyPWZUcPbhDwvPWoEj+O+5vGpH9h9aDfdvAx86WVWxWVPe3glwShN1kFfmSiwC5y8z8GYveC8VjvwNhFTxfme6t4WohJrVKWrb6yloLaYotoSCmqKyK8uVMvC2mJFVlWZqnTD71fSbUU9XZYb6CYGrJYIWegpKWqt/b5rDESEb8GYt52sU9NIE+GjRpAWOYzUiKGkhA/haZgwmCdC6M+RY3KOnJsSPlRdlxo5TN3jleiGkxEluUNS/tc8OvkDBw/7031FPZ2Xm+i+SnKsucUA53W2Fl36+sJgMWDVWRi1B3raT1AnvkXEgM4rbWw8WauGGqqajeRUF1NQU0xedRG5VYUKWZd92ZUlZNY2cCOvmhGbpOhpUqmpm5epJTISVmmCdpam7K5IqtI2UXJrEU/DRfyRpEYOJyViGE/Dh/IkbCiPw4bwONTBYMUjO9q2duxJmDBUXSf3eikRw9S95RkifIpuBEnR00g++QNHj/jQc0WVaiZ3W9lEt1XPh7m1Ln18YNBOcFpxGjUzuMcaSVFvF2kmdl4J/zjUSH5tI01WC1lVevKqi8mtKiK7slCRU1mk9mVXFpNWWcPjilo8j1Xy6fxGFZEvVzbTfZWZHt4WeqjUZKGLN4zb8ITka2upS9vE44jRpOpGqnL4ScRwnoQP43HYMB6FDlUkP8cQkkNeIHRIy7mPwjTk+tbIPeXeTyNHqGc9jRxJohL/RwID1tJrZSkdlzTTbUXjz8Lcs7W+a2z0FgN2gNPSkzB8F/Tw1g70eEs4r7XSdZWNgX5GbmTXqlmj2TUVZFeK8EVkVRSSWV6glo59KeXlpNXUs/N8OZ8vbKDL8ia6rjCq1C/Fj0RGKrXu3jZcvBsJD9tMU+5O0k5+y5OIETyNHMXjiBE8Ch9OcthwkkOHKZLsS8e6ImRoC4mt1ltofZ0YGTacx+Firib8k8hRPIyaStKJGYQd96LPymI+X2ziyxWNdF3ZrFK/VMIOAzSe6dNrIwwUAxafQE1Q7e5tP8n7LWC/z5erzBy5WU0zFgrr60gvLySrooiM8gLSywrIKCsgs7yQzIoiUkpLSK2uJ/5xBb1W1dJxcSNdVxhUSuq2yqwi010ZYKbTKvDaFUFl2mYKbiwhKWwET3SjeRQxkuSwEWo7KXT4S0l0ECIM46EQ/AIhw9SxRMd1YSNIDh/Jo/CRPFYmj+KBbioPT8xEF7iMAV65fLbIxJfLG1SC+dLL1CrML9dIDHDbDk4L4lBz2buttqqT3waSSjsus7IssobqZiMVxmZSSkX4ItJKC0jV5yvSSguUKSn6Ip6U13C7oIqJ2yv5dF6j6nR1aRWZbtKk8zbxxSoYt/4Rj66vozrFn4dhY3gUOYbk8NEkhY0iMWwkiaEjteWLyP7QkTwMHcHDkBE8UAznQfBw7gcPU8i62hcyXJ3nuFdS+CiSI0Yr7kdO4cGJmcQGL2Lwqgw+XWim6zJpLBhbEoyE12HAy3DZYDdgrsyI2A7dVlm1C1Zb1MVviuM6yXKdlluZsKuB1LI6GqxWnpTqSSsVoQt4WpKvkPVUta+QZH0Fj8prWR5Wxsdz6+myrJEuywx0XdHcKjWZ+XKVlR6rGggL30JDzk4ex/+DRBE+YgwPw0a3kVE8CBVGcj9kJPeDR3BPMdy+HKH2yTE552HoKHVdUvgYEsPHcDdiCvfjZ3EiZAHDVz/lkwVmui6VTuLz4e226tU6iQGqEp4dBYO2wZeOC1b9euTmXVZa1DjJ6adVyIye1IpKnpSI8AU8Ls7nUVGeWsq+JyUFJBbpVWfr0PUyOi6spdPiBjX2Iqmp60qTaktLUdZ9tYkOK2DlznAqUv3JvraUeyGjSIoYx8PwsTwMH9MmHoRp3A8dra6/GyyMJCFIQ9ZlnxyTc+Rc7f5jSQifwr04d86EzWW0dyIfzzfTZWmdEl9ya+vw/qJOdo3FANUMlf8KJUOjX3pZlAm/HnmomU7LzWw7X0mTzUxObT1JRfk8Li4guSifpMI8kgvzeGTfl1hYSFJpLWdSynFdW8Wn8xvovLTRHqHmlghJhdZxJYxe94Tk6z6UPvLnVshE7oZPJCGs7dwNG8/9sHHcDxvLvdCx3A0ZQ0LwaO4EjWohIVgYrY7dCx2jzhXuhE8mIdadc+Gz+WrtHT6ab6bzkjq+WGpPLK3CK1q8Ti8xYIj0hH8M14ZGu8qFvwFpcn2+xIL7sWqKGxrRG5p5UCDCF5BYkM/D/FxFYkEeSYX5PCzI535RJXcKK/l2Xxkfzqmn85IGFaHOy42tDDDR1ctCD69GwkM2oE9cTdIpD+7H/cD9+B+5Hyf8oLj3CziO3Y2dyZ2IadwNG8fd0HEkhIzlTvAYbgeN5lbQaLW8HTyGOyFj1TE5517YOG6FTeZ2rAcXIjyYsv4aH80z8cXiWr5Y8nxi6brS3CYd5RwxQI0FfReqjcx1sV/cwspfwOvn69JM7LjUwtDN9dwvqKHOauV+YYkS+WF+Pvfzcrmfm6uWsv0gP4+E/FIe6qtZH6Pn4zm1dFpUTydHhJY302WFREhDirWeqxrw2R/I7p+2sePgLnYe3MmOAzta2P6LbNeW+7ZyKtKL1CvLuR06kYTQ8dwOGcet4LHcDBrDzcAx3Awaq7Zl/53Q8eqcm6FTuBXjwaXIWXy74TwfzDXxxaJaFdYvlhqeSyw/0+glWjmWYsBweR/wTTD02wRdVsgB0xvRxb7svEKcNRB6t4JGm5Xk0kru5uZxPy+fu7m5JOTkcDcnl3u5edzLy+N2TiEPSmoIuqOny+JqPptfr8r+L5Y0qRFPhwFdXnhGh6VWOi418fniZj5baKD9gibaL2ik/fzX0cBHno3sO7KLskd+3Aybxq2QCdwMnsCNoHFcDxzLjcBxav1m8Hh17HboBG6ETOZGtAdXdLOY4XuSD+Y208leT6mc+gthfa1uK0y4bLAxUl5JTjsuA0M2OstNfgVyw88Wm1gbW0GNuZmMmgZuZecpA+5k53I7K0ch6wk5edzKzudOQRXn08sY5lPOh3Pq6LRYDHiWojo7IvUczaqslRwikZcUKNeIGK/ii8X1tF/YzIDlT7l/eSN5dzdwNXACN0Mmcj1oAtcCx2sEjVfbN4K/4mbIV1wLmcy1KA+uRs1iziYdH3gaVSOh46IGOi1pek1YX48YMEpeyk8OgD6+NjV4JAc6t5XlIn4z7ReZmLaviuxqmSBr4kZWPney87iVlcvNzGxuZGZzMzOH29m53MzK5UZOGbcLKph9pIR/d5cI1T2L1DItQhrPP6tl/zKjirzQSVjS9Eo6L23g44VWVmwPouzpFu6d8FQGXA+exNXAr7hyfAJXAidwNegrrgVP5HrwRK4ET+GKzoNrUTNZ5GzEYRMAABARSURBVB/CB54GOiyQsNarDqJ6/i+F9TUojZebcNloY4zMC/rqCPTaaFMDSM9u+HokRXZYYqL/+jquZFRQbbVyM7eEG5m5imvp2YrrGWJCjuJqZhF3iqrYerqYj+dU8/n8Wjos1CLVyRGpV4VjWbM6p4Wlr8OgwthjaQlnT/hRnLyVS0FTuBo8mStBk7h0/CvF5cCJavtq8CQuB03hUoQH16JnsXLrET70bOQzFU5JKI321K89/030as0Xy5uVATJF3Un+SHYQA9qKusmyZjouMXDwaim1Vgv3iqu4kpbDtYxcrqRlczk1iytpWVxNz+Faeg6X0/K4mV9JxINieiwr56M5WqQ6LGqgo6RWuwFtC4OxTXRZ1sQni+EH3zPkJ/mTeH45F45NVCJfPD6JC8cnquWlwMnavsApXAj34Hr0LNbs2M/HnnW0n1dLhwX2cC62h9NuwJto9iKi+fjD4DT6IOoFcqeWm74eKfs+WdDMwuAy9E1NpFQ1ciElhyvpOVxKzeZiSpZC1i+n5XAxNYcrWWVczNAz3l/Pv8+qUVn68wX19lQlxYk91S57e3Ra2qzql8CQbeif7uRiyHdcDJrKhcApnD82WXHhuAg/lfNCmKR8d3x37+ATzxo+mVurcqkMDKpcag/nm2j18nAZlQHyLRknmRrRY5215cavQ8pgEX/M1koe6WsoMJg5n5rPpdQcLqRkc/5JJueeZHL+aZbavvA0m/NpxVzPK2dJcBF/m1nNZ/Nq+Gx+HR1UxJpUTmrr89sczqVNfLoYxnonkHpnE0+ub+BMwBTOB07n7LGpnD02hbPHRfhpnD0+jbOh7lyN9mDrXn8+n1vOx3Pq+Hx+jUokHRY1aqn/LYbTZYMVKf6dhu6B7musdFxipNNrkDL180VGenjVEZ9cSrnFysWMEs49yebc02zOPM7kzKNMtTz7JItzT7I48ySfa7kV7L1USHvPCj6ZI+JL6m9oiZgY0JbnvxkGNUi25eBB9Cm7VLl+5tg0zhyfzumAqZy2r585Np3TwbO4Ej2bPQc20HFuCR/OruOzeVrzWFJ/h0VNdFxseDvhXGpU9xADJh4Fp0G75H2ttdUDXo20vf1PlVBuNnGroIqTyVmceZzNqeRMTiZlcDI5g1OPMjn9KItTj3K4mFlGdFIR/Vbp+fusZ6n/WcSa2vTcN0Eqyk8XWXFdnsrti76kJWzhxNHpnD7+DScDpitOHftacTJoFpeiPDl0aBVd5+fxd496PptbRft5dS2JpIOEsY36tInFBmXApABwctsBX6620EEe8AvIMYnUR3ON/HioVJutUNlEXFI2px5lcyIpk/jEDOIepqulmHIiKYvTKSWczyhh+s4i/vJDNZ96VvPp3Fraz6/nswWNfL6wSb1ylBkPQoc3oOUae/hah1/6CB8tsLF8exCFj3dwJXYx8Ue/5sSxb7RlwDeK+MCZXIyax7Ejy+mxIJN/d2+gvV38zxZI6m9UiUQMeJU+b0oHuwGTj4GTq30ktMNrxP9kvpFBG6u4lVtBnsFC3KM84pOyiEvMJPZhBjEP0hWxDzPVvrjkPM6n61kdkcvfZ5bx6ZwK2s+tVFn78wWOFlCd1rb+FUjlKgN3koOeF6lJvRzptriIMyc2k524i9iA74g/9g9ij35LXIBG7PGZnNfNJ+zYIvoufsJfZzWq8H36O4vf2oCpx8Gp3xYsXVdZX/oQtW+JQXX7Oy+tJ/hOCXqzlTNpemIeiPBZRN/PIOpeOrp7aWoZ/UDMyCQ2MZcLWVVEPCwj5G4ZYfeFcsIfVBAhPPwtVBL2oJwd54vptrxOFYuaUE2qd/zRAvjRJ56cxG1cP+1N1OFvlQkxRzWij83kbOQCIgMXMGjZ/efFn//7iu/QtbePjelBmJz6+lP75Wpe+SCJ4JqoEooMBq7n1xB5L5PoB1no7mUQeTediIQ0whNSibibRuS9DKLuZ6K7n8mZ1FIeVDSRVNVIUqUsm0h+CyRWNHKvtJZ9l4v4ZK6jPrGLtkiGDKoJDt1G3qN9xBx3J/roD0Qd/V4jYCanIhYSG+zJ6JXXeX9WE+09K1XR+EeIbzfA1m8zfBNMqVPvzejlO0+y88UTpSUhRc/wTRUk6atJrjISfjcL3f0sIu5mEJ6QTtidNEJvpypkXfbJMQdhCZktyLVvg7C72ZxN07Myooi/e0hlqQknQxqS+iesuU5KwhbuXPQj/NB3RB2dge7Ij0QencmJsEWcDJ3DZO9zLxG/4XcX32HAgG3wj1DSnXpvIlu+cvlyA5r4cK6RmYdLyWowcDKllNA7InwmoXfSCbmVRvCtVIJupihkXfaF3E5Xx8MSMn5G+G8iUy0j7udyLr2E7/cX87dZ0qR9lgs+XWBg5+G95D/ZT2zwAsIPzyDiyEzCj8wiLnQxp8Pn8P36GN6f2cSnLxNfGWD4vQ2wyoyIH8JJFgOSuq1DDlhflgM+8GxibbSejAYDuoeFhNzOIPh2BkE30wi8mUrgjReQfTfFlDSCbrUm/TcRLNifHfEgn5NPixjpW9JigIwpfbzAwsAVj7h7ZTMPb+wk+KcZhB9xJ+zwLKJDFnM2Yg6eviH8dVYDn86pov3cGtUik+bm544i7HcW32GAvA2bEcEtMeCGzKsXA158sGx/trCeA1f0PK01Enwnh6BbGQTeTOf4jbQ/nMBb6erZUYmFhN4roNeKUj7wkBSsGfDhfAurdh4n7/E+4iNWEnxoFiGHPdAFLeFspCfL/A/xN/d6PvlzxafDYqNl+H6YpeOiGHCix0ZlgOXFh0vrp8vSWmIflXK/vImAG5kE3szg2I30P5EMYpKLOHg1n8/mlvOJp4zX1PHpAiM9Fudx7pQ/j+/tJ/Cn2QQfnk144GLORs5j/Y6dfOhRxcezqzXxVXOzoVWl+/uL32LAEqNl9CGYHU2MGHDU2Q/pHj9vgHTlFxhxXVvJzfxqLubWcuhKGgE3Mjh6PZ0j1/4krmcQ97gY3/g8PnCXlCxFUC0fzLMy0zeOrMTdnI7dQMABd8KOL+F05EK27vWj/ZwyPpxdS/u51X+a+A4DOi41miccg7mxHJVWkI+LvzLA/HxvsomP5xsZtbmM6wWVnEitIjghT2X9kLv5iuBWy19DyJueq55fyJm0QmYeLOBvM7VxJXnt2GFBJSFh20hLPMSxw/MJCljMqcglHPjJm87zClRr6c8W32FAp2XNlslBsPAkfk59tuDusgkZuv1ZESRd/S7L6vE/XYAusZCw+0KBxj0h/w8jVCHPzmdtVC4d51eql/kyYvnhPAtfeV/m0Z3tXDy7jSM/LeBE5HKOHVmB88L0F4YYWov/+zY3X2qAaLrSZJ0eCsvO4u7UdyuDnX2h0zKTrXVgHN166YTJjAW3dRUMWFuO65pSXL0FPf1X/7G4euvVDOSPZlfx0Rwpx7Um5Mfzmth5eB8ZyYcIPu5FdNgKVfa7LU3kr9LW/wPGd9puQBPdvM227yNh1QUGOvXbzl96bsTcecXz40Etg1yLmvhsYSMfz23kozn1aqj2Aw+h9k/hw9naWyo1orqgTs1Oc1v+iNuXt3Lj6n5CA5cTE7qQcaukl2v4Q4cY2oKUKr02WuU/qJv9bvG+06CfrP/d2ZfUL7XO2HN9AYcBGo3qBYrqdf7JfKZo0Jqe8yys2BFE6sP9REdtIi50Lv9Yf5L33Q2096z6Q4cY3qQPMDeOlOAU2z+r/x/daxPHe/r+vCX0bKhXM0HayxKRd4EOixppv9BE98UFnIjbwq0bh4gNmctC/xD+5iG9XK2F9LIhhj/LAFUBL222TApU5b/2z7uVAZuZJk3RTktNL+2MvWjEu0AnNeppY6bfKe5e28bZuNWs27GHjz3r1fvc9vP+nI7Wa8v/1WbrjGhYe6XVv6/vs5X/4+xLRZdVLx8T+rkRfz6dlAFWPPzPcD5qHlv3baPTggplgLx10176/HnNzV/Q0Oa2DebFU77tlv0HHHr6aj+j0WszR7Ri6OfN0XeVzxfJsp7R3g/ouriMj+Y22fsF76D4iwx0XtFsmRYKK89zVP2SUiztnFw2aQb09qersw/WF1tD7yod7EWivNL8aL5FNZelVaQJ/26J7whvPz8rc2Kx+lzVfsTH6xztnPr44+SyRfsZq16bCe4pdcF/gFzQoVUrTd4DdHiugn7xNeWfyBItjF28TJZvwlXbP1j9nmQs7226hpOTsxdOLn52Azbxdxc/quVfCnRcbHiuY/aum9DhRd6hlN9xqdE2dBcsPEXNpmvaD7mtuWD/ITcxQD7Ovvw3uwnuMjbUeYXF0vE/igGLX867EsY+vlaL50lYdwl30Xjp6Rd+d95tOzIm5NRjY0tOCHTZDF8sMz83QPeu0uEdE711uHqus5hnxMLqi9qPeY4P5L2dt1v9mGfrT69NmgEuG/lXl01ccZZBuv8gJnR8F8VfazF/pwOv81zZdY1/FW03XrH/nK39J4RbPh3sPzLsqA9cfPk3Fz8SpIMmOcFx0z87Yh3fZaTCbZXyRfyV57i77Zr2g87rL//CDzo7Pl38W0zQ+gZ+yoRrUhx1XmExd1zy7lfMHf/ktr5UuH38rOaZsUr8qztuauKvvahp6n/rNb8r/6IJfbfxL84+BPeSb1J626yOJup/GWF4TnjRQ5qaQ3dj9TwFK88SfCSZf7G3eLTfk497jfiOj/Nhuwm+vNdtbUuR5O7sS530EzqvtFrtA3e2/6xmdHA0fxcbbNLD7bfZZpV2/sKT1HmfZ5ZoNvU47627qOnXZvFbTPB6Vic4+2hN1N7+vO/sQ7izLxYxossq+WKHyWGG9V1uDnb8tSK3iK0Jbp8QbJXXijKwJmM78nbLMw7L8tOEb77B+6LV0lO0W2MX/7XFzi99HBVzr004OfvQrvOKlu1Ozr4E9NxIlbSUZAxJ3id0XmGVN2vWTkublSnynllmW9hnXFjt2FrnnA7vDo5w2cNptMjsBXmBLmJ3WWmydPM2W+VlioznTwoCGdX0jKVq+WkCfK7SSbTptxWnJSdp53dD08rL/pPxv+GD6ieo3OBLux4baNfFnjv6+PO/XfyY6uxLSM+NpPXciEleb4ohkkMEmfIi845k8peYJP9FUEZdO6+0SsX+21mp/X+KLl4Wujr+FYD8PwZvK93WWNWXT3qs1ei5TsN5vYbMUBZkoqzM1ZTpgjJjbcgekHk7MnVkwnGQF+iSyuU14sxITJ4xpC05RcjqC0zddov/LVoM3Kv+70a71eft5b20819sav6Wj5dd9N4ydmQ34oMxOPWyV9r9ttv+ufdm3u+1iYEufni4+OInucTZhxhnHy46b+R2z40k99xIes+N6MWsnhtR/+f5t+DsA738oM9m6LcFZLq9fOdBvniihNyH+kK0fBdu7E+oL8bJV4MmBqDm58sUcZmlLBNlZa6mTBecEcFtmTQl83bmxhKwIB6/Jafw8DrHwI1XeD8gWXuTtfKyRcXdPZJ2Xmdpt++epkVLD7cNn/8HKEOoJXMszwYAAAAASUVORK5CYII=", "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
+ }
23446
24754
  //#endregion
23447
24755
  //#region src/adapters/discord.ts
23448
24756
  /** Discord message `content` hard limit. */
@@ -25883,11 +27191,19 @@ function createNotificationOutputProvider(deps) {
25883
27191
  renderedAs: prepared.renderedAs
25884
27192
  };
25885
27193
  }
27194
+ const iconUrlFor = deps.iconUrlFor;
25886
27195
  return {
25887
- listTargetKinds: async () => deps.adapters.map((a) => ({
25888
- ...a.descriptor,
25889
- addonId
25890
- })),
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
+ }),
25891
27207
  listTargets: async () => {
25892
27208
  return (await store.list()).map((target) => {
25893
27209
  const adapter = adapterByKind.get(target.kind);
@@ -25984,6 +27300,12 @@ function createApiSettingsStorePort(api) {
25984
27300
  collection,
25985
27301
  key: id
25986
27302
  });
27303
+ },
27304
+ declareCollection: async (collection, columns) => {
27305
+ await api.settingsStore.declareCollection.mutate({
27306
+ collection,
27307
+ columns: columns.map((c) => ({ ...c }))
27308
+ });
25987
27309
  }
25988
27310
  };
25989
27311
  }
@@ -26014,6 +27336,9 @@ function createMemorySettingsStorePort() {
26014
27336
  },
26015
27337
  remove: async (collection, id) => {
26016
27338
  bucket(collection).delete(id);
27339
+ },
27340
+ declareCollection: async (collection) => {
27341
+ bucket(collection);
26017
27342
  }
26018
27343
  };
26019
27344
  }
@@ -26034,6 +27359,13 @@ function createMemorySettingsStorePort() {
26034
27359
  */
26035
27360
  /** The default collection name (see `settings-store` addonId-prefix note). */
26036
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
+ }];
26037
27369
  function rowToTarget(row) {
26038
27370
  const candidate = {
26039
27371
  id: row.id,
@@ -26058,6 +27390,12 @@ var TargetStore = class {
26058
27390
  this.store = store;
26059
27391
  this.collection = collection;
26060
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
+ }
26061
27399
  async list() {
26062
27400
  const rows = await this.store.query(this.collection);
26063
27401
  const targets = [];
@@ -26129,15 +27467,22 @@ var NotifiersAddon = class extends BaseAddon {
26129
27467
  const api = this.ctx.api;
26130
27468
  const port = api ? createApiSettingsStorePort(api) : createMemorySettingsStorePort();
26131
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();
26132
27472
  const provider = createNotificationOutputProvider({
26133
- store: new TargetStore(port),
27473
+ store,
26134
27474
  adapters: ADAPTERS,
26135
- logger: this.ctx.logger.child("notification-output")
27475
+ logger: this.ctx.logger.child("notification-output"),
27476
+ iconUrlFor
26136
27477
  });
27478
+ const iconRouteProvider = createIconRouteProvider();
26137
27479
  this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: ADAPTERS.length } });
26138
27480
  return [{
26139
27481
  capability: notificationOutputCapability,
26140
27482
  provider
27483
+ }, {
27484
+ capability: addonRoutesCapability,
27485
+ provider: iconRouteProvider
26141
27486
  }];
26142
27487
  }
26143
27488
  async onShutdown() {