@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.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
1
2
  //#region ../../node_modules/zod/v4/core/core.js
2
3
  var _a$1;
3
4
  function $constructor(name, initializer, params) {
@@ -4286,6 +4287,14 @@ function object(shape, params) {
4286
4287
  ...normalizeParams(params)
4287
4288
  });
4288
4289
  }
4290
+ function looseObject(shape, params) {
4291
+ return new ZodObject({
4292
+ type: "object",
4293
+ shape,
4294
+ catchall: unknown(),
4295
+ ...normalizeParams(params)
4296
+ });
4297
+ }
4289
4298
  var ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
4290
4299
  $ZodUnion.init(inst, def);
4291
4300
  ZodType.init(inst, def);
@@ -4627,7 +4636,7 @@ function _instanceof(cls, params = {}) {
4627
4636
  return inst;
4628
4637
  }
4629
4638
  //#endregion
4630
- //#region ../types/dist/sleep-BC9Yqte7.mjs
4639
+ //#region ../types/dist/sleep-_sv7WKkq.mjs
4631
4640
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4632
4641
  EventCategory["SystemBoot"] = "system.boot";
4633
4642
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5037,10 +5046,51 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5037
5046
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
5038
5047
  EventCategory["EnrichmentSceneStateChanged"] = "enrichment.scene.state-changed";
5039
5048
  EventCategory["EnrichmentActivitySummary"] = "enrichment.activity.summary";
5049
+ /**
5050
+ * Unified track-lifecycle event: ONE category carrying `phase:
5051
+ * 'start' | 'update' | 'end'` with a rich, typed
5052
+ * `PipelineAnalyticsTrackLifecyclePayload`. Supersedes the thin
5053
+ * `PipelineAnalyticsTrackStarted` / `PipelineAnalyticsTrackEnded`
5054
+ * pair — a consumer subscribes once and branches on `phase`.
5055
+ */
5056
+ EventCategory["PipelineAnalyticsTrackLifecycle"] = "pipeline-analytics.track-lifecycle";
5057
+ /**
5058
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5059
+ * (`phase:'start'`). Kept as a soft alias — no known subscribers.
5060
+ */
5040
5061
  EventCategory["PipelineAnalyticsTrackStarted"] = "pipeline-analytics.track-started";
5062
+ /**
5063
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5064
+ * (`phase:'end'`). Kept as a soft alias — no known subscribers.
5065
+ */
5041
5066
  EventCategory["PipelineAnalyticsTrackEnded"] = "pipeline-analytics.track-ended";
5042
5067
  EventCategory["PipelineAnalyticsDetectionEvent"] = "pipeline-analytics.detection-event";
5043
5068
  EventCategory["PipelineAnalyticsFrameTracked"] = "pipeline-analytics.frame-tracked";
5069
+ /**
5070
+ * Fired by `addon-post-analysis` when a parked (stationary) object appears
5071
+ * (a track was promoted to a stationary-registry entry) or departs (the
5072
+ * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
5073
+ * the dedicated "Stationary" section — never the live event feed. Payload:
5074
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
5075
+ */
5076
+ EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
5077
+ /**
5078
+ * Fired by `addon-post-analysis` whenever a gallery face row changes:
5079
+ * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
5080
+ * `'unassigned'` its identity link changed, `'deleted'` the row was
5081
+ * removed. Telemetry semantics (D8): a lost event only delays a refresh,
5082
+ * never a correctness issue. Payload `PipelineAnalyticsFaceGalleryChangedPayload`.
5083
+ */
5084
+ EventCategory["PipelineAnalyticsFaceGalleryChanged"] = "pipeline-analytics.face-gallery-changed";
5085
+ /**
5086
+ * Fired by `addon-post-analysis` whenever a gallery plate row changes:
5087
+ * `kind:'buffered'` a new read was persisted (`PlateRecognizer.onTrackEnd`),
5088
+ * `'assigned'` / `'unassigned'` its vehicle link changed
5089
+ * (`PlateGalleryProvider`), `'deleted'` the row was removed. Telemetry
5090
+ * semantics (D8): a lost event only delays a refresh, never a correctness
5091
+ * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
5092
+ */
5093
+ EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
5044
5094
  EventCategory["FrigateLiveEvent"] = "frigate.live-event";
5045
5095
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
5046
5096
  /**
@@ -6869,7 +6919,8 @@ var MODEL_FORMATS = [
6869
6919
  "coreml",
6870
6920
  "openvino",
6871
6921
  "tflite",
6872
- "pt"
6922
+ "pt",
6923
+ "gguf"
6873
6924
  ];
6874
6925
  /**
6875
6926
  * Multi-file format payload.
@@ -6913,7 +6964,8 @@ var ModelFormatsSchema = object({
6913
6964
  coreml: ModelFormatEntrySchema.optional(),
6914
6965
  openvino: ModelFormatEntrySchema.optional(),
6915
6966
  tflite: ModelFormatEntrySchema.optional(),
6916
- pt: ModelFormatEntrySchema.optional()
6967
+ pt: ModelFormatEntrySchema.optional(),
6968
+ gguf: ModelFormatEntrySchema.optional()
6917
6969
  });
6918
6970
  /**
6919
6971
  * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
@@ -6954,6 +7006,15 @@ var ModelCatalogEntrySchema = object({
6954
7006
  width: number(),
6955
7007
  height: number()
6956
7008
  }),
7009
+ /**
7010
+ * Channel count of the model input tensor. Omit ⇒ 3 (RGB), the default for
7011
+ * every detector / classifier / embedder. Set to 1 for a grayscale CTC text
7012
+ * recognizer (EasyOCR VGG plate-OCR: input `[N,1,H,W]`) so the preprocess
7013
+ * feeds a single-channel, EasyOCR-normalized tensor instead of the default
7014
+ * 3-channel RGB one. Threaded through `PoolModelConfig.inputChannels` to the
7015
+ * Python inference pool.
7016
+ */
7017
+ inputChannels: number().int().positive().optional(),
6957
7018
  labels: array(LabelDefinitionSchema).readonly(),
6958
7019
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
6959
7020
  inputNormalization: _enum([
@@ -6963,6 +7024,16 @@ var ModelCatalogEntrySchema = object({
6963
7024
  ]).optional(),
6964
7025
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
6965
7026
  /**
7027
+ * Per-MODEL postprocessor override. Absent ⇒ the step's own
7028
+ * `StepDefinition.postprocessor` applies (the normal case — every model in a
7029
+ * step shares its decode). Set it when a step hosts models with DIFFERENT raw
7030
+ * output layouts under one slot: e.g. object-detection is `'yolo'` by default,
7031
+ * but a Coral SSD MobileNet build emits the `TFLite_Detection_PostProcess`
7032
+ * 4-tensor layout and needs `'ssd'`. Threaded into `PoolModelConfig.postprocessor`
7033
+ * by the engine factory (`modelEntry.postprocessor ?? def.postprocessor`).
7034
+ */
7035
+ postprocessor: custom().optional(),
7036
+ /**
6966
7037
  * When true, the executor produces a landmark-aligned crop (similarity warp
6967
7038
  * onto the canonical template) before this step runs, instead of a plain
6968
7039
  * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
@@ -7052,6 +7123,123 @@ var ConvertResultSchema = object({
7052
7123
  })).readonly()
7053
7124
  });
7054
7125
  /**
7126
+ * Build an `IAddonRouteProvider` from a list of routes. Implements
7127
+ * both the operator-facing `getRoutes` (returning route descriptors
7128
+ * minus the handlers, which can't cross JSON) and the framework-
7129
+ * private `invoke` method that the hub calls when this provider lives
7130
+ * in a forked worker.
7131
+ *
7132
+ * Co-located addons use the returned `getRoutes` directly because
7133
+ * their handlers don't need to cross any wire. The `invoke` method
7134
+ * is present anyway so the bridge code on the hub is uniform — it
7135
+ * doesn't need to switch on "local vs remote provider" at the call
7136
+ * site.
7137
+ *
7138
+ * Example:
7139
+ * const routes: IAddonHttpRoute[] = [
7140
+ * { method: 'GET', path: '/start', access: 'public', handler: this.handleStart },
7141
+ * ]
7142
+ * return [
7143
+ * {
7144
+ * capability: addonRoutesCapability,
7145
+ * provider: buildAddonRouteProvider('auth-oidc', routes),
7146
+ * },
7147
+ * ]
7148
+ */
7149
+ function buildAddonRouteProvider(id, routes) {
7150
+ return {
7151
+ id,
7152
+ getRoutes: () => routes,
7153
+ invoke: async (input) => {
7154
+ const match = matchRoute(routes, input.method, input.path);
7155
+ if (!match) return {
7156
+ status: 404,
7157
+ headers: {},
7158
+ redirectUrl: null,
7159
+ body: { error: `No route matches ${input.method} ${input.path}` }
7160
+ };
7161
+ const envelope = {
7162
+ status: 200,
7163
+ headers: {},
7164
+ redirectUrl: null
7165
+ };
7166
+ const reply = buildCapturingReply(envelope);
7167
+ const request = {
7168
+ params: {
7169
+ ...input.params,
7170
+ ...match.params
7171
+ },
7172
+ query: input.query,
7173
+ body: input.body,
7174
+ headers: input.headers,
7175
+ ...input.user ? { user: input.user } : {},
7176
+ ...input.scopedToken !== void 0 ? { scopedToken: input.scopedToken } : {}
7177
+ };
7178
+ await match.route.handler(request, reply);
7179
+ return envelope;
7180
+ }
7181
+ };
7182
+ }
7183
+ /**
7184
+ * Pattern matcher: same semantics as `AddonRouteRegistry.matchRoute`
7185
+ * but operating on a flat list and bypassing the `/addon/<id>/` prefix
7186
+ * — the bridge sends the post-prefix path directly so we don't need
7187
+ * to round-trip it through normalization.
7188
+ */
7189
+ function matchRoute(routes, method, path) {
7190
+ const normalizedMethod = method.toUpperCase();
7191
+ for (const route of routes) {
7192
+ if (route.method !== normalizedMethod) continue;
7193
+ const params = matchPath(route.path, path);
7194
+ if (params !== null) return {
7195
+ route,
7196
+ params
7197
+ };
7198
+ }
7199
+ return null;
7200
+ }
7201
+ function matchPath(pattern, p) {
7202
+ const patternParts = pattern.split("/").filter(Boolean);
7203
+ const pathParts = p.split("/").filter(Boolean);
7204
+ if (patternParts.length !== pathParts.length) return null;
7205
+ const params = {};
7206
+ for (let i = 0; i < patternParts.length; i++) {
7207
+ const a = patternParts[i];
7208
+ const b = pathParts[i];
7209
+ if (a.startsWith(":")) params[a.slice(1)] = b;
7210
+ else if (a !== b) return null;
7211
+ }
7212
+ return params;
7213
+ }
7214
+ function buildCapturingReply(envelope) {
7215
+ const wrapper = {
7216
+ status(code) {
7217
+ envelope.status = code;
7218
+ return wrapper;
7219
+ },
7220
+ code(code) {
7221
+ envelope.status = code;
7222
+ return wrapper;
7223
+ },
7224
+ send(data) {
7225
+ envelope.body = data;
7226
+ },
7227
+ redirect(url) {
7228
+ envelope.redirectUrl = url;
7229
+ if (envelope.status === 200) envelope.status = 302;
7230
+ },
7231
+ header(name, value) {
7232
+ envelope.headers[name.toLowerCase()] = value;
7233
+ return wrapper;
7234
+ },
7235
+ type(mime) {
7236
+ envelope.contentType = mime;
7237
+ return wrapper;
7238
+ }
7239
+ };
7240
+ return wrapper;
7241
+ }
7242
+ /**
7055
7243
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
7056
7244
  * Named `RecordingWeekday` to avoid collision with the string-union
7057
7245
  * `Weekday` exported from `interfaces/timezones.ts`.
@@ -10601,7 +10789,8 @@ var ModelFormatSchema$1 = _enum([
10601
10789
  "coreml",
10602
10790
  "openvino",
10603
10791
  "tflite",
10604
- "pt"
10792
+ "pt",
10793
+ "gguf"
10605
10794
  ]);
10606
10795
  var PipelineSlotSchema = _enum([
10607
10796
  "detector",
@@ -10695,7 +10884,8 @@ var EngineProvisioningSchema = object({
10695
10884
  runtimeId: _enum([
10696
10885
  "onnx",
10697
10886
  "openvino",
10698
- "coreml"
10887
+ "coreml",
10888
+ "edgetpu"
10699
10889
  ]).nullable(),
10700
10890
  device: string().nullable(),
10701
10891
  state: _enum([
@@ -10856,7 +11046,15 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10856
11046
  image: _instanceof(Uint8Array).optional(),
10857
11047
  referenceImage: string().optional(),
10858
11048
  deviceId: number().optional(),
10859
- sessionId: string().optional()
11049
+ sessionId: string().optional(),
11050
+ /**
11051
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
11052
+ * reference-image, and detail-subtree calls. 'frame' is the live
11053
+ * per-frame dispatch: ONLY root-plane steps run; crop children
11054
+ * (inputClasses ≠ null) are skipped and served per-track via
11055
+ * pipelineRunner.runDetailSubtree (two-plane design).
11056
+ */
11057
+ plane: _enum(["full", "frame"]).optional()
10860
11058
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
10861
11059
  engine: PipelineEngineChoiceSchema.optional(),
10862
11060
  steps: array(PipelineStepInputSchema).min(1),
@@ -10999,6 +11197,28 @@ var NativeCropResultSchema = object({
10999
11197
  width: number().int().positive(),
11000
11198
  height: number().int().positive()
11001
11199
  });
11200
+ /** Parent detection context passed to `runDetailSubtree` — the crop's
11201
+ * originating detection, in FRAME-space coordinates. Reuses
11202
+ * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
11203
+ * the coordinates are frame-space rather than getNativeCrop's
11204
+ * normalized [0,1] convention). */
11205
+ var DetailParentSchema = object({
11206
+ bbox: NativeCropBboxSchema,
11207
+ className: string()
11208
+ });
11209
+ /** One child-step result from `runDetailSubtree` — an embedding, label,
11210
+ * or refined detection produced by running the crop-subtree on a
11211
+ * single tracked detection. */
11212
+ var DetailResultSchema = object({
11213
+ stepId: string(),
11214
+ className: string(),
11215
+ score: number(),
11216
+ /** FRAME-space bbox (already mapped back from crop space). */
11217
+ bbox: NativeCropBboxSchema.optional(),
11218
+ embedding: string().optional(),
11219
+ label: string().optional(),
11220
+ alignedCropJpeg: string().optional()
11221
+ });
11002
11222
  /**
11003
11223
  * Per-camera tunable ranges + defaults. Single source of truth used
11004
11224
  * by both the Zod data schema (validation + default fallback) and
@@ -11259,7 +11479,13 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
11259
11479
  handle: FrameHandleSchema,
11260
11480
  bbox: NativeCropBboxSchema,
11261
11481
  maxWidth: number().int().positive().optional()
11262
- }), NativeCropResultSchema.nullable());
11482
+ }), NativeCropResultSchema.nullable()), method(object({
11483
+ deviceId: number(),
11484
+ frameHandle: FrameHandleSchema.optional(),
11485
+ cropJpeg: string().optional(),
11486
+ parent: DetailParentSchema,
11487
+ steps: array(string()).optional()
11488
+ }), object({ details: array(DetailResultSchema) }).nullable(), { kind: "mutation" });
11263
11489
  object({
11264
11490
  detected: boolean(),
11265
11491
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -11871,6 +12097,140 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
11871
12097
  deviceId: number(),
11872
12098
  status: PtzAutotrackStatusSchema
11873
12099
  });
12100
+ /**
12101
+ * scene-monitor — device-scoped reference-region state cap. An operator marks
12102
+ * a rect ROI on a camera frame and names one or more states; the engine
12103
+ * (pipeline-analytics, designated post-processing node) reports which state is
12104
+ * active on an ongoing basis. Two operator-selectable check modes share every-
12105
+ * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
12106
+ * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
12107
+ *
12108
+ * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) — the framework
12109
+ * derives the device-detail contribution; the provider carries NO hand-written
12110
+ * settings-contribution methods. `status.kind:'push'` — the engine pushes on
12111
+ * every hysteresis flip / availability change; consumers never poll.
12112
+ */
12113
+ /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
12114
+ * be added without a wire break (matching falls back to any-condition refs). */
12115
+ var SceneConditionSchema = string();
12116
+ /** One captured reference — condition-tagged, model-version-gated. `embedding`
12117
+ * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
12118
+ var SceneReferenceSchema = object({
12119
+ embedding: array(number()),
12120
+ modelId: string(),
12121
+ condition: SceneConditionSchema,
12122
+ capturedAt: number(),
12123
+ thumbnailMediaId: string().optional()
12124
+ });
12125
+ var SceneMonitorStateSchema = object({
12126
+ id: string(),
12127
+ label: string(),
12128
+ references: array(SceneReferenceSchema),
12129
+ textPrompts: array(string()).optional(),
12130
+ referenceCount: number(),
12131
+ currentlyMatched: boolean()
12132
+ });
12133
+ /** Per-mode comparator params in a discriminated union so an `llm` scene never
12134
+ * carries similarity knobs and vice-versa. */
12135
+ var SceneCheckSchema = discriminatedUnion("mode", [object({
12136
+ mode: literal("similarity"),
12137
+ threshold: number().min(0).max(1),
12138
+ hysteresisCount: number().int().positive()
12139
+ }), object({
12140
+ mode: literal("llm"),
12141
+ prompt: string(),
12142
+ profileId: string().optional(),
12143
+ hysteresisCount: number().int().positive()
12144
+ })]);
12145
+ var SceneMonitorSchema = object({
12146
+ id: string(),
12147
+ label: string(),
12148
+ roi: MaskRectShapeSchema,
12149
+ enabled: boolean(),
12150
+ triggerMode: _enum([
12151
+ "periodic",
12152
+ "on-motion",
12153
+ "both"
12154
+ ]),
12155
+ checkIntervalSec: number().optional(),
12156
+ check: SceneCheckSchema,
12157
+ states: array(SceneMonitorStateSchema),
12158
+ currentStateId: string().nullable(),
12159
+ lastCheckedAt: number().nullable(),
12160
+ lastConfidence: number().nullable(),
12161
+ currentCondition: SceneConditionSchema.nullable(),
12162
+ availability: _enum(["ok", "unavailable"]),
12163
+ unavailableReason: string().nullable()
12164
+ });
12165
+ var SceneMonitorStatusSchema = object({
12166
+ monitors: array(SceneMonitorSchema),
12167
+ lastFetchedAt: number()
12168
+ });
12169
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
12170
+ deviceId: number(),
12171
+ label: string(),
12172
+ roi: MaskRectShapeSchema,
12173
+ check: SceneCheckSchema,
12174
+ triggerMode: _enum([
12175
+ "periodic",
12176
+ "on-motion",
12177
+ "both"
12178
+ ]).optional(),
12179
+ checkIntervalSec: number().optional()
12180
+ }), SceneMonitorSchema, {
12181
+ kind: "mutation",
12182
+ auth: "admin"
12183
+ }), method(object({
12184
+ deviceId: number(),
12185
+ monitorId: string(),
12186
+ patch: object({
12187
+ label: string().optional(),
12188
+ roi: MaskRectShapeSchema.optional(),
12189
+ enabled: boolean().optional(),
12190
+ triggerMode: _enum([
12191
+ "periodic",
12192
+ "on-motion",
12193
+ "both"
12194
+ ]).optional(),
12195
+ checkIntervalSec: number().optional(),
12196
+ check: SceneCheckSchema.optional()
12197
+ })
12198
+ }), _void(), {
12199
+ kind: "mutation",
12200
+ auth: "admin"
12201
+ }), method(object({
12202
+ deviceId: number(),
12203
+ monitorId: string()
12204
+ }), _void(), {
12205
+ kind: "mutation",
12206
+ auth: "admin"
12207
+ }), method(object({
12208
+ deviceId: number(),
12209
+ monitorId: string(),
12210
+ stateId: string().optional(),
12211
+ label: string().optional(),
12212
+ condition: SceneConditionSchema.optional()
12213
+ }), object({
12214
+ stateId: string(),
12215
+ referenceCount: number()
12216
+ }), {
12217
+ kind: "mutation",
12218
+ auth: "admin"
12219
+ }), method(object({
12220
+ deviceId: number(),
12221
+ monitorId: string(),
12222
+ stateId: string(),
12223
+ index: number()
12224
+ }), _void(), {
12225
+ kind: "mutation",
12226
+ auth: "admin"
12227
+ }), method(object({
12228
+ deviceId: number(),
12229
+ monitorId: string()
12230
+ }), _void(), {
12231
+ kind: "mutation",
12232
+ auth: "admin"
12233
+ });
11874
12234
  object({
11875
12235
  /** Whether the script is currently executing. */
11876
12236
  isRunning: boolean(),
@@ -12278,6 +12638,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
12278
12638
  * with the per-track detail panel and live overlay. */
12279
12639
  trackIds: array(string()).readonly()
12280
12640
  });
12641
+ /**
12642
+ * A parked ("stationary") object surfaced alongside occupancy — an object that
12643
+ * settled and stopped moving. It is NO LONGER a tracked object (the tracker was
12644
+ * told to forget it so it stops re-spawning tracks/events), but it IS still
12645
+ * physically present, so it keeps counting toward `frame` occupancy and is
12646
+ * listed here so the UI can show it in a dedicated "Stationary" section instead
12647
+ * of flooding the live event feed.
12648
+ */
12649
+ var StationaryObjectSchema = object({
12650
+ id: string(),
12651
+ className: string(),
12652
+ bbox: object({
12653
+ x: number(),
12654
+ y: number(),
12655
+ w: number(),
12656
+ h: number()
12657
+ }),
12658
+ frameWidth: number().int().nonnegative(),
12659
+ frameHeight: number().int().nonnegative(),
12660
+ /** When the source track was first seen. */
12661
+ firstSeenAt: number().int(),
12662
+ /** When the object was recognised as parked (promotion time). */
12663
+ becameStationaryAt: number().int(),
12664
+ /** Last frame a detection confirmed the object is still there. */
12665
+ lastConfirmedAt: number().int(),
12666
+ /** Enrichment label carried from the source track (identity / plate). */
12667
+ label: string().optional(),
12668
+ /** Native-resolution key-frame media key for the parked object's best image. */
12669
+ keyFrameMediaKey: string().optional()
12670
+ });
12281
12671
  var CameraOccupancySnapshotSchema = object({
12282
12672
  /** Frame timestamp of the inference result that produced this snapshot. */
12283
12673
  ts: number().int(),
@@ -12286,10 +12676,15 @@ var CameraOccupancySnapshotSchema = object({
12286
12676
  frameHeight: number().int().nonnegative(),
12287
12677
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
12288
12678
  zones: array(ZoneScopeBreakdownSchema).readonly(),
12289
- /** Frame-wide aggregate (everywhere, regardless of zone membership). */
12679
+ /** Frame-wide aggregate (everywhere, regardless of zone membership).
12680
+ * INCLUDES currently-confirmed stationary objects (they are still present). */
12290
12681
  frame: PerScopeBreakdownSchema,
12291
12682
  /** Detections that landed outside every zone. Empty when no zones defined. */
12292
- unzoned: PerScopeBreakdownSchema
12683
+ unzoned: PerScopeBreakdownSchema,
12684
+ /** Parked objects on this camera (additive — absent on legacy snapshots).
12685
+ * Surfaced separately so the UI shows them in a dedicated section rather
12686
+ * than as repeated tracks/events. */
12687
+ stationaryObjects: array(StationaryObjectSchema).readonly().optional()
12293
12688
  });
12294
12689
  /**
12295
12690
  * Time-series resolution. The history methods return one bucket per
@@ -12659,7 +13054,26 @@ var InvokeReplyEnvelopeSchema = object({
12659
13054
  /** Set when the handler called `reply.type(mime)`. */
12660
13055
  contentType: string().optional()
12661
13056
  });
12662
- method(_void(), array(AddonHttpRouteSchema)), method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" });
13057
+ var addonRoutesCapability = {
13058
+ name: "addon-routes",
13059
+ scope: "system",
13060
+ mode: "collection",
13061
+ internal: true,
13062
+ methods: {
13063
+ getRoutes: method(_void(), array(AddonHttpRouteSchema)),
13064
+ /**
13065
+ * Cross-process dispatch entry point. Forked addons implement this
13066
+ * (via `buildAddonRouteProvider`) so the hub's Fastify catch-all
13067
+ * can route through Moleculer when the handler lives in a worker.
13068
+ *
13069
+ * Local addons can implement it for free with the same helper;
13070
+ * the hub bypasses the wire on co-located addons.
13071
+ */
13072
+ invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
13073
+ },
13074
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
13075
+ mount: { kind: "skip" }
13076
+ };
12663
13077
  var ConfigTabDeclarationSchema = object({
12664
13078
  id: string(),
12665
13079
  label: string(),
@@ -12892,7 +13306,12 @@ var NotificationRuleConditionsSchema = object({
12892
13306
  clipDescription: object({
12893
13307
  text: string().min(1),
12894
13308
  minSimilarity: number().min(0).max(1)
12895
- }).optional()
13309
+ }).optional(),
13310
+ /** Match events whose recognized-entity label (face identity name or plate
13311
+ * vehicle name, propagated onto `event.data.label`) is one of these values.
13312
+ * Empty/absent → unaffected (back-compat). Enables "notify me when <named
13313
+ * vehicle/person> is seen". */
13314
+ labels: array(string()).readonly().optional()
12896
13315
  });
12897
13316
  var NotificationRuleTemplateSchema = object({
12898
13317
  title: string(),
@@ -13150,11 +13569,19 @@ method(object({
13150
13569
  * login page needs NO change.
13151
13570
  *
13152
13571
  * - `widget` — a Module-Federation widget the login page mounts (via
13153
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13154
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13155
- * addon bundle. The referenced widget also declares `preAuth: true` in
13156
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13157
- * stamps a public `bundleUrl` from `addonId` + `bundle`.
13572
+ * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
13573
+ * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
13574
+ * mechanism kept for future use; no shipped addon uses it on the login
13575
+ * page (the passkey ceremony below runs natively in the shell instead).
13576
+ *
13577
+ * - `passkey` — a declarative WebAuthn ceremony the shell renders
13578
+ * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
13579
+ * a remotely-loaded bundle). Carries the addon's effective `rpId` /
13580
+ * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
13581
+ * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
13582
+ * fetching any remote code pre-auth. Contribution stays unconditional —
13583
+ * enrollment state is never leaked pre-auth; visibility is a shell
13584
+ * decision.
13158
13585
  *
13159
13586
  * Every contribution carries a `stage`:
13160
13587
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -13168,30 +13595,45 @@ method(object({
13168
13595
  */
13169
13596
  /** When a login method renders in the two-phase login flow. */
13170
13597
  var LoginStageEnum = _enum(["primary", "second-factor"]);
13171
- /** One login-method contribution — redirect button OR pre-auth widget. */
13172
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13173
- kind: literal("redirect"),
13174
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13175
- id: string(),
13176
- /** Operator-facing button label. */
13177
- label: string(),
13178
- /** lucide-react icon name. */
13179
- icon: string().optional(),
13180
- /** Addon-owned HTTP route the button navigates to (GET). */
13181
- startUrl: string(),
13182
- stage: LoginStageEnum
13183
- }), object({
13184
- kind: literal("widget"),
13185
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13186
- id: string(),
13187
- /** Owning addon id drives the public bundle URL + the MF namespace. */
13188
- addonId: string(),
13189
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13190
- bundle: string(),
13191
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
13192
- remote: WidgetRemoteSchema,
13193
- stage: LoginStageEnum
13194
- })]);
13598
+ /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
13599
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [
13600
+ object({
13601
+ kind: literal("redirect"),
13602
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13603
+ id: string(),
13604
+ /** Operator-facing button label. */
13605
+ label: string(),
13606
+ /** lucide-react icon name. */
13607
+ icon: string().optional(),
13608
+ /** Addon-owned HTTP route the button navigates to (GET). */
13609
+ startUrl: string(),
13610
+ stage: LoginStageEnum
13611
+ }),
13612
+ object({
13613
+ kind: literal("widget"),
13614
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13615
+ id: string(),
13616
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13617
+ addonId: string(),
13618
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13619
+ bundle: string(),
13620
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13621
+ remote: WidgetRemoteSchema,
13622
+ stage: LoginStageEnum
13623
+ }),
13624
+ object({
13625
+ kind: literal("passkey"),
13626
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
13627
+ id: string(),
13628
+ /** Operator-facing button label. */
13629
+ label: string(),
13630
+ stage: LoginStageEnum,
13631
+ /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
13632
+ rpId: string(),
13633
+ /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
13634
+ origin: string().nullable()
13635
+ })
13636
+ ]);
13195
13637
  method(_void(), array(LoginMethodContributionSchema).readonly());
13196
13638
  /**
13197
13639
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -15019,14 +15461,14 @@ var TargetKindCapsSchema = object({
15019
15461
  * the union is large and not meant for runtime validation here; the exported
15020
15462
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
15021
15463
  */
15022
- var ConfigSchemaPassthrough = unknown();
15464
+ var ConfigSchemaPassthrough$1 = unknown();
15023
15465
  var TargetKindSchema = object({
15024
15466
  kind: string(),
15025
15467
  label: string(),
15026
15468
  icon: string(),
15027
15469
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
15028
15470
  addonId: string(),
15029
- configSchema: ConfigSchemaPassthrough,
15471
+ configSchema: ConfigSchemaPassthrough$1,
15030
15472
  supportsDiscovery: boolean(),
15031
15473
  caps: TargetKindCapsSchema
15032
15474
  });
@@ -15093,101 +15535,393 @@ var notificationOutputCapability = {
15093
15535
  }
15094
15536
  };
15095
15537
  /**
15096
- * Zod schemas for persisted record types.
15097
- *
15098
- * These schemas serve as the single source of truth for types that are
15099
- * stored in the settings-store backend and retrieved via tRPC. They
15100
- * replace the manual TypeScript interfaces with schema-derived types,
15101
- * enabling runtime validation when reading from the backend.
15538
+ * Shared LLM generate contracts imported by BOTH `llm.cap.ts` (consumer
15539
+ * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15540
+ * caps stay wire-compatible without a circular cap→cap import.
15102
15541
  *
15103
- * Convention: each schema is named `<Type>Schema`, the inferred TS type
15104
- * keeps the original name for backward compatibility.
15542
+ * Errors are a discriminated-union RESULT, never thrown: the shape survives
15543
+ * every transport tier structurally, and failed calls still write usage rows.
15544
+ * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15105
15545
  */
15546
+ var LlmUsageSchema = object({
15547
+ inputTokens: number(),
15548
+ outputTokens: number()
15549
+ });
15550
+ var LlmErrorCodeSchema = _enum([
15551
+ "timeout",
15552
+ "rate-limited",
15553
+ "auth",
15554
+ "refusal",
15555
+ "bad-request",
15556
+ "unavailable",
15557
+ "no-profile",
15558
+ "budget-exceeded",
15559
+ "adapter-error"
15560
+ ]);
15561
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15562
+ ok: literal(true),
15563
+ text: string(),
15564
+ model: string(),
15565
+ usage: LlmUsageSchema,
15566
+ truncated: boolean(),
15567
+ latencyMs: number()
15568
+ }), object({
15569
+ ok: literal(false),
15570
+ code: LlmErrorCodeSchema,
15571
+ message: string(),
15572
+ retryAfterMs: number().optional()
15573
+ })]);
15106
15574
  /**
15107
- * Auth model (v22026-05-12 caps-only):
15108
- *
15109
- * Two principals:
15110
- * - `isAdmin: true` — full bypass. Admin scope set is ignored.
15111
- * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
15112
- *
15113
- * No role enum. There used to be `viewer / agent / scoped` and earlier
15114
- * `super_admin` — collapsed onto the single boolean because every
15115
- * non-admin caller is governed by the same scope-check rules; the
15116
- * distinction was only ever used to route role-specific procedures
15117
- * which we no longer have. Agents are admin sessions issued via
15118
- * `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
15119
- * with `isAdmin: false` + an explicit scope list.
15575
+ * `Uint8Array` is the sanctioned binary convention superjson + the UDS
15576
+ * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15577
+ * notification-output.cap.ts:27-31 precedents).
15120
15578
  */
15121
- /** Per-method access flavour. Materialised on every cap method via codegen. */
15122
- var MethodAccessSchema = _enum([
15123
- "view",
15124
- "create",
15125
- "delete"
15126
- ]);
15579
+ var LlmImageSchema = object({
15580
+ bytes: _instanceof(Uint8Array),
15581
+ mimeType: string()
15582
+ });
15583
+ var LlmGenerateBaseInputSchema = object({
15584
+ /** Collection routing (the notification-output posture). */
15585
+ addonId: string().optional(),
15586
+ /** Explicit profile; else the resolution chain (spec §3). */
15587
+ profileId: string().optional(),
15588
+ /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15589
+ consumer: string(),
15590
+ system: string().optional(),
15591
+ /** v1: single-turn. `messages[]` is a v2 additive field. */
15592
+ prompt: string(),
15593
+ /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15594
+ jsonSchema: record(string(), unknown()).optional(),
15595
+ /** Per-call override of the profile default. */
15596
+ maxTokens: number().int().positive().optional(),
15597
+ temperature: number().optional()
15598
+ });
15127
15599
  /**
15128
- * A single scope grant. Three forms checked in matcher with OR
15129
- * semantics; the first match decides whether the call passes:
15130
- *
15131
- * - `category` broadest. `target: 'device' | 'system'` selects every
15132
- * cap with the matching `scope` declaration. New caps inherit grants
15133
- * automatically — adding a device cap to the codebase makes it
15134
- * reachable to every user with `category:device [...]` without a
15135
- * re-grant. Use this for "family viewer" / "operator" tiers.
15136
- *
15137
- * - `addon` — every cap that ships inside a given addon id. Niche;
15138
- * useful when an integration needs to interact with one specific
15139
- * addon's surface regardless of how many caps it exports.
15140
- *
15141
- * - `capability` — the most precise form. Single cap name. Use for
15142
- * integration tokens where you want to grant `addons.installPackage`
15143
- * and nothing else.
15600
+ * `llm-runtime` node-side managed llama.cpp executor (spec §4). Registered
15601
+ * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15602
+ * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
15603
+ * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15604
+ * this only through the `llm` cap's methods.
15144
15605
  *
15145
- * `access` is REQUIRED with min 1 element. `admin` bypass is the
15146
- * "give me everything" escape; there is no wildcard scope.
15606
+ * One running llama-server child per node in v1 (models are RAM-heavy).
15607
+ * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15608
+ * watchdog — operator decision #3).
15147
15609
  */
15148
- var AllowedProviderSchema = union([literal("*"), array(string())]);
15149
- var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
15150
- var CapScopeSchema = _enum(["device", "system"]);
15151
- var TokenScopeSchema = discriminatedUnion("type", [
15610
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
15152
15611
  object({
15153
- type: literal("category"),
15154
- target: CapScopeSchema,
15155
- access: array(MethodAccessSchema).min(1)
15156
- }),
15157
- object({
15158
- type: literal("capability"),
15159
- target: string(),
15160
- access: array(MethodAccessSchema).min(1)
15612
+ kind: literal("catalog"),
15613
+ catalogId: string()
15161
15614
  }),
15162
15615
  object({
15163
- type: literal("addon"),
15164
- target: string(),
15165
- access: array(MethodAccessSchema).min(1)
15616
+ kind: literal("url"),
15617
+ url: string(),
15618
+ sha256: string().optional()
15166
15619
  }),
15167
15620
  object({
15168
- type: literal("device"),
15169
- /**
15170
- * One or more deviceIds (serialised as strings for wire-format
15171
- * consistency with the rest of the union). Matcher accepts if
15172
- * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
15173
- * of one scope-per-device when granting access to a set of cameras.
15174
- */
15175
- targets: array(string()).min(1),
15176
- access: array(MethodAccessSchema).min(1)
15621
+ kind: literal("path"),
15622
+ path: string()
15177
15623
  })
15178
15624
  ]);
15179
- object({
15180
- id: string(),
15181
- username: string(),
15182
- passwordHash: string(),
15183
- /**
15184
- * Admin bypass. When true, the middleware skips the scope-access
15185
- * check entirely. There is no other axis of privilege; the legacy
15186
- * role enum collapsed onto this boolean in v2.
15187
- */
15188
- isAdmin: boolean().default(false),
15189
- allowedProviders: AllowedProviderSchema,
15190
- allowedDevices: AllowedDevicesSchema,
15625
+ var ManagedRuntimeConfigSchema = object({
15626
+ /** WHERE the runtime lives — hub or any agent. */
15627
+ nodeId: string(),
15628
+ /** Closed for v1; 'ollama' is a v2 candidate. */
15629
+ engine: _enum(["llama-cpp"]),
15630
+ model: ManagedModelRefSchema,
15631
+ contextSize: number().int().default(4096),
15632
+ /** 0 = CPU-only. */
15633
+ gpuLayers: number().int().default(0),
15634
+ /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15635
+ threads: number().int().optional(),
15636
+ /** Concurrent slots. */
15637
+ parallel: number().int().default(1),
15638
+ /** Else lazy: first generate boots it. */
15639
+ autoStart: boolean().default(false),
15640
+ /** 0 = never; frees RAM after quiet periods. */
15641
+ idleStopMinutes: number().int().default(30)
15642
+ });
15643
+ var LlmRuntimeStatusSchema = object({
15644
+ /** Status is ALWAYS node-qualified. */
15645
+ nodeId: string(),
15646
+ state: _enum([
15647
+ "stopped",
15648
+ "downloading",
15649
+ "starting",
15650
+ "ready",
15651
+ "crashed",
15652
+ "failed"
15653
+ ]),
15654
+ pid: number().optional(),
15655
+ port: number().optional(),
15656
+ modelPath: string().optional(),
15657
+ modelId: string().optional(),
15658
+ downloadProgress: number().min(0).max(1).optional(),
15659
+ lastError: string().optional(),
15660
+ crashesInWindow: number(),
15661
+ /** Child RSS (sampled best-effort). */
15662
+ memoryBytes: number().optional(),
15663
+ vramBytes: number().optional()
15664
+ });
15665
+ var LlmNodeModelSchema = object({
15666
+ file: string(),
15667
+ sizeBytes: number(),
15668
+ catalogId: string().optional(),
15669
+ installedAt: number().optional()
15670
+ });
15671
+ var LlmRuntimeDiskUsageSchema = object({
15672
+ nodeId: string(),
15673
+ modelsBytes: number(),
15674
+ freeBytes: number().optional()
15675
+ });
15676
+ method(LlmGenerateBaseInputSchema.extend({
15677
+ images: array(LlmImageSchema).optional(),
15678
+ runtime: ManagedRuntimeConfigSchema,
15679
+ /** The managed profile's timeout, threaded by the hub provider. */
15680
+ timeoutMs: number().int().positive().optional()
15681
+ }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15682
+ kind: "mutation",
15683
+ auth: "admin"
15684
+ }), method(object({}), _void(), {
15685
+ kind: "mutation",
15686
+ auth: "admin"
15687
+ }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15688
+ kind: "mutation",
15689
+ auth: "admin"
15690
+ }), method(object({ file: string() }), _void(), {
15691
+ kind: "mutation",
15692
+ auth: "admin"
15693
+ }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15694
+ /**
15695
+ * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15696
+ * methods concat-fan across providers; single-row methods route to ONE
15697
+ * provider by the `addonId` in the call input (the notification-output
15698
+ * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15699
+ * (hub-placed); the cap stays open for future providers.
15700
+ *
15701
+ * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15702
+ * `apiKey` is a password field — providers REDACT it on read and merge on
15703
+ * write; a stored key NEVER round-trips to a client.
15704
+ */
15705
+ var LlmProfileKindSchema = _enum([
15706
+ "openai-compatible",
15707
+ "openai",
15708
+ "anthropic",
15709
+ "google",
15710
+ "managed-local"
15711
+ ]);
15712
+ var LlmProfileSchema = object({
15713
+ id: string(),
15714
+ name: string(),
15715
+ kind: LlmProfileKindSchema,
15716
+ /** Stamped by the provider — keeps the fanned catalog routable. */
15717
+ addonId: string(),
15718
+ enabled: boolean(),
15719
+ /** Vendor model id, or the managed runtime's loaded model. */
15720
+ model: string(),
15721
+ /** Required for openai-compatible; override for cloud kinds. */
15722
+ baseUrl: string().optional(),
15723
+ /** ConfigUISchema type:'password' — never round-trips (spec §5). */
15724
+ apiKey: string().optional(),
15725
+ supportsVision: boolean(),
15726
+ temperature: number().min(0).max(2).optional(),
15727
+ maxTokens: number().int().positive().optional(),
15728
+ timeoutMs: number().int().positive().default(6e4),
15729
+ extraHeaders: record(string(), string()).optional(),
15730
+ /** kind === 'managed-local' only (spec §4). */
15731
+ runtime: ManagedRuntimeConfigSchema.optional()
15732
+ });
15733
+ /** ConfigUISchema tree passed through untyped on the wire (the
15734
+ * notification-output `ConfigSchemaPassthrough` precedent at
15735
+ * notification-output.cap.ts:151); the exported TS type re-tightens it. */
15736
+ var ConfigSchemaPassthrough = unknown();
15737
+ var LlmProfileKindDescriptorSchema = object({
15738
+ kind: LlmProfileKindSchema,
15739
+ label: string(),
15740
+ icon: string(),
15741
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
15742
+ addonId: string(),
15743
+ configSchema: ConfigSchemaPassthrough
15744
+ });
15745
+ var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
15746
+ var LlmDefaultSchema = object({
15747
+ selector: LlmDefaultSelectorSchema,
15748
+ profileId: string()
15749
+ });
15750
+ /** Server-side rollup row — getUsage never dumps raw call rows (spec §6). */
15751
+ var LlmUsageRollupSchema = object({
15752
+ day: string(),
15753
+ consumer: string(),
15754
+ profileId: string(),
15755
+ calls: number(),
15756
+ okCalls: number(),
15757
+ errorCalls: number(),
15758
+ inputTokens: number(),
15759
+ outputTokens: number(),
15760
+ avgLatencyMs: number()
15761
+ });
15762
+ /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
15763
+ var ManagedModelCatalogEntrySchema = object({
15764
+ id: string(),
15765
+ label: string(),
15766
+ family: string(),
15767
+ purpose: _enum(["text", "vision"]),
15768
+ url: string(),
15769
+ sha256: string(),
15770
+ sizeBytes: number(),
15771
+ quantization: string(),
15772
+ /** Load-time guidance shown in the picker. */
15773
+ minRamBytes: number(),
15774
+ contextSizeDefault: number().int(),
15775
+ /** Vision models: companion projector file. */
15776
+ mmprojUrl: string().optional()
15777
+ });
15778
+ var LlmRuntimeNodeSchema = object({
15779
+ nodeId: string(),
15780
+ reachable: boolean(),
15781
+ status: LlmRuntimeStatusSchema.optional(),
15782
+ disk: LlmRuntimeDiskUsageSchema.optional(),
15783
+ error: string().optional()
15784
+ });
15785
+ var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15786
+ var ProfileRefInputSchema = object({
15787
+ addonId: string(),
15788
+ profileId: string()
15789
+ });
15790
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
15791
+ kind: "mutation",
15792
+ auth: "admin"
15793
+ }), method(ProfileRefInputSchema, _void(), {
15794
+ kind: "mutation",
15795
+ auth: "admin"
15796
+ }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
15797
+ kind: "mutation",
15798
+ auth: "admin"
15799
+ }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
15800
+ selector: LlmDefaultSelectorSchema,
15801
+ profileId: string().nullable()
15802
+ }), _void(), {
15803
+ kind: "mutation",
15804
+ auth: "admin"
15805
+ }), method(object({
15806
+ since: number().optional(),
15807
+ until: number().optional(),
15808
+ consumer: string().optional(),
15809
+ profileId: string().optional()
15810
+ }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
15811
+ nodeId: string(),
15812
+ model: ManagedModelRefSchema
15813
+ }), _void(), {
15814
+ kind: "mutation",
15815
+ auth: "admin"
15816
+ }), method(object({
15817
+ nodeId: string(),
15818
+ file: string()
15819
+ }), _void(), {
15820
+ kind: "mutation",
15821
+ auth: "admin"
15822
+ }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
15823
+ kind: "mutation",
15824
+ auth: "admin"
15825
+ }), method(ProfileRefInputSchema, _void(), {
15826
+ kind: "mutation",
15827
+ auth: "admin"
15828
+ });
15829
+ /**
15830
+ * Zod schemas for persisted record types.
15831
+ *
15832
+ * These schemas serve as the single source of truth for types that are
15833
+ * stored in the settings-store backend and retrieved via tRPC. They
15834
+ * replace the manual TypeScript interfaces with schema-derived types,
15835
+ * enabling runtime validation when reading from the backend.
15836
+ *
15837
+ * Convention: each schema is named `<Type>Schema`, the inferred TS type
15838
+ * keeps the original name for backward compatibility.
15839
+ */
15840
+ /**
15841
+ * Auth model (v2 — 2026-05-12 caps-only):
15842
+ *
15843
+ * Two principals:
15844
+ * - `isAdmin: true` — full bypass. Admin scope set is ignored.
15845
+ * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
15846
+ *
15847
+ * No role enum. There used to be `viewer / agent / scoped` and earlier
15848
+ * `super_admin` — collapsed onto the single boolean because every
15849
+ * non-admin caller is governed by the same scope-check rules; the
15850
+ * distinction was only ever used to route role-specific procedures
15851
+ * which we no longer have. Agents are admin sessions issued via
15852
+ * `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
15853
+ * with `isAdmin: false` + an explicit scope list.
15854
+ */
15855
+ /** Per-method access flavour. Materialised on every cap method via codegen. */
15856
+ var MethodAccessSchema = _enum([
15857
+ "view",
15858
+ "create",
15859
+ "delete"
15860
+ ]);
15861
+ /**
15862
+ * A single scope grant. Three forms — checked in matcher with OR
15863
+ * semantics; the first match decides whether the call passes:
15864
+ *
15865
+ * - `category` — broadest. `target: 'device' | 'system'` selects every
15866
+ * cap with the matching `scope` declaration. New caps inherit grants
15867
+ * automatically — adding a device cap to the codebase makes it
15868
+ * reachable to every user with `category:device [...]` without a
15869
+ * re-grant. Use this for "family viewer" / "operator" tiers.
15870
+ *
15871
+ * - `addon` — every cap that ships inside a given addon id. Niche;
15872
+ * useful when an integration needs to interact with one specific
15873
+ * addon's surface regardless of how many caps it exports.
15874
+ *
15875
+ * - `capability` — the most precise form. Single cap name. Use for
15876
+ * integration tokens where you want to grant `addons.installPackage`
15877
+ * and nothing else.
15878
+ *
15879
+ * `access` is REQUIRED with min 1 element. `admin` bypass is the
15880
+ * "give me everything" escape; there is no wildcard scope.
15881
+ */
15882
+ var AllowedProviderSchema = union([literal("*"), array(string())]);
15883
+ var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
15884
+ var CapScopeSchema = _enum(["device", "system"]);
15885
+ var TokenScopeSchema = discriminatedUnion("type", [
15886
+ object({
15887
+ type: literal("category"),
15888
+ target: CapScopeSchema,
15889
+ access: array(MethodAccessSchema).min(1)
15890
+ }),
15891
+ object({
15892
+ type: literal("capability"),
15893
+ target: string(),
15894
+ access: array(MethodAccessSchema).min(1)
15895
+ }),
15896
+ object({
15897
+ type: literal("addon"),
15898
+ target: string(),
15899
+ access: array(MethodAccessSchema).min(1)
15900
+ }),
15901
+ object({
15902
+ type: literal("device"),
15903
+ /**
15904
+ * One or more deviceIds (serialised as strings for wire-format
15905
+ * consistency with the rest of the union). Matcher accepts if
15906
+ * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
15907
+ * of one scope-per-device when granting access to a set of cameras.
15908
+ */
15909
+ targets: array(string()).min(1),
15910
+ access: array(MethodAccessSchema).min(1)
15911
+ })
15912
+ ]);
15913
+ object({
15914
+ id: string(),
15915
+ username: string(),
15916
+ passwordHash: string(),
15917
+ /**
15918
+ * Admin bypass. When true, the middleware skips the scope-access
15919
+ * check entirely. There is no other axis of privilege; the legacy
15920
+ * role enum collapsed onto this boolean in v2.
15921
+ */
15922
+ isAdmin: boolean().default(false),
15923
+ allowedProviders: AllowedProviderSchema,
15924
+ allowedDevices: AllowedDevicesSchema,
15191
15925
  /**
15192
15926
  * Scopes granted to this user. Admins bypass; their `scopes` is
15193
15927
  * ignored. Non-admins without scopes are locked out of every
@@ -15294,6 +16028,22 @@ var TrackSnapshotSchema = object({
15294
16028
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
15295
16029
  mediaKey: string()
15296
16030
  });
16031
+ /**
16032
+ * One audio-classification label heard on the track's camera while the
16033
+ * track was alive, aggregated per label. An "episode" is one persisted
16034
+ * audio event (the confident-classification path: score ≥ the device's
16035
+ * `classificationMinScore`, class-change-or-heartbeat coalesced) — NOT
16036
+ * one 32 ms inference chunk, so counts stay human-scaled.
16037
+ */
16038
+ var TrackAudioLabelSchema = object({
16039
+ label: string(),
16040
+ /** Highest classification score observed across the label's episodes. */
16041
+ peakScore: number(),
16042
+ /** Number of coalesced audio-event episodes carrying this label. */
16043
+ count: number(),
16044
+ firstAt: number(),
16045
+ lastAt: number()
16046
+ });
15297
16047
  var TrackSchema = object({
15298
16048
  trackId: string(),
15299
16049
  deviceId: number(),
@@ -15308,6 +16058,10 @@ var TrackSchema = object({
15308
16058
  snapshots: array(TrackSnapshotSchema).readonly(),
15309
16059
  /** Deduplicated zones the track has entered at least once. */
15310
16060
  zonesVisited: array(string()).readonly(),
16061
+ /** Deduplicated set of detector classes observed for this track over its
16062
+ * life (a track may be reclassified, e.g. person→vehicle). Absent on
16063
+ * legacy rows written before class accumulation shipped. */
16064
+ classes: array(string()).readonly().optional(),
15311
16065
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15312
16066
  totalDistance: number(),
15313
16067
  state: TrackStateSchema,
@@ -15321,7 +16075,11 @@ var TrackSchema = object({
15321
16075
  bestEventId: string().optional(),
15322
16076
  /** Tag of the importance sub-signal that dominated the score
15323
16077
  * (identity|dwell|proximity|class|confidence|travel|zone). */
15324
- importanceReason: string().optional()
16078
+ importanceReason: string().optional(),
16079
+ /** Audio-classification labels heard on the camera during the track's
16080
+ * life (score ≥ device `classificationMinScore`), aggregated per label.
16081
+ * Absent on legacy rows / tracks with no confident audio. */
16082
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15325
16083
  });
15326
16084
  var BaseEventFields = {
15327
16085
  id: string(),
@@ -15469,7 +16227,7 @@ var KeyEventSchema = object({
15469
16227
  /** Track lifetime in ms (lastSeen - firstSeen). */
15470
16228
  windowMs: number().optional()
15471
16229
  });
15472
- var TrackedDetectionSchema = object({
16230
+ object({
15473
16231
  trackId: string(),
15474
16232
  className: string(),
15475
16233
  confidence: number(),
@@ -15477,6 +16235,23 @@ var TrackedDetectionSchema = object({
15477
16235
  zones: array(string()).readonly(),
15478
16236
  state: TrackStateSchema
15479
16237
  });
16238
+ var OverlayDetectionSchema = looseObject({
16239
+ id: string(),
16240
+ kind: _enum(["first-level", "detail"]),
16241
+ macroClass: string(),
16242
+ score: number(),
16243
+ bbox: object({
16244
+ x: number(),
16245
+ y: number(),
16246
+ width: number(),
16247
+ height: number()
16248
+ }),
16249
+ labels: array(looseObject({
16250
+ label: string(),
16251
+ score: number()
16252
+ })).readonly(),
16253
+ parentId: string().optional()
16254
+ });
15480
16255
  var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
15481
16256
  var SearchObjectEventsInput = object({
15482
16257
  text: string(),
@@ -15487,6 +16262,20 @@ var SearchObjectEventsInput = object({
15487
16262
  limit: number().default(50),
15488
16263
  minScore: number().min(0).max(1).default(.2)
15489
16264
  });
16265
+ var TrackCascadeCountsSchema = object({
16266
+ /** Persisted track roots deleted (authoritative). */
16267
+ tracks: number().int(),
16268
+ /** Object events removed with their tracks (best-effort; see note above). */
16269
+ events: number().int(),
16270
+ /** Track/face/plate-owned media removed (best-effort). Never identity media. */
16271
+ media: number().int(),
16272
+ /** Unassigned (non-enrolled) face reads removed (best-effort). */
16273
+ faces: number().int(),
16274
+ /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16275
+ plates: number().int(),
16276
+ /** Per-track CLIP search vectors removed (best-effort). */
16277
+ embeddings: number().int()
16278
+ });
15490
16279
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
15491
16280
  deviceId: number(),
15492
16281
  trackId: string()
@@ -15518,6 +16307,24 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15518
16307
  }), {
15519
16308
  kind: "mutation",
15520
16309
  auth: "admin"
16310
+ }), method(object({
16311
+ deviceId: number(),
16312
+ cutoffMs: number()
16313
+ }), TrackCascadeCountsSchema, {
16314
+ kind: "mutation",
16315
+ auth: "admin"
16316
+ }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
16317
+ kind: "mutation",
16318
+ auth: "admin"
16319
+ }), method(object({
16320
+ deviceId: number(),
16321
+ trackIds: array(string()).min(1)
16322
+ }), object({
16323
+ deleted: number().int(),
16324
+ failed: array(string()).readonly()
16325
+ }), {
16326
+ kind: "mutation",
16327
+ auth: "admin"
15521
16328
  }), method(object({
15522
16329
  eventId: string(),
15523
16330
  kind: MediaFileKindEnum.optional()
@@ -15526,7 +16333,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15526
16333
  timestamp: number(),
15527
16334
  frameWidth: number(),
15528
16335
  frameHeight: number(),
15529
- detections: array(TrackedDetectionSchema).readonly()
16336
+ detections: array(OverlayDetectionSchema).readonly()
15530
16337
  }), object({
15531
16338
  deviceId: number(),
15532
16339
  trackId: string(),
@@ -18295,6 +19102,24 @@ DeviceType.Camera, method(object({
18295
19102
  deviceId: number(),
18296
19103
  status: OsdStatusSchema
18297
19104
  });
19105
+ var VehicleSchema = object({
19106
+ id: string(),
19107
+ name: string(),
19108
+ sampleCount: number().int().nonnegative(),
19109
+ coverMediaKey: string().optional(),
19110
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
19111
+ coverBase64: string().optional()
19112
+ });
19113
+ var VehicleSampleInfoSchema = object({
19114
+ id: string(),
19115
+ /** The plate text this sample enrolled (self-labeling — no embedding). */
19116
+ text: string(),
19117
+ /** OCR confidence of the enrolled read (0..1). */
19118
+ score: number(),
19119
+ addedAt: number().int(),
19120
+ deviceId: number().int().optional(),
19121
+ base64: string().optional()
19122
+ });
18298
19123
  var PlateInfoSchema = object({
18299
19124
  plateId: string(),
18300
19125
  deviceId: number().int(),
@@ -18306,6 +19131,16 @@ var PlateInfoSchema = object({
18306
19131
  score: number(),
18307
19132
  /** True when the text was manually corrected (exempt from retention). */
18308
19133
  corrected: boolean(),
19134
+ /** Recognized vehicle id when a matcher lookup named this read (SQL NULL → absent). */
19135
+ recognizedVehicleId: string().optional(),
19136
+ /** Resolved vehicle name for `recognizedVehicleId` (UI convenience). */
19137
+ vehicleName: string().optional(),
19138
+ /** True once the read was assigned to a vehicle (enrolled as a sample). */
19139
+ assigned: boolean(),
19140
+ /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
19141
+ plateBbox: BoundingBoxSchema.optional(),
19142
+ /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
19143
+ keyFrameMediaKey: string().optional(),
18309
19144
  base64: string().optional()
18310
19145
  });
18311
19146
  var MediaFileLiteSchema = object({
@@ -18345,6 +19180,48 @@ method(object({
18345
19180
  }), method(object({ plateId: string() }), _void(), {
18346
19181
  kind: "mutation",
18347
19182
  auth: "admin"
19183
+ }), method(_void(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
19184
+ kind: "mutation",
19185
+ auth: "admin"
19186
+ }), method(object({
19187
+ id: string(),
19188
+ name: string().min(1)
19189
+ }), _void(), {
19190
+ kind: "mutation",
19191
+ auth: "admin"
19192
+ }), method(object({ id: string() }), _void(), {
19193
+ kind: "mutation",
19194
+ auth: "admin"
19195
+ }), method(object({ vehicleId: string() }), array(VehicleSampleInfoSchema).readonly()), method(object({
19196
+ vehicleId: string(),
19197
+ sampleId: string()
19198
+ }), _void(), {
19199
+ kind: "mutation",
19200
+ auth: "admin"
19201
+ }), method(object({
19202
+ plateId: string(),
19203
+ vehicleId: string()
19204
+ }), _void(), {
19205
+ kind: "mutation",
19206
+ auth: "admin"
19207
+ }), method(object({ plateId: string() }), _void(), {
19208
+ kind: "mutation",
19209
+ auth: "admin"
19210
+ }), method(object({
19211
+ plateIds: array(string()).min(1),
19212
+ vehicleId: string()
19213
+ }), object({
19214
+ assigned: number().int(),
19215
+ failed: array(string()).readonly()
19216
+ }), {
19217
+ kind: "mutation",
19218
+ auth: "admin"
19219
+ }), method(object({ plateIds: array(string()).min(1) }), object({
19220
+ unassigned: number().int(),
19221
+ failed: array(string()).readonly()
19222
+ }), {
19223
+ kind: "mutation",
19224
+ auth: "admin"
18348
19225
  });
18349
19226
  var ModelFormatSchema = _enum(MODEL_FORMATS);
18350
19227
  var HwAccelBackendInputSchema = _enum([
@@ -18423,6 +19300,10 @@ var GpuInfoSchema = object({
18423
19300
  memoryMB: number().optional()
18424
19301
  });
18425
19302
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
19303
+ var CoralInfoSchema = object({
19304
+ type: literal("coral-edgetpu"),
19305
+ bus: string().optional()
19306
+ });
18426
19307
  var HardwareInfoSchema = object({
18427
19308
  platform: HardwarePlatformSchema,
18428
19309
  arch: HardwareArchSchema,
@@ -18431,7 +19312,8 @@ var HardwareInfoSchema = object({
18431
19312
  totalRAM_MB: number(),
18432
19313
  availableRAM_MB: number(),
18433
19314
  gpu: GpuInfoSchema.nullable(),
18434
- npu: NpuInfoSchema.nullable()
19315
+ npu: NpuInfoSchema.nullable(),
19316
+ coral: CoralInfoSchema.nullable().optional()
18435
19317
  });
18436
19318
  var PlatformScoreSchema = object({
18437
19319
  runtime: _enum(["node", "python"]),
@@ -20890,6 +21772,168 @@ Object.freeze({
20890
21772
  addonId: null,
20891
21773
  access: "create"
20892
21774
  },
21775
+ "llm.deleteModel": {
21776
+ capName: "llm",
21777
+ capScope: "system",
21778
+ addonId: null,
21779
+ access: "delete"
21780
+ },
21781
+ "llm.deleteProfile": {
21782
+ capName: "llm",
21783
+ capScope: "system",
21784
+ addonId: null,
21785
+ access: "delete"
21786
+ },
21787
+ "llm.generate": {
21788
+ capName: "llm",
21789
+ capScope: "system",
21790
+ addonId: null,
21791
+ access: "create"
21792
+ },
21793
+ "llm.generateVision": {
21794
+ capName: "llm",
21795
+ capScope: "system",
21796
+ addonId: null,
21797
+ access: "create"
21798
+ },
21799
+ "llm.getDefaults": {
21800
+ capName: "llm",
21801
+ capScope: "system",
21802
+ addonId: null,
21803
+ access: "view"
21804
+ },
21805
+ "llm.getRuntimeStatus": {
21806
+ capName: "llm",
21807
+ capScope: "system",
21808
+ addonId: null,
21809
+ access: "view"
21810
+ },
21811
+ "llm.getUsage": {
21812
+ capName: "llm",
21813
+ capScope: "system",
21814
+ addonId: null,
21815
+ access: "view"
21816
+ },
21817
+ "llm.installModel": {
21818
+ capName: "llm",
21819
+ capScope: "system",
21820
+ addonId: null,
21821
+ access: "create"
21822
+ },
21823
+ "llm.listModelCatalog": {
21824
+ capName: "llm",
21825
+ capScope: "system",
21826
+ addonId: null,
21827
+ access: "view"
21828
+ },
21829
+ "llm.listModels": {
21830
+ capName: "llm",
21831
+ capScope: "system",
21832
+ addonId: null,
21833
+ access: "view"
21834
+ },
21835
+ "llm.listNodeModels": {
21836
+ capName: "llm",
21837
+ capScope: "system",
21838
+ addonId: null,
21839
+ access: "view"
21840
+ },
21841
+ "llm.listProfileKinds": {
21842
+ capName: "llm",
21843
+ capScope: "system",
21844
+ addonId: null,
21845
+ access: "view"
21846
+ },
21847
+ "llm.listProfiles": {
21848
+ capName: "llm",
21849
+ capScope: "system",
21850
+ addonId: null,
21851
+ access: "view"
21852
+ },
21853
+ "llm.listRuntimeNodes": {
21854
+ capName: "llm",
21855
+ capScope: "system",
21856
+ addonId: null,
21857
+ access: "view"
21858
+ },
21859
+ "llm.setDefault": {
21860
+ capName: "llm",
21861
+ capScope: "system",
21862
+ addonId: null,
21863
+ access: "create"
21864
+ },
21865
+ "llm.startRuntime": {
21866
+ capName: "llm",
21867
+ capScope: "system",
21868
+ addonId: null,
21869
+ access: "create"
21870
+ },
21871
+ "llm.stopRuntime": {
21872
+ capName: "llm",
21873
+ capScope: "system",
21874
+ addonId: null,
21875
+ access: "create"
21876
+ },
21877
+ "llm.testProfile": {
21878
+ capName: "llm",
21879
+ capScope: "system",
21880
+ addonId: null,
21881
+ access: "create"
21882
+ },
21883
+ "llm.upsertProfile": {
21884
+ capName: "llm",
21885
+ capScope: "system",
21886
+ addonId: null,
21887
+ access: "create"
21888
+ },
21889
+ "llmRuntime.complete": {
21890
+ capName: "llm-runtime",
21891
+ capScope: "system",
21892
+ addonId: null,
21893
+ access: "create"
21894
+ },
21895
+ "llmRuntime.deleteModel": {
21896
+ capName: "llm-runtime",
21897
+ capScope: "system",
21898
+ addonId: null,
21899
+ access: "delete"
21900
+ },
21901
+ "llmRuntime.ensureStarted": {
21902
+ capName: "llm-runtime",
21903
+ capScope: "system",
21904
+ addonId: null,
21905
+ access: "create"
21906
+ },
21907
+ "llmRuntime.getDiskUsage": {
21908
+ capName: "llm-runtime",
21909
+ capScope: "system",
21910
+ addonId: null,
21911
+ access: "view"
21912
+ },
21913
+ "llmRuntime.installModel": {
21914
+ capName: "llm-runtime",
21915
+ capScope: "system",
21916
+ addonId: null,
21917
+ access: "create"
21918
+ },
21919
+ "llmRuntime.listLocalModels": {
21920
+ capName: "llm-runtime",
21921
+ capScope: "system",
21922
+ addonId: null,
21923
+ access: "view"
21924
+ },
21925
+ "llmRuntime.status": {
21926
+ capName: "llm-runtime",
21927
+ capScope: "system",
21928
+ addonId: null,
21929
+ access: "view"
21930
+ },
21931
+ "llmRuntime.stop": {
21932
+ capName: "llm-runtime",
21933
+ capScope: "system",
21934
+ addonId: null,
21935
+ access: "create"
21936
+ },
20893
21937
  "localNetwork.getAllowedAddresses": {
20894
21938
  capName: "local-network",
20895
21939
  capScope: "system",
@@ -21520,6 +22564,12 @@ Object.freeze({
21520
22564
  addonId: null,
21521
22565
  access: "delete"
21522
22566
  },
22567
+ "pipelineAnalytics.deleteTracks": {
22568
+ capName: "pipeline-analytics",
22569
+ capScope: "device",
22570
+ addonId: null,
22571
+ access: "delete"
22572
+ },
21523
22573
  "pipelineAnalytics.getActiveTracks": {
21524
22574
  capName: "pipeline-analytics",
21525
22575
  capScope: "device",
@@ -21586,12 +22636,24 @@ Object.freeze({
21586
22636
  addonId: null,
21587
22637
  access: "create"
21588
22638
  },
22639
+ "pipelineAnalytics.pruneTracksBefore": {
22640
+ capName: "pipeline-analytics",
22641
+ capScope: "device",
22642
+ addonId: null,
22643
+ access: "create"
22644
+ },
21589
22645
  "pipelineAnalytics.searchObjectEvents": {
21590
22646
  capName: "pipeline-analytics",
21591
22647
  capScope: "device",
21592
22648
  addonId: null,
21593
22649
  access: "view"
21594
22650
  },
22651
+ "pipelineAnalytics.wipeAllAnalytics": {
22652
+ capName: "pipeline-analytics",
22653
+ capScope: "device",
22654
+ addonId: null,
22655
+ access: "delete"
22656
+ },
21595
22657
  "pipelineExecutor.cacheFrameInPool": {
21596
22658
  capName: "pipeline-executor",
21597
22659
  capScope: "system",
@@ -22090,18 +23152,48 @@ Object.freeze({
22090
23152
  addonId: null,
22091
23153
  access: "create"
22092
23154
  },
23155
+ "pipelineRunner.runDetailSubtree": {
23156
+ capName: "pipeline-runner",
23157
+ capScope: "system",
23158
+ addonId: null,
23159
+ access: "create"
23160
+ },
23161
+ "plateGallery.assignPlate": {
23162
+ capName: "plate-gallery",
23163
+ capScope: "system",
23164
+ addonId: null,
23165
+ access: "create"
23166
+ },
23167
+ "plateGallery.assignPlates": {
23168
+ capName: "plate-gallery",
23169
+ capScope: "system",
23170
+ addonId: null,
23171
+ access: "create"
23172
+ },
22093
23173
  "plateGallery.correctPlateText": {
22094
23174
  capName: "plate-gallery",
22095
23175
  capScope: "system",
22096
23176
  addonId: null,
22097
23177
  access: "create"
22098
23178
  },
23179
+ "plateGallery.createVehicle": {
23180
+ capName: "plate-gallery",
23181
+ capScope: "system",
23182
+ addonId: null,
23183
+ access: "create"
23184
+ },
22099
23185
  "plateGallery.deletePlate": {
22100
23186
  capName: "plate-gallery",
22101
23187
  capScope: "system",
22102
23188
  addonId: null,
22103
23189
  access: "delete"
22104
23190
  },
23191
+ "plateGallery.deleteVehicle": {
23192
+ capName: "plate-gallery",
23193
+ capScope: "system",
23194
+ addonId: null,
23195
+ access: "delete"
23196
+ },
22105
23197
  "plateGallery.getPlateByTrack": {
22106
23198
  capName: "plate-gallery",
22107
23199
  capScope: "system",
@@ -22120,6 +23212,30 @@ Object.freeze({
22120
23212
  addonId: null,
22121
23213
  access: "view"
22122
23214
  },
23215
+ "plateGallery.listVehicles": {
23216
+ capName: "plate-gallery",
23217
+ capScope: "system",
23218
+ addonId: null,
23219
+ access: "view"
23220
+ },
23221
+ "plateGallery.listVehicleSamples": {
23222
+ capName: "plate-gallery",
23223
+ capScope: "system",
23224
+ addonId: null,
23225
+ access: "view"
23226
+ },
23227
+ "plateGallery.removeVehicleSample": {
23228
+ capName: "plate-gallery",
23229
+ capScope: "system",
23230
+ addonId: null,
23231
+ access: "delete"
23232
+ },
23233
+ "plateGallery.renameVehicle": {
23234
+ capName: "plate-gallery",
23235
+ capScope: "system",
23236
+ addonId: null,
23237
+ access: "create"
23238
+ },
22123
23239
  "plateGallery.searchPlates": {
22124
23240
  capName: "plate-gallery",
22125
23241
  capScope: "system",
@@ -22132,6 +23248,18 @@ Object.freeze({
22132
23248
  addonId: null,
22133
23249
  access: "view"
22134
23250
  },
23251
+ "plateGallery.unassignPlate": {
23252
+ capName: "plate-gallery",
23253
+ capScope: "system",
23254
+ addonId: null,
23255
+ access: "create"
23256
+ },
23257
+ "plateGallery.unassignPlates": {
23258
+ capName: "plate-gallery",
23259
+ capScope: "system",
23260
+ addonId: null,
23261
+ access: "create"
23262
+ },
22135
23263
  "platformProbe.getCapabilities": {
22136
23264
  capName: "platform-probe",
22137
23265
  capScope: "system",
@@ -22324,6 +23452,48 @@ Object.freeze({
22324
23452
  addonId: null,
22325
23453
  access: "create"
22326
23454
  },
23455
+ "sceneMonitor.captureReference": {
23456
+ capName: "scene-monitor",
23457
+ capScope: "device",
23458
+ addonId: null,
23459
+ access: "create"
23460
+ },
23461
+ "sceneMonitor.createScene": {
23462
+ capName: "scene-monitor",
23463
+ capScope: "device",
23464
+ addonId: null,
23465
+ access: "create"
23466
+ },
23467
+ "sceneMonitor.deleteReference": {
23468
+ capName: "scene-monitor",
23469
+ capScope: "device",
23470
+ addonId: null,
23471
+ access: "delete"
23472
+ },
23473
+ "sceneMonitor.deleteScene": {
23474
+ capName: "scene-monitor",
23475
+ capScope: "device",
23476
+ addonId: null,
23477
+ access: "delete"
23478
+ },
23479
+ "sceneMonitor.listScenes": {
23480
+ capName: "scene-monitor",
23481
+ capScope: "device",
23482
+ addonId: null,
23483
+ access: "view"
23484
+ },
23485
+ "sceneMonitor.recheckNow": {
23486
+ capName: "scene-monitor",
23487
+ capScope: "device",
23488
+ addonId: null,
23489
+ access: "create"
23490
+ },
23491
+ "sceneMonitor.updateScene": {
23492
+ capName: "scene-monitor",
23493
+ capScope: "device",
23494
+ addonId: null,
23495
+ access: "create"
23496
+ },
22327
23497
  "scriptRunner.run": {
22328
23498
  capName: "script-runner",
22329
23499
  capScope: "device",
@@ -23408,6 +24578,7 @@ Object.freeze({
23408
24578
  Object.freeze({
23409
24579
  "broker": "broker",
23410
24580
  "device-export": "device-export",
24581
+ "llm": "ai",
23411
24582
  "mesh-network": "mesh",
23412
24583
  "mqtt-broker": "broker",
23413
24584
  "network-access": "ingress",
@@ -23439,6 +24610,143 @@ object({
23439
24610
  bootAttempts: number(),
23440
24611
  schemaVersion: literal(1)
23441
24612
  });
24613
+ var NOTIFIER_ICONS = {
24614
+ telegram: {
24615
+ contentType: "image/svg+xml",
24616
+ 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>"
24617
+ },
24618
+ discord: {
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=\"#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>"
24621
+ },
24622
+ whatsapp: {
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=\"#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>"
24625
+ },
24626
+ ntfy: {
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=\"#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>"
24629
+ },
24630
+ pushover: {
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=\"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>"
24633
+ },
24634
+ webhook: {
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=\"#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>"
24637
+ },
24638
+ gotify: {
24639
+ contentType: "image/svg+xml",
24640
+ 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>"
24641
+ },
24642
+ zentik: {
24643
+ contentType: "image/png",
24644
+ body: Uint8Array.from(Buffer.from("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", "base64"))
24645
+ }
24646
+ };
24647
+ //#endregion
24648
+ //#region src/icon-routes.ts
24649
+ /**
24650
+ * Serves the bundled per-kind brand icons (`icons-data.ts`) over the addon's
24651
+ * own HTTP surface via the `addon-routes` cap. One public GET route per kind:
24652
+ *
24653
+ * GET /addon/notifiers/icons/<kind> → the bundled brand icon
24654
+ *
24655
+ * Each route sets the icon's OWN content-type (`image/svg+xml` for the vector
24656
+ * icons, `image/png` for the raster zentik logo) so a browser `<img src>`
24657
+ * renders it — an SVG that wraps a raster `data:` URI is blocked by the strict
24658
+ * `<img>` SVG mode, so zentik ships as its real PNG bytes instead.
24659
+ *
24660
+ * The bytes ship INSIDE the addon — no external CDN / hotlink — so the icons are
24661
+ * self-contained and CSP-safe. `listTargetKinds` stamps each kind with the
24662
+ * matching `iconUrl` (see `iconUrlFor`), letting the admin UI render
24663
+ * `<img src=iconUrl>` instead of a text label.
24664
+ *
24665
+ * We register a STATIC route per kind rather than one `/:kind` param route: the
24666
+ * forked-addon route bridge re-matches the pattern against itself, which would
24667
+ * clobber a `:kind` path param with the literal token `':kind'`. Static paths
24668
+ * carry no param, so they route correctly across the process boundary.
24669
+ *
24670
+ * ── Why the addon pre-encodes and owns `content-encoding` ────────────────────
24671
+ * The hub serves an addon route by piping the response THROUGH its global
24672
+ * `@fastify/compress`. For a forked addon, our reply body crosses the UDS route
24673
+ * bridge as opaque bytes and the hub then re-encodes them — and on that bridged
24674
+ * path the compressor emits an EMPTY brotli stream for any compressible body
24675
+ * over its 1 KiB threshold (`content-encoding: br` + `content-length: 0`), so a
24676
+ * browser `<img>` decodes nothing and shows a broken icon. Small icons (< 1 KiB)
24677
+ * and the raster PNG (a non-compressible type) slip under the compressor and
24678
+ * render, which is why only the larger SVGs broke.
24679
+ *
24680
+ * The fix is to make the compressor SKIP us: it leaves a response untouched when
24681
+ * `content-encoding` is already set to a non-identity coding. So each route
24682
+ * negotiates the request's `Accept-Encoding`, serves a pre-computed br/gzip/
24683
+ * deflate variant (or identity when the client accepts none), and stamps the
24684
+ * matching `content-encoding` itself. Bytes we own, correctly framed, reach the
24685
+ * browser intact. Mirrors the `compress: false` treatment the hub's own binary
24686
+ * pull route already uses for the same class of bug.
24687
+ */
24688
+ /** The addon id — also the `/addon/<id>/…` route prefix. Mirrors `NOTIFIERS_ADDON_ID`. */
24689
+ var NOTIFIERS_ROUTE_ID = "notifiers";
24690
+ /** One day; icons are immutable per addon version. */
24691
+ var CACHE_CONTROL = "public, max-age=86400, immutable";
24692
+ var ICON_KINDS = new Set(Object.keys(NOTIFIER_ICONS));
24693
+ /** `true` when a bundled brand icon exists for `kind`. */
24694
+ function hasIcon(kind) {
24695
+ return ICON_KINDS.has(kind);
24696
+ }
24697
+ /**
24698
+ * The stable per-kind icon URL, or `undefined` when no icon is bundled. Stamped
24699
+ * onto each descriptor by `listTargetKinds`. Root-relative so it resolves
24700
+ * against whatever origin the admin UI is served from.
24701
+ */
24702
+ function iconUrlFor(kind) {
24703
+ return hasIcon(kind) ? `/addon/${NOTIFIERS_ROUTE_ID}/icons/${kind}` : void 0;
24704
+ }
24705
+ /** Pre-encode one bundled icon into every content coding. */
24706
+ function encodeIcon(icon) {
24707
+ const identity = typeof icon.body === "string" ? Buffer.from(icon.body, "utf8") : Buffer.from(icon.body);
24708
+ return {
24709
+ contentType: icon.contentType,
24710
+ identity,
24711
+ br: brotliCompressSync(identity),
24712
+ gzip: gzipSync(identity),
24713
+ deflate: deflateSync(identity)
24714
+ };
24715
+ }
24716
+ /** Pick the best coding the client accepts (br › gzip › deflate), or `null` for
24717
+ * identity when the header names none of them. Token-matched so `gzipx` or a
24718
+ * `q=0` refusal don't false-positive. */
24719
+ function negotiateEncoding(acceptEncoding) {
24720
+ const tokens = acceptEncoding.toLowerCase().split(",").map((part) => part.trim().split(";")[0]?.trim());
24721
+ for (const coding of [
24722
+ "br",
24723
+ "gzip",
24724
+ "deflate"
24725
+ ]) if (tokens.includes(coding)) return coding;
24726
+ return null;
24727
+ }
24728
+ /** One static GET route serving the bundled icon for a single kind, with the
24729
+ * icon's own content-type (svg vs png) and a client-negotiated content-coding
24730
+ * the addon owns end-to-end (see the file header for why). */
24731
+ function iconRoute(kind) {
24732
+ const encoded = encodeIcon(NOTIFIER_ICONS[kind]);
24733
+ return {
24734
+ method: "GET",
24735
+ path: `/icons/${kind}`,
24736
+ access: "public",
24737
+ description: `Official brand icon (${encoded.contentType}) for the '${kind}' notifier kind.`,
24738
+ handler: async (request, reply) => {
24739
+ const coding = negotiateEncoding(String(request.headers["accept-encoding"] ?? ""));
24740
+ reply.code(200).type(encoded.contentType).header("cache-control", CACHE_CONTROL).header("vary", "accept-encoding");
24741
+ if (coding !== null) reply.header("content-encoding", coding);
24742
+ reply.send(coding !== null ? encoded[coding] : encoded.identity);
24743
+ }
24744
+ };
24745
+ }
24746
+ /** Build the `addon-routes` provider that serves the bundled icons. */
24747
+ function createIconRouteProvider() {
24748
+ return buildAddonRouteProvider(NOTIFIERS_ROUTE_ID, Object.keys(NOTIFIER_ICONS).map(iconRoute));
24749
+ }
23442
24750
  //#endregion
23443
24751
  //#region src/adapters/discord.ts
23444
24752
  /** Discord message `content` hard limit. */
@@ -25879,11 +27187,19 @@ function createNotificationOutputProvider(deps) {
25879
27187
  renderedAs: prepared.renderedAs
25880
27188
  };
25881
27189
  }
27190
+ const iconUrlFor = deps.iconUrlFor;
25882
27191
  return {
25883
- listTargetKinds: async () => deps.adapters.map((a) => ({
25884
- ...a.descriptor,
25885
- addonId
25886
- })),
27192
+ listTargetKinds: async () => deps.adapters.map((a) => {
27193
+ const descriptor = {
27194
+ ...a.descriptor,
27195
+ addonId
27196
+ };
27197
+ const iconUrl = iconUrlFor?.(a.descriptor.kind);
27198
+ return iconUrl !== void 0 ? {
27199
+ ...descriptor,
27200
+ iconUrl
27201
+ } : descriptor;
27202
+ }),
25887
27203
  listTargets: async () => {
25888
27204
  return (await store.list()).map((target) => {
25889
27205
  const adapter = adapterByKind.get(target.kind);
@@ -25980,6 +27296,12 @@ function createApiSettingsStorePort(api) {
25980
27296
  collection,
25981
27297
  key: id
25982
27298
  });
27299
+ },
27300
+ declareCollection: async (collection, columns) => {
27301
+ await api.settingsStore.declareCollection.mutate({
27302
+ collection,
27303
+ columns: columns.map((c) => ({ ...c }))
27304
+ });
25983
27305
  }
25984
27306
  };
25985
27307
  }
@@ -26010,6 +27332,9 @@ function createMemorySettingsStorePort() {
26010
27332
  },
26011
27333
  remove: async (collection, id) => {
26012
27334
  bucket(collection).delete(id);
27335
+ },
27336
+ declareCollection: async (collection) => {
27337
+ bucket(collection);
26013
27338
  }
26014
27339
  };
26015
27340
  }
@@ -26030,6 +27355,13 @@ function createMemorySettingsStorePort() {
26030
27355
  */
26031
27356
  /** The default collection name (see `settings-store` addonId-prefix note). */
26032
27357
  var TARGETS_COLLECTION = "notification-targets";
27358
+ /** KV JSON-blob shape — a single `data` column routes the row through the
27359
+ * settings backend's canonical key/value path (id TEXT PK, data TEXT). */
27360
+ var KV_BLOB_COLUMNS = [{
27361
+ name: "data",
27362
+ type: "TEXT",
27363
+ notNull: true
27364
+ }];
26033
27365
  function rowToTarget(row) {
26034
27366
  const candidate = {
26035
27367
  id: row.id,
@@ -26054,6 +27386,12 @@ var TargetStore = class {
26054
27386
  this.store = store;
26055
27387
  this.collection = collection;
26056
27388
  }
27389
+ /** Declare the backing collection before any read/write. The SQLite settings
27390
+ * backend rejects undeclared collections (fail-fast), so this MUST run before
27391
+ * `list`/`getById`/`upsert`/`delete` — mirrors addon-ai's ProfileStore.init. */
27392
+ async init() {
27393
+ await this.store.declareCollection(this.collection, KV_BLOB_COLUMNS);
27394
+ }
26057
27395
  async list() {
26058
27396
  const rows = await this.store.query(this.collection);
26059
27397
  const targets = [];
@@ -26125,15 +27463,22 @@ var NotifiersAddon = class extends BaseAddon {
26125
27463
  const api = this.ctx.api;
26126
27464
  const port = api ? createApiSettingsStorePort(api) : createMemorySettingsStorePort();
26127
27465
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
27466
+ const store = new TargetStore(port);
27467
+ await store.init();
26128
27468
  const provider = createNotificationOutputProvider({
26129
- store: new TargetStore(port),
27469
+ store,
26130
27470
  adapters: ADAPTERS,
26131
- logger: this.ctx.logger.child("notification-output")
27471
+ logger: this.ctx.logger.child("notification-output"),
27472
+ iconUrlFor
26132
27473
  });
27474
+ const iconRouteProvider = createIconRouteProvider();
26133
27475
  this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: ADAPTERS.length } });
26134
27476
  return [{
26135
27477
  capability: notificationOutputCapability,
26136
27478
  provider
27479
+ }, {
27480
+ capability: addonRoutesCapability,
27481
+ provider: iconRouteProvider
26137
27482
  }];
26138
27483
  }
26139
27484
  async onShutdown() {