@camstack/addon-provider-homeassistant 1.1.24 → 1.1.26

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 +1321 -47
  2. package/dist/addon.mjs +1321 -47
  3. package/package.json +8 -1
package/dist/addon.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
2
3
  //#region ../../node_modules/zod/v4/core/core.js
3
4
  var _a$1;
4
5
  function $constructor(name, initializer, params) {
@@ -4287,6 +4288,14 @@ function object(shape, params) {
4287
4288
  ...normalizeParams(params)
4288
4289
  });
4289
4290
  }
4291
+ function looseObject(shape, params) {
4292
+ return new ZodObject({
4293
+ type: "object",
4294
+ shape,
4295
+ catchall: unknown(),
4296
+ ...normalizeParams(params)
4297
+ });
4298
+ }
4290
4299
  var ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
4291
4300
  $ZodUnion.init(inst, def);
4292
4301
  ZodType.init(inst, def);
@@ -4628,7 +4637,7 @@ function _instanceof(cls, params = {}) {
4628
4637
  return inst;
4629
4638
  }
4630
4639
  //#endregion
4631
- //#region ../types/dist/sleep-Baang_XW.mjs
4640
+ //#region ../types/dist/sleep-_sv7WKkq.mjs
4632
4641
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4633
4642
  EventCategory["SystemBoot"] = "system.boot";
4634
4643
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5038,10 +5047,51 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5038
5047
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
5039
5048
  EventCategory["EnrichmentSceneStateChanged"] = "enrichment.scene.state-changed";
5040
5049
  EventCategory["EnrichmentActivitySummary"] = "enrichment.activity.summary";
5050
+ /**
5051
+ * Unified track-lifecycle event: ONE category carrying `phase:
5052
+ * 'start' | 'update' | 'end'` with a rich, typed
5053
+ * `PipelineAnalyticsTrackLifecyclePayload`. Supersedes the thin
5054
+ * `PipelineAnalyticsTrackStarted` / `PipelineAnalyticsTrackEnded`
5055
+ * pair — a consumer subscribes once and branches on `phase`.
5056
+ */
5057
+ EventCategory["PipelineAnalyticsTrackLifecycle"] = "pipeline-analytics.track-lifecycle";
5058
+ /**
5059
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5060
+ * (`phase:'start'`). Kept as a soft alias — no known subscribers.
5061
+ */
5041
5062
  EventCategory["PipelineAnalyticsTrackStarted"] = "pipeline-analytics.track-started";
5063
+ /**
5064
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5065
+ * (`phase:'end'`). Kept as a soft alias — no known subscribers.
5066
+ */
5042
5067
  EventCategory["PipelineAnalyticsTrackEnded"] = "pipeline-analytics.track-ended";
5043
5068
  EventCategory["PipelineAnalyticsDetectionEvent"] = "pipeline-analytics.detection-event";
5044
5069
  EventCategory["PipelineAnalyticsFrameTracked"] = "pipeline-analytics.frame-tracked";
5070
+ /**
5071
+ * Fired by `addon-post-analysis` when a parked (stationary) object appears
5072
+ * (a track was promoted to a stationary-registry entry) or departs (the
5073
+ * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
5074
+ * the dedicated "Stationary" section — never the live event feed. Payload:
5075
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
5076
+ */
5077
+ EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
5078
+ /**
5079
+ * Fired by `addon-post-analysis` whenever a gallery face row changes:
5080
+ * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
5081
+ * `'unassigned'` its identity link changed, `'deleted'` the row was
5082
+ * removed. Telemetry semantics (D8): a lost event only delays a refresh,
5083
+ * never a correctness issue. Payload `PipelineAnalyticsFaceGalleryChangedPayload`.
5084
+ */
5085
+ EventCategory["PipelineAnalyticsFaceGalleryChanged"] = "pipeline-analytics.face-gallery-changed";
5086
+ /**
5087
+ * Fired by `addon-post-analysis` whenever a gallery plate row changes:
5088
+ * `kind:'buffered'` a new read was persisted (`PlateRecognizer.onTrackEnd`),
5089
+ * `'assigned'` / `'unassigned'` its vehicle link changed
5090
+ * (`PlateGalleryProvider`), `'deleted'` the row was removed. Telemetry
5091
+ * semantics (D8): a lost event only delays a refresh, never a correctness
5092
+ * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
5093
+ */
5094
+ EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
5045
5095
  EventCategory["FrigateLiveEvent"] = "frigate.live-event";
5046
5096
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
5047
5097
  /**
@@ -6886,7 +6936,8 @@ var MODEL_FORMATS = [
6886
6936
  "coreml",
6887
6937
  "openvino",
6888
6938
  "tflite",
6889
- "pt"
6939
+ "pt",
6940
+ "gguf"
6890
6941
  ];
6891
6942
  /**
6892
6943
  * Multi-file format payload.
@@ -6930,7 +6981,8 @@ var ModelFormatsSchema = object({
6930
6981
  coreml: ModelFormatEntrySchema.optional(),
6931
6982
  openvino: ModelFormatEntrySchema.optional(),
6932
6983
  tflite: ModelFormatEntrySchema.optional(),
6933
- pt: ModelFormatEntrySchema.optional()
6984
+ pt: ModelFormatEntrySchema.optional(),
6985
+ gguf: ModelFormatEntrySchema.optional()
6934
6986
  });
6935
6987
  /**
6936
6988
  * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
@@ -6971,6 +7023,15 @@ var ModelCatalogEntrySchema = object({
6971
7023
  width: number(),
6972
7024
  height: number()
6973
7025
  }),
7026
+ /**
7027
+ * Channel count of the model input tensor. Omit ⇒ 3 (RGB), the default for
7028
+ * every detector / classifier / embedder. Set to 1 for a grayscale CTC text
7029
+ * recognizer (EasyOCR VGG plate-OCR: input `[N,1,H,W]`) so the preprocess
7030
+ * feeds a single-channel, EasyOCR-normalized tensor instead of the default
7031
+ * 3-channel RGB one. Threaded through `PoolModelConfig.inputChannels` to the
7032
+ * Python inference pool.
7033
+ */
7034
+ inputChannels: number().int().positive().optional(),
6974
7035
  labels: array(LabelDefinitionSchema).readonly(),
6975
7036
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
6976
7037
  inputNormalization: _enum([
@@ -6980,6 +7041,16 @@ var ModelCatalogEntrySchema = object({
6980
7041
  ]).optional(),
6981
7042
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
6982
7043
  /**
7044
+ * Per-MODEL postprocessor override. Absent ⇒ the step's own
7045
+ * `StepDefinition.postprocessor` applies (the normal case — every model in a
7046
+ * step shares its decode). Set it when a step hosts models with DIFFERENT raw
7047
+ * output layouts under one slot: e.g. object-detection is `'yolo'` by default,
7048
+ * but a Coral SSD MobileNet build emits the `TFLite_Detection_PostProcess`
7049
+ * 4-tensor layout and needs `'ssd'`. Threaded into `PoolModelConfig.postprocessor`
7050
+ * by the engine factory (`modelEntry.postprocessor ?? def.postprocessor`).
7051
+ */
7052
+ postprocessor: custom().optional(),
7053
+ /**
6983
7054
  * When true, the executor produces a landmark-aligned crop (similarity warp
6984
7055
  * onto the canonical template) before this step runs, instead of a plain
6985
7056
  * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
@@ -7069,6 +7140,123 @@ var ConvertResultSchema = object({
7069
7140
  })).readonly()
7070
7141
  });
7071
7142
  /**
7143
+ * Build an `IAddonRouteProvider` from a list of routes. Implements
7144
+ * both the operator-facing `getRoutes` (returning route descriptors
7145
+ * minus the handlers, which can't cross JSON) and the framework-
7146
+ * private `invoke` method that the hub calls when this provider lives
7147
+ * in a forked worker.
7148
+ *
7149
+ * Co-located addons use the returned `getRoutes` directly because
7150
+ * their handlers don't need to cross any wire. The `invoke` method
7151
+ * is present anyway so the bridge code on the hub is uniform — it
7152
+ * doesn't need to switch on "local vs remote provider" at the call
7153
+ * site.
7154
+ *
7155
+ * Example:
7156
+ * const routes: IAddonHttpRoute[] = [
7157
+ * { method: 'GET', path: '/start', access: 'public', handler: this.handleStart },
7158
+ * ]
7159
+ * return [
7160
+ * {
7161
+ * capability: addonRoutesCapability,
7162
+ * provider: buildAddonRouteProvider('auth-oidc', routes),
7163
+ * },
7164
+ * ]
7165
+ */
7166
+ function buildAddonRouteProvider(id, routes) {
7167
+ return {
7168
+ id,
7169
+ getRoutes: () => routes,
7170
+ invoke: async (input) => {
7171
+ const match = matchRoute(routes, input.method, input.path);
7172
+ if (!match) return {
7173
+ status: 404,
7174
+ headers: {},
7175
+ redirectUrl: null,
7176
+ body: { error: `No route matches ${input.method} ${input.path}` }
7177
+ };
7178
+ const envelope = {
7179
+ status: 200,
7180
+ headers: {},
7181
+ redirectUrl: null
7182
+ };
7183
+ const reply = buildCapturingReply(envelope);
7184
+ const request = {
7185
+ params: {
7186
+ ...input.params,
7187
+ ...match.params
7188
+ },
7189
+ query: input.query,
7190
+ body: input.body,
7191
+ headers: input.headers,
7192
+ ...input.user ? { user: input.user } : {},
7193
+ ...input.scopedToken !== void 0 ? { scopedToken: input.scopedToken } : {}
7194
+ };
7195
+ await match.route.handler(request, reply);
7196
+ return envelope;
7197
+ }
7198
+ };
7199
+ }
7200
+ /**
7201
+ * Pattern matcher: same semantics as `AddonRouteRegistry.matchRoute`
7202
+ * but operating on a flat list and bypassing the `/addon/<id>/` prefix
7203
+ * — the bridge sends the post-prefix path directly so we don't need
7204
+ * to round-trip it through normalization.
7205
+ */
7206
+ function matchRoute(routes, method, path) {
7207
+ const normalizedMethod = method.toUpperCase();
7208
+ for (const route of routes) {
7209
+ if (route.method !== normalizedMethod) continue;
7210
+ const params = matchPath(route.path, path);
7211
+ if (params !== null) return {
7212
+ route,
7213
+ params
7214
+ };
7215
+ }
7216
+ return null;
7217
+ }
7218
+ function matchPath(pattern, p) {
7219
+ const patternParts = pattern.split("/").filter(Boolean);
7220
+ const pathParts = p.split("/").filter(Boolean);
7221
+ if (patternParts.length !== pathParts.length) return null;
7222
+ const params = {};
7223
+ for (let i = 0; i < patternParts.length; i++) {
7224
+ const a = patternParts[i];
7225
+ const b = pathParts[i];
7226
+ if (a.startsWith(":")) params[a.slice(1)] = b;
7227
+ else if (a !== b) return null;
7228
+ }
7229
+ return params;
7230
+ }
7231
+ function buildCapturingReply(envelope) {
7232
+ const wrapper = {
7233
+ status(code) {
7234
+ envelope.status = code;
7235
+ return wrapper;
7236
+ },
7237
+ code(code) {
7238
+ envelope.status = code;
7239
+ return wrapper;
7240
+ },
7241
+ send(data) {
7242
+ envelope.body = data;
7243
+ },
7244
+ redirect(url) {
7245
+ envelope.redirectUrl = url;
7246
+ if (envelope.status === 200) envelope.status = 302;
7247
+ },
7248
+ header(name, value) {
7249
+ envelope.headers[name.toLowerCase()] = value;
7250
+ return wrapper;
7251
+ },
7252
+ type(mime) {
7253
+ envelope.contentType = mime;
7254
+ return wrapper;
7255
+ }
7256
+ };
7257
+ return wrapper;
7258
+ }
7259
+ /**
7072
7260
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
7073
7261
  * Named `RecordingWeekday` to avoid collision with the string-union
7074
7262
  * `Weekday` exported from `interfaces/timezones.ts`.
@@ -11924,7 +12112,8 @@ var ModelFormatSchema$1 = _enum([
11924
12112
  "coreml",
11925
12113
  "openvino",
11926
12114
  "tflite",
11927
- "pt"
12115
+ "pt",
12116
+ "gguf"
11928
12117
  ]);
11929
12118
  var PipelineSlotSchema = _enum([
11930
12119
  "detector",
@@ -12018,7 +12207,8 @@ var EngineProvisioningSchema = object({
12018
12207
  runtimeId: _enum([
12019
12208
  "onnx",
12020
12209
  "openvino",
12021
- "coreml"
12210
+ "coreml",
12211
+ "edgetpu"
12022
12212
  ]).nullable(),
12023
12213
  device: string().nullable(),
12024
12214
  state: _enum([
@@ -13659,6 +13849,167 @@ var ptzAutotrackCapability = {
13659
13849
  runtimeState: PtzAutotrackRuntimeStateSchema
13660
13850
  };
13661
13851
  /**
13852
+ * scene-monitor — device-scoped reference-region state cap. An operator marks
13853
+ * a rect ROI on a camera frame and names one or more states; the engine
13854
+ * (pipeline-analytics, designated post-processing node) reports which state is
13855
+ * active on an ongoing basis. Two operator-selectable check modes share every-
13856
+ * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
13857
+ * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
13858
+ *
13859
+ * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) — the framework
13860
+ * derives the device-detail contribution; the provider carries NO hand-written
13861
+ * settings-contribution methods. `status.kind:'push'` — the engine pushes on
13862
+ * every hysteresis flip / availability change; consumers never poll.
13863
+ */
13864
+ /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
13865
+ * be added without a wire break (matching falls back to any-condition refs). */
13866
+ var SceneConditionSchema = string();
13867
+ /** One captured reference — condition-tagged, model-version-gated. `embedding`
13868
+ * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
13869
+ var SceneReferenceSchema = object({
13870
+ embedding: array(number()),
13871
+ modelId: string(),
13872
+ condition: SceneConditionSchema,
13873
+ capturedAt: number(),
13874
+ thumbnailMediaId: string().optional()
13875
+ });
13876
+ var SceneMonitorStateSchema = object({
13877
+ id: string(),
13878
+ label: string(),
13879
+ references: array(SceneReferenceSchema),
13880
+ textPrompts: array(string()).optional(),
13881
+ referenceCount: number(),
13882
+ currentlyMatched: boolean()
13883
+ });
13884
+ /** Per-mode comparator params in a discriminated union so an `llm` scene never
13885
+ * carries similarity knobs and vice-versa. */
13886
+ var SceneCheckSchema = discriminatedUnion("mode", [object({
13887
+ mode: literal("similarity"),
13888
+ threshold: number().min(0).max(1),
13889
+ hysteresisCount: number().int().positive()
13890
+ }), object({
13891
+ mode: literal("llm"),
13892
+ prompt: string(),
13893
+ profileId: string().optional(),
13894
+ hysteresisCount: number().int().positive()
13895
+ })]);
13896
+ var SceneMonitorSchema = object({
13897
+ id: string(),
13898
+ label: string(),
13899
+ roi: MaskRectShapeSchema,
13900
+ enabled: boolean(),
13901
+ triggerMode: _enum([
13902
+ "periodic",
13903
+ "on-motion",
13904
+ "both"
13905
+ ]),
13906
+ checkIntervalSec: number().optional(),
13907
+ check: SceneCheckSchema,
13908
+ states: array(SceneMonitorStateSchema),
13909
+ currentStateId: string().nullable(),
13910
+ lastCheckedAt: number().nullable(),
13911
+ lastConfidence: number().nullable(),
13912
+ currentCondition: SceneConditionSchema.nullable(),
13913
+ availability: _enum(["ok", "unavailable"]),
13914
+ unavailableReason: string().nullable()
13915
+ });
13916
+ var SceneMonitorStatusSchema = object({
13917
+ monitors: array(SceneMonitorSchema),
13918
+ lastFetchedAt: number()
13919
+ });
13920
+ var sceneMonitorCapability = {
13921
+ name: "scene-monitor",
13922
+ scope: "device",
13923
+ mode: "singleton",
13924
+ kind: "wrapper",
13925
+ defaultActive: true,
13926
+ deviceTypes: [DeviceType.Camera],
13927
+ deviceConfig: { ui: {
13928
+ kind: "widget",
13929
+ widgetId: "host/scene-monitor-editor",
13930
+ tab: "scenes",
13931
+ label: "Scenes"
13932
+ } },
13933
+ methods: {
13934
+ listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
13935
+ createScene: method(object({
13936
+ deviceId: number(),
13937
+ label: string(),
13938
+ roi: MaskRectShapeSchema,
13939
+ check: SceneCheckSchema,
13940
+ triggerMode: _enum([
13941
+ "periodic",
13942
+ "on-motion",
13943
+ "both"
13944
+ ]).optional(),
13945
+ checkIntervalSec: number().optional()
13946
+ }), SceneMonitorSchema, {
13947
+ kind: "mutation",
13948
+ auth: "admin"
13949
+ }),
13950
+ updateScene: method(object({
13951
+ deviceId: number(),
13952
+ monitorId: string(),
13953
+ patch: object({
13954
+ label: string().optional(),
13955
+ roi: MaskRectShapeSchema.optional(),
13956
+ enabled: boolean().optional(),
13957
+ triggerMode: _enum([
13958
+ "periodic",
13959
+ "on-motion",
13960
+ "both"
13961
+ ]).optional(),
13962
+ checkIntervalSec: number().optional(),
13963
+ check: SceneCheckSchema.optional()
13964
+ })
13965
+ }), _void(), {
13966
+ kind: "mutation",
13967
+ auth: "admin"
13968
+ }),
13969
+ deleteScene: method(object({
13970
+ deviceId: number(),
13971
+ monitorId: string()
13972
+ }), _void(), {
13973
+ kind: "mutation",
13974
+ auth: "admin"
13975
+ }),
13976
+ captureReference: method(object({
13977
+ deviceId: number(),
13978
+ monitorId: string(),
13979
+ stateId: string().optional(),
13980
+ label: string().optional(),
13981
+ condition: SceneConditionSchema.optional()
13982
+ }), object({
13983
+ stateId: string(),
13984
+ referenceCount: number()
13985
+ }), {
13986
+ kind: "mutation",
13987
+ auth: "admin"
13988
+ }),
13989
+ deleteReference: method(object({
13990
+ deviceId: number(),
13991
+ monitorId: string(),
13992
+ stateId: string(),
13993
+ index: number()
13994
+ }), _void(), {
13995
+ kind: "mutation",
13996
+ auth: "admin"
13997
+ }),
13998
+ recheckNow: method(object({
13999
+ deviceId: number(),
14000
+ monitorId: string()
14001
+ }), _void(), {
14002
+ kind: "mutation",
14003
+ auth: "admin"
14004
+ })
14005
+ },
14006
+ status: {
14007
+ schema: SceneMonitorStatusSchema,
14008
+ kind: "push"
14009
+ },
14010
+ runtimeState: SceneMonitorStatusSchema
14011
+ };
14012
+ /**
13662
14013
  * Script-runner cap. Models HA `script.*` entities on
13663
14014
  * `DeviceType.Script`. A Script is a pre-recorded action sequence
13664
14015
  * that can be invoked imperatively — optionally with a variables
@@ -14390,6 +14741,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
14390
14741
  * with the per-track detail panel and live overlay. */
14391
14742
  trackIds: array(string()).readonly()
14392
14743
  });
14744
+ /**
14745
+ * A parked ("stationary") object surfaced alongside occupancy — an object that
14746
+ * settled and stopped moving. It is NO LONGER a tracked object (the tracker was
14747
+ * told to forget it so it stops re-spawning tracks/events), but it IS still
14748
+ * physically present, so it keeps counting toward `frame` occupancy and is
14749
+ * listed here so the UI can show it in a dedicated "Stationary" section instead
14750
+ * of flooding the live event feed.
14751
+ */
14752
+ var StationaryObjectSchema = object({
14753
+ id: string(),
14754
+ className: string(),
14755
+ bbox: object({
14756
+ x: number(),
14757
+ y: number(),
14758
+ w: number(),
14759
+ h: number()
14760
+ }),
14761
+ frameWidth: number().int().nonnegative(),
14762
+ frameHeight: number().int().nonnegative(),
14763
+ /** When the source track was first seen. */
14764
+ firstSeenAt: number().int(),
14765
+ /** When the object was recognised as parked (promotion time). */
14766
+ becameStationaryAt: number().int(),
14767
+ /** Last frame a detection confirmed the object is still there. */
14768
+ lastConfirmedAt: number().int(),
14769
+ /** Enrichment label carried from the source track (identity / plate). */
14770
+ label: string().optional(),
14771
+ /** Native-resolution key-frame media key for the parked object's best image. */
14772
+ keyFrameMediaKey: string().optional()
14773
+ });
14393
14774
  var CameraOccupancySnapshotSchema = object({
14394
14775
  /** Frame timestamp of the inference result that produced this snapshot. */
14395
14776
  ts: number().int(),
@@ -14398,10 +14779,15 @@ var CameraOccupancySnapshotSchema = object({
14398
14779
  frameHeight: number().int().nonnegative(),
14399
14780
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
14400
14781
  zones: array(ZoneScopeBreakdownSchema).readonly(),
14401
- /** Frame-wide aggregate (everywhere, regardless of zone membership). */
14782
+ /** Frame-wide aggregate (everywhere, regardless of zone membership).
14783
+ * INCLUDES currently-confirmed stationary objects (they are still present). */
14402
14784
  frame: PerScopeBreakdownSchema,
14403
14785
  /** Detections that landed outside every zone. Empty when no zones defined. */
14404
- unzoned: PerScopeBreakdownSchema
14786
+ unzoned: PerScopeBreakdownSchema,
14787
+ /** Parked objects on this camera (additive — absent on legacy snapshots).
14788
+ * Surfaced separately so the UI shows them in a dedicated section rather
14789
+ * than as repeated tracks/events. */
14790
+ stationaryObjects: array(StationaryObjectSchema).readonly().optional()
14405
14791
  });
14406
14792
  /**
14407
14793
  * Time-series resolution. The history methods return one bucket per
@@ -14636,6 +15022,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
14636
15022
  pressureSensor: pressureSensorCapability,
14637
15023
  privacyMask: privacyMaskCapability,
14638
15024
  ptzAutotrack: ptzAutotrackCapability,
15025
+ sceneMonitor: sceneMonitorCapability,
14639
15026
  scriptRunner: scriptRunnerCapability,
14640
15027
  smoke: smokeCapability,
14641
15028
  streamParams: streamParamsCapability,
@@ -15688,7 +16075,26 @@ var InvokeReplyEnvelopeSchema = object({
15688
16075
  /** Set when the handler called `reply.type(mime)`. */
15689
16076
  contentType: string().optional()
15690
16077
  });
15691
- method(_void(), array(AddonHttpRouteSchema)), method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" });
16078
+ var addonRoutesCapability = {
16079
+ name: "addon-routes",
16080
+ scope: "system",
16081
+ mode: "collection",
16082
+ internal: true,
16083
+ methods: {
16084
+ getRoutes: method(_void(), array(AddonHttpRouteSchema)),
16085
+ /**
16086
+ * Cross-process dispatch entry point. Forked addons implement this
16087
+ * (via `buildAddonRouteProvider`) so the hub's Fastify catch-all
16088
+ * can route through Moleculer when the handler lives in a worker.
16089
+ *
16090
+ * Local addons can implement it for free with the same helper;
16091
+ * the hub bypasses the wire on co-located addons.
16092
+ */
16093
+ invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
16094
+ },
16095
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
16096
+ mount: { kind: "skip" }
16097
+ };
15692
16098
  var ConfigTabDeclarationSchema = object({
15693
16099
  id: string(),
15694
16100
  label: string(),
@@ -15921,7 +16327,12 @@ var NotificationRuleConditionsSchema = object({
15921
16327
  clipDescription: object({
15922
16328
  text: string().min(1),
15923
16329
  minSimilarity: number().min(0).max(1)
15924
- }).optional()
16330
+ }).optional(),
16331
+ /** Match events whose recognized-entity label (face identity name or plate
16332
+ * vehicle name, propagated onto `event.data.label`) is one of these values.
16333
+ * Empty/absent → unaffected (back-compat). Enables "notify me when <named
16334
+ * vehicle/person> is seen". */
16335
+ labels: array(string()).readonly().optional()
15925
16336
  });
15926
16337
  var NotificationRuleTemplateSchema = object({
15927
16338
  title: string(),
@@ -16179,11 +16590,19 @@ method(object({
16179
16590
  * login page needs NO change.
16180
16591
  *
16181
16592
  * - `widget` — a Module-Federation widget the login page mounts (via
16182
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
16183
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
16184
- * addon bundle. The referenced widget also declares `preAuth: true` in
16185
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
16186
- * stamps a public `bundleUrl` from `addonId` + `bundle`.
16593
+ * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
16594
+ * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
16595
+ * mechanism kept for future use; no shipped addon uses it on the login
16596
+ * page (the passkey ceremony below runs natively in the shell instead).
16597
+ *
16598
+ * - `passkey` — a declarative WebAuthn ceremony the shell renders
16599
+ * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
16600
+ * a remotely-loaded bundle). Carries the addon's effective `rpId` /
16601
+ * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
16602
+ * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
16603
+ * fetching any remote code pre-auth. Contribution stays unconditional —
16604
+ * enrollment state is never leaked pre-auth; visibility is a shell
16605
+ * decision.
16187
16606
  *
16188
16607
  * Every contribution carries a `stage`:
16189
16608
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -16197,30 +16616,45 @@ method(object({
16197
16616
  */
16198
16617
  /** When a login method renders in the two-phase login flow. */
16199
16618
  var LoginStageEnum = _enum(["primary", "second-factor"]);
16200
- /** One login-method contribution — redirect button OR pre-auth widget. */
16201
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
16202
- kind: literal("redirect"),
16203
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
16204
- id: string(),
16205
- /** Operator-facing button label. */
16206
- label: string(),
16207
- /** lucide-react icon name. */
16208
- icon: string().optional(),
16209
- /** Addon-owned HTTP route the button navigates to (GET). */
16210
- startUrl: string(),
16211
- stage: LoginStageEnum
16212
- }), object({
16213
- kind: literal("widget"),
16214
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
16215
- id: string(),
16216
- /** Owning addon id drives the public bundle URL + the MF namespace. */
16217
- addonId: string(),
16218
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
16219
- bundle: string(),
16220
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
16221
- remote: WidgetRemoteSchema,
16222
- stage: LoginStageEnum
16223
- })]);
16619
+ /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
16620
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [
16621
+ object({
16622
+ kind: literal("redirect"),
16623
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
16624
+ id: string(),
16625
+ /** Operator-facing button label. */
16626
+ label: string(),
16627
+ /** lucide-react icon name. */
16628
+ icon: string().optional(),
16629
+ /** Addon-owned HTTP route the button navigates to (GET). */
16630
+ startUrl: string(),
16631
+ stage: LoginStageEnum
16632
+ }),
16633
+ object({
16634
+ kind: literal("widget"),
16635
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
16636
+ id: string(),
16637
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
16638
+ addonId: string(),
16639
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
16640
+ bundle: string(),
16641
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
16642
+ remote: WidgetRemoteSchema,
16643
+ stage: LoginStageEnum
16644
+ }),
16645
+ object({
16646
+ kind: literal("passkey"),
16647
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
16648
+ id: string(),
16649
+ /** Operator-facing button label. */
16650
+ label: string(),
16651
+ stage: LoginStageEnum,
16652
+ /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
16653
+ rpId: string(),
16654
+ /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
16655
+ origin: string().nullable()
16656
+ })
16657
+ ]);
16224
16658
  method(_void(), array(LoginMethodContributionSchema).readonly());
16225
16659
  /**
16226
16660
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -18114,14 +18548,14 @@ var TargetKindCapsSchema = object({
18114
18548
  * the union is large and not meant for runtime validation here; the exported
18115
18549
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
18116
18550
  */
18117
- var ConfigSchemaPassthrough = unknown();
18551
+ var ConfigSchemaPassthrough$1 = unknown();
18118
18552
  var TargetKindSchema = object({
18119
18553
  kind: string(),
18120
18554
  label: string(),
18121
18555
  icon: string(),
18122
18556
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
18123
18557
  addonId: string(),
18124
- configSchema: ConfigSchemaPassthrough,
18558
+ configSchema: ConfigSchemaPassthrough$1,
18125
18559
  supportsDiscovery: boolean(),
18126
18560
  caps: TargetKindCapsSchema
18127
18561
  });
@@ -18188,6 +18622,298 @@ var notificationOutputCapability = {
18188
18622
  }
18189
18623
  };
18190
18624
  /**
18625
+ * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
18626
+ * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
18627
+ * caps stay wire-compatible without a circular cap→cap import.
18628
+ *
18629
+ * Errors are a discriminated-union RESULT, never thrown: the shape survives
18630
+ * every transport tier structurally, and failed calls still write usage rows.
18631
+ * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
18632
+ */
18633
+ var LlmUsageSchema = object({
18634
+ inputTokens: number(),
18635
+ outputTokens: number()
18636
+ });
18637
+ var LlmErrorCodeSchema = _enum([
18638
+ "timeout",
18639
+ "rate-limited",
18640
+ "auth",
18641
+ "refusal",
18642
+ "bad-request",
18643
+ "unavailable",
18644
+ "no-profile",
18645
+ "budget-exceeded",
18646
+ "adapter-error"
18647
+ ]);
18648
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
18649
+ ok: literal(true),
18650
+ text: string(),
18651
+ model: string(),
18652
+ usage: LlmUsageSchema,
18653
+ truncated: boolean(),
18654
+ latencyMs: number()
18655
+ }), object({
18656
+ ok: literal(false),
18657
+ code: LlmErrorCodeSchema,
18658
+ message: string(),
18659
+ retryAfterMs: number().optional()
18660
+ })]);
18661
+ /**
18662
+ * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
18663
+ * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
18664
+ * notification-output.cap.ts:27-31 precedents).
18665
+ */
18666
+ var LlmImageSchema = object({
18667
+ bytes: _instanceof(Uint8Array),
18668
+ mimeType: string()
18669
+ });
18670
+ var LlmGenerateBaseInputSchema = object({
18671
+ /** Collection routing (the notification-output posture). */
18672
+ addonId: string().optional(),
18673
+ /** Explicit profile; else the resolution chain (spec §3). */
18674
+ profileId: string().optional(),
18675
+ /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
18676
+ consumer: string(),
18677
+ system: string().optional(),
18678
+ /** v1: single-turn. `messages[]` is a v2 additive field. */
18679
+ prompt: string(),
18680
+ /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
18681
+ jsonSchema: record(string(), unknown()).optional(),
18682
+ /** Per-call override of the profile default. */
18683
+ maxTokens: number().int().positive().optional(),
18684
+ temperature: number().optional()
18685
+ });
18686
+ /**
18687
+ * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
18688
+ * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
18689
+ * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
18690
+ * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
18691
+ * this only through the `llm` cap's methods.
18692
+ *
18693
+ * One running llama-server child per node in v1 (models are RAM-heavy).
18694
+ * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
18695
+ * watchdog — operator decision #3).
18696
+ */
18697
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
18698
+ object({
18699
+ kind: literal("catalog"),
18700
+ catalogId: string()
18701
+ }),
18702
+ object({
18703
+ kind: literal("url"),
18704
+ url: string(),
18705
+ sha256: string().optional()
18706
+ }),
18707
+ object({
18708
+ kind: literal("path"),
18709
+ path: string()
18710
+ })
18711
+ ]);
18712
+ var ManagedRuntimeConfigSchema = object({
18713
+ /** WHERE the runtime lives — hub or any agent. */
18714
+ nodeId: string(),
18715
+ /** Closed for v1; 'ollama' is a v2 candidate. */
18716
+ engine: _enum(["llama-cpp"]),
18717
+ model: ManagedModelRefSchema,
18718
+ contextSize: number().int().default(4096),
18719
+ /** 0 = CPU-only. */
18720
+ gpuLayers: number().int().default(0),
18721
+ /** Default: cpus-2, clamped ≥1 (resolved node-side). */
18722
+ threads: number().int().optional(),
18723
+ /** Concurrent slots. */
18724
+ parallel: number().int().default(1),
18725
+ /** Else lazy: first generate boots it. */
18726
+ autoStart: boolean().default(false),
18727
+ /** 0 = never; frees RAM after quiet periods. */
18728
+ idleStopMinutes: number().int().default(30)
18729
+ });
18730
+ var LlmRuntimeStatusSchema = object({
18731
+ /** Status is ALWAYS node-qualified. */
18732
+ nodeId: string(),
18733
+ state: _enum([
18734
+ "stopped",
18735
+ "downloading",
18736
+ "starting",
18737
+ "ready",
18738
+ "crashed",
18739
+ "failed"
18740
+ ]),
18741
+ pid: number().optional(),
18742
+ port: number().optional(),
18743
+ modelPath: string().optional(),
18744
+ modelId: string().optional(),
18745
+ downloadProgress: number().min(0).max(1).optional(),
18746
+ lastError: string().optional(),
18747
+ crashesInWindow: number(),
18748
+ /** Child RSS (sampled best-effort). */
18749
+ memoryBytes: number().optional(),
18750
+ vramBytes: number().optional()
18751
+ });
18752
+ var LlmNodeModelSchema = object({
18753
+ file: string(),
18754
+ sizeBytes: number(),
18755
+ catalogId: string().optional(),
18756
+ installedAt: number().optional()
18757
+ });
18758
+ var LlmRuntimeDiskUsageSchema = object({
18759
+ nodeId: string(),
18760
+ modelsBytes: number(),
18761
+ freeBytes: number().optional()
18762
+ });
18763
+ method(LlmGenerateBaseInputSchema.extend({
18764
+ images: array(LlmImageSchema).optional(),
18765
+ runtime: ManagedRuntimeConfigSchema,
18766
+ /** The managed profile's timeout, threaded by the hub provider. */
18767
+ timeoutMs: number().int().positive().optional()
18768
+ }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
18769
+ kind: "mutation",
18770
+ auth: "admin"
18771
+ }), method(object({}), _void(), {
18772
+ kind: "mutation",
18773
+ auth: "admin"
18774
+ }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
18775
+ kind: "mutation",
18776
+ auth: "admin"
18777
+ }), method(object({ file: string() }), _void(), {
18778
+ kind: "mutation",
18779
+ auth: "admin"
18780
+ }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
18781
+ /**
18782
+ * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
18783
+ * methods concat-fan across providers; single-row methods route to ONE
18784
+ * provider by the `addonId` in the call input (the notification-output
18785
+ * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
18786
+ * (hub-placed); the cap stays open for future providers.
18787
+ *
18788
+ * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
18789
+ * `apiKey` is a password field — providers REDACT it on read and merge on
18790
+ * write; a stored key NEVER round-trips to a client.
18791
+ */
18792
+ var LlmProfileKindSchema = _enum([
18793
+ "openai-compatible",
18794
+ "openai",
18795
+ "anthropic",
18796
+ "google",
18797
+ "managed-local"
18798
+ ]);
18799
+ var LlmProfileSchema = object({
18800
+ id: string(),
18801
+ name: string(),
18802
+ kind: LlmProfileKindSchema,
18803
+ /** Stamped by the provider — keeps the fanned catalog routable. */
18804
+ addonId: string(),
18805
+ enabled: boolean(),
18806
+ /** Vendor model id, or the managed runtime's loaded model. */
18807
+ model: string(),
18808
+ /** Required for openai-compatible; override for cloud kinds. */
18809
+ baseUrl: string().optional(),
18810
+ /** ConfigUISchema type:'password' — never round-trips (spec §5). */
18811
+ apiKey: string().optional(),
18812
+ supportsVision: boolean(),
18813
+ temperature: number().min(0).max(2).optional(),
18814
+ maxTokens: number().int().positive().optional(),
18815
+ timeoutMs: number().int().positive().default(6e4),
18816
+ extraHeaders: record(string(), string()).optional(),
18817
+ /** kind === 'managed-local' only (spec §4). */
18818
+ runtime: ManagedRuntimeConfigSchema.optional()
18819
+ });
18820
+ /** ConfigUISchema tree passed through untyped on the wire (the
18821
+ * notification-output `ConfigSchemaPassthrough` precedent at
18822
+ * notification-output.cap.ts:151); the exported TS type re-tightens it. */
18823
+ var ConfigSchemaPassthrough = unknown();
18824
+ var LlmProfileKindDescriptorSchema = object({
18825
+ kind: LlmProfileKindSchema,
18826
+ label: string(),
18827
+ icon: string(),
18828
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
18829
+ addonId: string(),
18830
+ configSchema: ConfigSchemaPassthrough
18831
+ });
18832
+ var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
18833
+ var LlmDefaultSchema = object({
18834
+ selector: LlmDefaultSelectorSchema,
18835
+ profileId: string()
18836
+ });
18837
+ /** Server-side rollup row — getUsage never dumps raw call rows (spec §6). */
18838
+ var LlmUsageRollupSchema = object({
18839
+ day: string(),
18840
+ consumer: string(),
18841
+ profileId: string(),
18842
+ calls: number(),
18843
+ okCalls: number(),
18844
+ errorCalls: number(),
18845
+ inputTokens: number(),
18846
+ outputTokens: number(),
18847
+ avgLatencyMs: number()
18848
+ });
18849
+ /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
18850
+ var ManagedModelCatalogEntrySchema = object({
18851
+ id: string(),
18852
+ label: string(),
18853
+ family: string(),
18854
+ purpose: _enum(["text", "vision"]),
18855
+ url: string(),
18856
+ sha256: string(),
18857
+ sizeBytes: number(),
18858
+ quantization: string(),
18859
+ /** Load-time guidance shown in the picker. */
18860
+ minRamBytes: number(),
18861
+ contextSizeDefault: number().int(),
18862
+ /** Vision models: companion projector file. */
18863
+ mmprojUrl: string().optional()
18864
+ });
18865
+ var LlmRuntimeNodeSchema = object({
18866
+ nodeId: string(),
18867
+ reachable: boolean(),
18868
+ status: LlmRuntimeStatusSchema.optional(),
18869
+ disk: LlmRuntimeDiskUsageSchema.optional(),
18870
+ error: string().optional()
18871
+ });
18872
+ var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
18873
+ var ProfileRefInputSchema = object({
18874
+ addonId: string(),
18875
+ profileId: string()
18876
+ });
18877
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
18878
+ kind: "mutation",
18879
+ auth: "admin"
18880
+ }), method(ProfileRefInputSchema, _void(), {
18881
+ kind: "mutation",
18882
+ auth: "admin"
18883
+ }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
18884
+ kind: "mutation",
18885
+ auth: "admin"
18886
+ }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
18887
+ selector: LlmDefaultSelectorSchema,
18888
+ profileId: string().nullable()
18889
+ }), _void(), {
18890
+ kind: "mutation",
18891
+ auth: "admin"
18892
+ }), method(object({
18893
+ since: number().optional(),
18894
+ until: number().optional(),
18895
+ consumer: string().optional(),
18896
+ profileId: string().optional()
18897
+ }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
18898
+ nodeId: string(),
18899
+ model: ManagedModelRefSchema
18900
+ }), _void(), {
18901
+ kind: "mutation",
18902
+ auth: "admin"
18903
+ }), method(object({
18904
+ nodeId: string(),
18905
+ file: string()
18906
+ }), _void(), {
18907
+ kind: "mutation",
18908
+ auth: "admin"
18909
+ }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
18910
+ kind: "mutation",
18911
+ auth: "admin"
18912
+ }), method(ProfileRefInputSchema, _void(), {
18913
+ kind: "mutation",
18914
+ auth: "admin"
18915
+ });
18916
+ /**
18191
18917
  * Zod schemas for persisted record types.
18192
18918
  *
18193
18919
  * These schemas serve as the single source of truth for types that are
@@ -18389,6 +19115,22 @@ var TrackSnapshotSchema = object({
18389
19115
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
18390
19116
  mediaKey: string()
18391
19117
  });
19118
+ /**
19119
+ * One audio-classification label heard on the track's camera while the
19120
+ * track was alive, aggregated per label. An "episode" is one persisted
19121
+ * audio event (the confident-classification path: score ≥ the device's
19122
+ * `classificationMinScore`, class-change-or-heartbeat coalesced) — NOT
19123
+ * one 32 ms inference chunk, so counts stay human-scaled.
19124
+ */
19125
+ var TrackAudioLabelSchema = object({
19126
+ label: string(),
19127
+ /** Highest classification score observed across the label's episodes. */
19128
+ peakScore: number(),
19129
+ /** Number of coalesced audio-event episodes carrying this label. */
19130
+ count: number(),
19131
+ firstAt: number(),
19132
+ lastAt: number()
19133
+ });
18392
19134
  var TrackSchema = object({
18393
19135
  trackId: string(),
18394
19136
  deviceId: number(),
@@ -18403,6 +19145,10 @@ var TrackSchema = object({
18403
19145
  snapshots: array(TrackSnapshotSchema).readonly(),
18404
19146
  /** Deduplicated zones the track has entered at least once. */
18405
19147
  zonesVisited: array(string()).readonly(),
19148
+ /** Deduplicated set of detector classes observed for this track over its
19149
+ * life (a track may be reclassified, e.g. person→vehicle). Absent on
19150
+ * legacy rows written before class accumulation shipped. */
19151
+ classes: array(string()).readonly().optional(),
18406
19152
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
18407
19153
  totalDistance: number(),
18408
19154
  state: TrackStateSchema,
@@ -18416,7 +19162,11 @@ var TrackSchema = object({
18416
19162
  bestEventId: string().optional(),
18417
19163
  /** Tag of the importance sub-signal that dominated the score
18418
19164
  * (identity|dwell|proximity|class|confidence|travel|zone). */
18419
- importanceReason: string().optional()
19165
+ importanceReason: string().optional(),
19166
+ /** Audio-classification labels heard on the camera during the track's
19167
+ * life (score ≥ device `classificationMinScore`), aggregated per label.
19168
+ * Absent on legacy rows / tracks with no confident audio. */
19169
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18420
19170
  });
18421
19171
  var BaseEventFields = {
18422
19172
  id: string(),
@@ -18564,7 +19314,7 @@ var KeyEventSchema = object({
18564
19314
  /** Track lifetime in ms (lastSeen - firstSeen). */
18565
19315
  windowMs: number().optional()
18566
19316
  });
18567
- var TrackedDetectionSchema = object({
19317
+ object({
18568
19318
  trackId: string(),
18569
19319
  className: string(),
18570
19320
  confidence: number(),
@@ -18572,6 +19322,23 @@ var TrackedDetectionSchema = object({
18572
19322
  zones: array(string()).readonly(),
18573
19323
  state: TrackStateSchema
18574
19324
  });
19325
+ var OverlayDetectionSchema = looseObject({
19326
+ id: string(),
19327
+ kind: _enum(["first-level", "detail"]),
19328
+ macroClass: string(),
19329
+ score: number(),
19330
+ bbox: object({
19331
+ x: number(),
19332
+ y: number(),
19333
+ width: number(),
19334
+ height: number()
19335
+ }),
19336
+ labels: array(looseObject({
19337
+ label: string(),
19338
+ score: number()
19339
+ })).readonly(),
19340
+ parentId: string().optional()
19341
+ });
18575
19342
  var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
18576
19343
  var SearchObjectEventsInput = object({
18577
19344
  text: string(),
@@ -18582,6 +19349,20 @@ var SearchObjectEventsInput = object({
18582
19349
  limit: number().default(50),
18583
19350
  minScore: number().min(0).max(1).default(.2)
18584
19351
  });
19352
+ var TrackCascadeCountsSchema = object({
19353
+ /** Persisted track roots deleted (authoritative). */
19354
+ tracks: number().int(),
19355
+ /** Object events removed with their tracks (best-effort; see note above). */
19356
+ events: number().int(),
19357
+ /** Track/face/plate-owned media removed (best-effort). Never identity media. */
19358
+ media: number().int(),
19359
+ /** Unassigned (non-enrolled) face reads removed (best-effort). */
19360
+ faces: number().int(),
19361
+ /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19362
+ plates: number().int(),
19363
+ /** Per-track CLIP search vectors removed (best-effort). */
19364
+ embeddings: number().int()
19365
+ });
18585
19366
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
18586
19367
  deviceId: number(),
18587
19368
  trackId: string()
@@ -18613,6 +19394,24 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18613
19394
  }), {
18614
19395
  kind: "mutation",
18615
19396
  auth: "admin"
19397
+ }), method(object({
19398
+ deviceId: number(),
19399
+ cutoffMs: number()
19400
+ }), TrackCascadeCountsSchema, {
19401
+ kind: "mutation",
19402
+ auth: "admin"
19403
+ }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
19404
+ kind: "mutation",
19405
+ auth: "admin"
19406
+ }), method(object({
19407
+ deviceId: number(),
19408
+ trackIds: array(string()).min(1)
19409
+ }), object({
19410
+ deleted: number().int(),
19411
+ failed: array(string()).readonly()
19412
+ }), {
19413
+ kind: "mutation",
19414
+ auth: "admin"
18616
19415
  }), method(object({
18617
19416
  eventId: string(),
18618
19417
  kind: MediaFileKindEnum.optional()
@@ -18621,7 +19420,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18621
19420
  timestamp: number(),
18622
19421
  frameWidth: number(),
18623
19422
  frameHeight: number(),
18624
- detections: array(TrackedDetectionSchema).readonly()
19423
+ detections: array(OverlayDetectionSchema).readonly()
18625
19424
  }), object({
18626
19425
  deviceId: number(),
18627
19426
  trackId: string(),
@@ -21479,6 +22278,24 @@ DeviceType.Camera, method(object({
21479
22278
  deviceId: number(),
21480
22279
  status: OsdStatusSchema
21481
22280
  });
22281
+ var VehicleSchema = object({
22282
+ id: string(),
22283
+ name: string(),
22284
+ sampleCount: number().int().nonnegative(),
22285
+ coverMediaKey: string().optional(),
22286
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
22287
+ coverBase64: string().optional()
22288
+ });
22289
+ var VehicleSampleInfoSchema = object({
22290
+ id: string(),
22291
+ /** The plate text this sample enrolled (self-labeling — no embedding). */
22292
+ text: string(),
22293
+ /** OCR confidence of the enrolled read (0..1). */
22294
+ score: number(),
22295
+ addedAt: number().int(),
22296
+ deviceId: number().int().optional(),
22297
+ base64: string().optional()
22298
+ });
21482
22299
  var PlateInfoSchema = object({
21483
22300
  plateId: string(),
21484
22301
  deviceId: number().int(),
@@ -21490,6 +22307,16 @@ var PlateInfoSchema = object({
21490
22307
  score: number(),
21491
22308
  /** True when the text was manually corrected (exempt from retention). */
21492
22309
  corrected: boolean(),
22310
+ /** Recognized vehicle id when a matcher lookup named this read (SQL NULL → absent). */
22311
+ recognizedVehicleId: string().optional(),
22312
+ /** Resolved vehicle name for `recognizedVehicleId` (UI convenience). */
22313
+ vehicleName: string().optional(),
22314
+ /** True once the read was assigned to a vehicle (enrolled as a sample). */
22315
+ assigned: boolean(),
22316
+ /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
22317
+ plateBbox: BoundingBoxSchema.optional(),
22318
+ /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
22319
+ keyFrameMediaKey: string().optional(),
21493
22320
  base64: string().optional()
21494
22321
  });
21495
22322
  var MediaFileLiteSchema = object({
@@ -21529,6 +22356,48 @@ method(object({
21529
22356
  }), method(object({ plateId: string() }), _void(), {
21530
22357
  kind: "mutation",
21531
22358
  auth: "admin"
22359
+ }), method(_void(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
22360
+ kind: "mutation",
22361
+ auth: "admin"
22362
+ }), method(object({
22363
+ id: string(),
22364
+ name: string().min(1)
22365
+ }), _void(), {
22366
+ kind: "mutation",
22367
+ auth: "admin"
22368
+ }), method(object({ id: string() }), _void(), {
22369
+ kind: "mutation",
22370
+ auth: "admin"
22371
+ }), method(object({ vehicleId: string() }), array(VehicleSampleInfoSchema).readonly()), method(object({
22372
+ vehicleId: string(),
22373
+ sampleId: string()
22374
+ }), _void(), {
22375
+ kind: "mutation",
22376
+ auth: "admin"
22377
+ }), method(object({
22378
+ plateId: string(),
22379
+ vehicleId: string()
22380
+ }), _void(), {
22381
+ kind: "mutation",
22382
+ auth: "admin"
22383
+ }), method(object({ plateId: string() }), _void(), {
22384
+ kind: "mutation",
22385
+ auth: "admin"
22386
+ }), method(object({
22387
+ plateIds: array(string()).min(1),
22388
+ vehicleId: string()
22389
+ }), object({
22390
+ assigned: number().int(),
22391
+ failed: array(string()).readonly()
22392
+ }), {
22393
+ kind: "mutation",
22394
+ auth: "admin"
22395
+ }), method(object({ plateIds: array(string()).min(1) }), object({
22396
+ unassigned: number().int(),
22397
+ failed: array(string()).readonly()
22398
+ }), {
22399
+ kind: "mutation",
22400
+ auth: "admin"
21532
22401
  });
21533
22402
  var ModelFormatSchema = _enum(MODEL_FORMATS);
21534
22403
  var HwAccelBackendInputSchema = _enum([
@@ -21607,6 +22476,10 @@ var GpuInfoSchema = object({
21607
22476
  memoryMB: number().optional()
21608
22477
  });
21609
22478
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
22479
+ var CoralInfoSchema = object({
22480
+ type: literal("coral-edgetpu"),
22481
+ bus: string().optional()
22482
+ });
21610
22483
  var HardwareInfoSchema = object({
21611
22484
  platform: HardwarePlatformSchema,
21612
22485
  arch: HardwareArchSchema,
@@ -21615,7 +22488,8 @@ var HardwareInfoSchema = object({
21615
22488
  totalRAM_MB: number(),
21616
22489
  availableRAM_MB: number(),
21617
22490
  gpu: GpuInfoSchema.nullable(),
21618
- npu: NpuInfoSchema.nullable()
22491
+ npu: NpuInfoSchema.nullable(),
22492
+ coral: CoralInfoSchema.nullable().optional()
21619
22493
  });
21620
22494
  var PlatformScoreSchema = object({
21621
22495
  runtime: _enum(["node", "python"]),
@@ -24074,6 +24948,168 @@ Object.freeze({
24074
24948
  addonId: null,
24075
24949
  access: "create"
24076
24950
  },
24951
+ "llm.deleteModel": {
24952
+ capName: "llm",
24953
+ capScope: "system",
24954
+ addonId: null,
24955
+ access: "delete"
24956
+ },
24957
+ "llm.deleteProfile": {
24958
+ capName: "llm",
24959
+ capScope: "system",
24960
+ addonId: null,
24961
+ access: "delete"
24962
+ },
24963
+ "llm.generate": {
24964
+ capName: "llm",
24965
+ capScope: "system",
24966
+ addonId: null,
24967
+ access: "create"
24968
+ },
24969
+ "llm.generateVision": {
24970
+ capName: "llm",
24971
+ capScope: "system",
24972
+ addonId: null,
24973
+ access: "create"
24974
+ },
24975
+ "llm.getDefaults": {
24976
+ capName: "llm",
24977
+ capScope: "system",
24978
+ addonId: null,
24979
+ access: "view"
24980
+ },
24981
+ "llm.getRuntimeStatus": {
24982
+ capName: "llm",
24983
+ capScope: "system",
24984
+ addonId: null,
24985
+ access: "view"
24986
+ },
24987
+ "llm.getUsage": {
24988
+ capName: "llm",
24989
+ capScope: "system",
24990
+ addonId: null,
24991
+ access: "view"
24992
+ },
24993
+ "llm.installModel": {
24994
+ capName: "llm",
24995
+ capScope: "system",
24996
+ addonId: null,
24997
+ access: "create"
24998
+ },
24999
+ "llm.listModelCatalog": {
25000
+ capName: "llm",
25001
+ capScope: "system",
25002
+ addonId: null,
25003
+ access: "view"
25004
+ },
25005
+ "llm.listModels": {
25006
+ capName: "llm",
25007
+ capScope: "system",
25008
+ addonId: null,
25009
+ access: "view"
25010
+ },
25011
+ "llm.listNodeModels": {
25012
+ capName: "llm",
25013
+ capScope: "system",
25014
+ addonId: null,
25015
+ access: "view"
25016
+ },
25017
+ "llm.listProfileKinds": {
25018
+ capName: "llm",
25019
+ capScope: "system",
25020
+ addonId: null,
25021
+ access: "view"
25022
+ },
25023
+ "llm.listProfiles": {
25024
+ capName: "llm",
25025
+ capScope: "system",
25026
+ addonId: null,
25027
+ access: "view"
25028
+ },
25029
+ "llm.listRuntimeNodes": {
25030
+ capName: "llm",
25031
+ capScope: "system",
25032
+ addonId: null,
25033
+ access: "view"
25034
+ },
25035
+ "llm.setDefault": {
25036
+ capName: "llm",
25037
+ capScope: "system",
25038
+ addonId: null,
25039
+ access: "create"
25040
+ },
25041
+ "llm.startRuntime": {
25042
+ capName: "llm",
25043
+ capScope: "system",
25044
+ addonId: null,
25045
+ access: "create"
25046
+ },
25047
+ "llm.stopRuntime": {
25048
+ capName: "llm",
25049
+ capScope: "system",
25050
+ addonId: null,
25051
+ access: "create"
25052
+ },
25053
+ "llm.testProfile": {
25054
+ capName: "llm",
25055
+ capScope: "system",
25056
+ addonId: null,
25057
+ access: "create"
25058
+ },
25059
+ "llm.upsertProfile": {
25060
+ capName: "llm",
25061
+ capScope: "system",
25062
+ addonId: null,
25063
+ access: "create"
25064
+ },
25065
+ "llmRuntime.complete": {
25066
+ capName: "llm-runtime",
25067
+ capScope: "system",
25068
+ addonId: null,
25069
+ access: "create"
25070
+ },
25071
+ "llmRuntime.deleteModel": {
25072
+ capName: "llm-runtime",
25073
+ capScope: "system",
25074
+ addonId: null,
25075
+ access: "delete"
25076
+ },
25077
+ "llmRuntime.ensureStarted": {
25078
+ capName: "llm-runtime",
25079
+ capScope: "system",
25080
+ addonId: null,
25081
+ access: "create"
25082
+ },
25083
+ "llmRuntime.getDiskUsage": {
25084
+ capName: "llm-runtime",
25085
+ capScope: "system",
25086
+ addonId: null,
25087
+ access: "view"
25088
+ },
25089
+ "llmRuntime.installModel": {
25090
+ capName: "llm-runtime",
25091
+ capScope: "system",
25092
+ addonId: null,
25093
+ access: "create"
25094
+ },
25095
+ "llmRuntime.listLocalModels": {
25096
+ capName: "llm-runtime",
25097
+ capScope: "system",
25098
+ addonId: null,
25099
+ access: "view"
25100
+ },
25101
+ "llmRuntime.status": {
25102
+ capName: "llm-runtime",
25103
+ capScope: "system",
25104
+ addonId: null,
25105
+ access: "view"
25106
+ },
25107
+ "llmRuntime.stop": {
25108
+ capName: "llm-runtime",
25109
+ capScope: "system",
25110
+ addonId: null,
25111
+ access: "create"
25112
+ },
24077
25113
  "localNetwork.getAllowedAddresses": {
24078
25114
  capName: "local-network",
24079
25115
  capScope: "system",
@@ -24704,6 +25740,12 @@ Object.freeze({
24704
25740
  addonId: null,
24705
25741
  access: "delete"
24706
25742
  },
25743
+ "pipelineAnalytics.deleteTracks": {
25744
+ capName: "pipeline-analytics",
25745
+ capScope: "device",
25746
+ addonId: null,
25747
+ access: "delete"
25748
+ },
24707
25749
  "pipelineAnalytics.getActiveTracks": {
24708
25750
  capName: "pipeline-analytics",
24709
25751
  capScope: "device",
@@ -24770,12 +25812,24 @@ Object.freeze({
24770
25812
  addonId: null,
24771
25813
  access: "create"
24772
25814
  },
25815
+ "pipelineAnalytics.pruneTracksBefore": {
25816
+ capName: "pipeline-analytics",
25817
+ capScope: "device",
25818
+ addonId: null,
25819
+ access: "create"
25820
+ },
24773
25821
  "pipelineAnalytics.searchObjectEvents": {
24774
25822
  capName: "pipeline-analytics",
24775
25823
  capScope: "device",
24776
25824
  addonId: null,
24777
25825
  access: "view"
24778
25826
  },
25827
+ "pipelineAnalytics.wipeAllAnalytics": {
25828
+ capName: "pipeline-analytics",
25829
+ capScope: "device",
25830
+ addonId: null,
25831
+ access: "delete"
25832
+ },
24779
25833
  "pipelineExecutor.cacheFrameInPool": {
24780
25834
  capName: "pipeline-executor",
24781
25835
  capScope: "system",
@@ -25280,18 +26334,42 @@ Object.freeze({
25280
26334
  addonId: null,
25281
26335
  access: "create"
25282
26336
  },
26337
+ "plateGallery.assignPlate": {
26338
+ capName: "plate-gallery",
26339
+ capScope: "system",
26340
+ addonId: null,
26341
+ access: "create"
26342
+ },
26343
+ "plateGallery.assignPlates": {
26344
+ capName: "plate-gallery",
26345
+ capScope: "system",
26346
+ addonId: null,
26347
+ access: "create"
26348
+ },
25283
26349
  "plateGallery.correctPlateText": {
25284
26350
  capName: "plate-gallery",
25285
26351
  capScope: "system",
25286
26352
  addonId: null,
25287
26353
  access: "create"
25288
26354
  },
26355
+ "plateGallery.createVehicle": {
26356
+ capName: "plate-gallery",
26357
+ capScope: "system",
26358
+ addonId: null,
26359
+ access: "create"
26360
+ },
25289
26361
  "plateGallery.deletePlate": {
25290
26362
  capName: "plate-gallery",
25291
26363
  capScope: "system",
25292
26364
  addonId: null,
25293
26365
  access: "delete"
25294
26366
  },
26367
+ "plateGallery.deleteVehicle": {
26368
+ capName: "plate-gallery",
26369
+ capScope: "system",
26370
+ addonId: null,
26371
+ access: "delete"
26372
+ },
25295
26373
  "plateGallery.getPlateByTrack": {
25296
26374
  capName: "plate-gallery",
25297
26375
  capScope: "system",
@@ -25310,6 +26388,30 @@ Object.freeze({
25310
26388
  addonId: null,
25311
26389
  access: "view"
25312
26390
  },
26391
+ "plateGallery.listVehicles": {
26392
+ capName: "plate-gallery",
26393
+ capScope: "system",
26394
+ addonId: null,
26395
+ access: "view"
26396
+ },
26397
+ "plateGallery.listVehicleSamples": {
26398
+ capName: "plate-gallery",
26399
+ capScope: "system",
26400
+ addonId: null,
26401
+ access: "view"
26402
+ },
26403
+ "plateGallery.removeVehicleSample": {
26404
+ capName: "plate-gallery",
26405
+ capScope: "system",
26406
+ addonId: null,
26407
+ access: "delete"
26408
+ },
26409
+ "plateGallery.renameVehicle": {
26410
+ capName: "plate-gallery",
26411
+ capScope: "system",
26412
+ addonId: null,
26413
+ access: "create"
26414
+ },
25313
26415
  "plateGallery.searchPlates": {
25314
26416
  capName: "plate-gallery",
25315
26417
  capScope: "system",
@@ -25322,6 +26424,18 @@ Object.freeze({
25322
26424
  addonId: null,
25323
26425
  access: "view"
25324
26426
  },
26427
+ "plateGallery.unassignPlate": {
26428
+ capName: "plate-gallery",
26429
+ capScope: "system",
26430
+ addonId: null,
26431
+ access: "create"
26432
+ },
26433
+ "plateGallery.unassignPlates": {
26434
+ capName: "plate-gallery",
26435
+ capScope: "system",
26436
+ addonId: null,
26437
+ access: "create"
26438
+ },
25325
26439
  "platformProbe.getCapabilities": {
25326
26440
  capName: "platform-probe",
25327
26441
  capScope: "system",
@@ -25514,6 +26628,48 @@ Object.freeze({
25514
26628
  addonId: null,
25515
26629
  access: "create"
25516
26630
  },
26631
+ "sceneMonitor.captureReference": {
26632
+ capName: "scene-monitor",
26633
+ capScope: "device",
26634
+ addonId: null,
26635
+ access: "create"
26636
+ },
26637
+ "sceneMonitor.createScene": {
26638
+ capName: "scene-monitor",
26639
+ capScope: "device",
26640
+ addonId: null,
26641
+ access: "create"
26642
+ },
26643
+ "sceneMonitor.deleteReference": {
26644
+ capName: "scene-monitor",
26645
+ capScope: "device",
26646
+ addonId: null,
26647
+ access: "delete"
26648
+ },
26649
+ "sceneMonitor.deleteScene": {
26650
+ capName: "scene-monitor",
26651
+ capScope: "device",
26652
+ addonId: null,
26653
+ access: "delete"
26654
+ },
26655
+ "sceneMonitor.listScenes": {
26656
+ capName: "scene-monitor",
26657
+ capScope: "device",
26658
+ addonId: null,
26659
+ access: "view"
26660
+ },
26661
+ "sceneMonitor.recheckNow": {
26662
+ capName: "scene-monitor",
26663
+ capScope: "device",
26664
+ addonId: null,
26665
+ access: "create"
26666
+ },
26667
+ "sceneMonitor.updateScene": {
26668
+ capName: "scene-monitor",
26669
+ capScope: "device",
26670
+ addonId: null,
26671
+ access: "create"
26672
+ },
25517
26673
  "scriptRunner.run": {
25518
26674
  capName: "script-runner",
25519
26675
  capScope: "device",
@@ -26598,6 +27754,7 @@ Object.freeze({
26598
27754
  Object.freeze({
26599
27755
  "broker": "broker",
26600
27756
  "device-export": "device-export",
27757
+ "llm": "ai",
26601
27758
  "mesh-network": "mesh",
26602
27759
  "mqtt-broker": "broker",
26603
27760
  "network-access": "ingress",
@@ -32926,6 +34083,90 @@ function deriveIntegrationDomain(platforms) {
32926
34083
  return best ?? present[0] ?? null;
32927
34084
  }
32928
34085
  //#endregion
34086
+ //#region src/ha-notification-icon.ts
34087
+ /**
34088
+ * Bundled brand icon for the `homeassistant` notification-output kind, served
34089
+ * by THIS addon over its own `addon-routes` HTTP surface. Mirrors the pattern
34090
+ * `addon-notifiers` uses for the other notifier kinds (each provider serves the
34091
+ * icon for the kinds it contributes), so `notificationOutput.listTargetKinds`
34092
+ * can stamp a self-hosted `iconUrl` onto the `homeassistant` descriptor and the
34093
+ * admin UI renders `<img src=iconUrl>` instead of a text label.
34094
+ *
34095
+ * GET /addon/provider-homeassistant/icons/homeassistant → image/svg+xml
34096
+ *
34097
+ * The bytes ship INSIDE the addon (no external CDN / hotlink) so the icon is
34098
+ * self-contained and CSP-safe. The SVG is a standalone document (declares the
34099
+ * default `xmlns`, a `viewBox`, and an intrinsic `fill`) with NO editor-tool
34100
+ * namespaces, so a browser `<img src>` renders it in its strict SVG mode.
34101
+ *
34102
+ * We register ONE CONCRETE static route (`/icons/homeassistant`), never a
34103
+ * `/:kind` param route: the forked-addon route bridge re-matches the pattern
34104
+ * against itself and would clobber a `:kind` path param with the literal token
34105
+ * `':kind'`. A static path carries no param, so it routes correctly across the
34106
+ * process boundary.
34107
+ *
34108
+ * The route negotiates `Accept-Encoding` and stamps `content-encoding` itself
34109
+ * (serving a pre-computed br/gzip/deflate variant, or identity). This makes the
34110
+ * hub's global `@fastify/compress` skip the response — on the forked-addon route
34111
+ * bridge that compressor emits an EMPTY brotli stream for any compressible body
34112
+ * over its 1 KiB threshold, which shows as a broken `<img>`. Owning the coding
34113
+ * keeps this icon robust even if its bytes ever grow past the threshold, and
34114
+ * matches how `addon-notifiers` serves its notifier-kind icons.
34115
+ */
34116
+ /** The single notifier kind this addon contributes an icon for. */
34117
+ var HA_ICON_KIND = "homeassistant";
34118
+ /** The addon id — also the `/addon/<id>/…` route prefix. Mirrors the manifest id. */
34119
+ var HA_ROUTE_ID = "provider-homeassistant";
34120
+ /** One day; the icon is immutable per addon version. */
34121
+ var CACHE_CONTROL = "public, max-age=86400, immutable";
34122
+ /**
34123
+ * Home Assistant's official brand mark (simple-icons `homeassistant`, CC0) —
34124
+ * the blue "house + circuit" glyph — tinted with the official HA brand hex
34125
+ * (#18BCF2) so it reads on the dark admin UI. A standalone SVG document, no
34126
+ * editor-tool namespaces.
34127
+ */
34128
+ var HOME_ASSISTANT_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"#18BCF2\"><path d=\"M22.939 10.627 13.061.749a1.505 1.505 0 0 0-2.121 0l-9.879 9.878C.478 11.21 0 12.363 0 13.187v9c0 .826.675 1.5 1.5 1.5h9.227l-4.063-4.062a2.034 2.034 0 0 1-.664.113c-1.13 0-2.05-.92-2.05-2.05s.92-2.05 2.05-2.05 2.05.92 2.05 2.05c0 .233-.041.456-.113.665l3.163 3.163V9.928a2.05 2.05 0 0 1-1.15-1.84c0-1.13.92-2.05 2.05-2.05s2.05.92 2.05 2.05a2.05 2.05 0 0 1-1.15 1.84v8.127l3.146-3.146A2.051 2.051 0 0 1 18 12.239c1.13 0 2.05.92 2.05 2.05s-.92 2.05-2.05 2.05c-.25 0-.488-.047-.709-.13L12.9 20.602v3.088h9.6c.825 0 1.5-.675 1.5-1.5v-9c0-.825-.477-1.977-1.061-2.561z\"/></svg>";
34129
+ /**
34130
+ * The stable icon URL for the `homeassistant` kind. Stamped onto the descriptor
34131
+ * by `listTargetKinds`. Root-relative so it resolves against whatever origin
34132
+ * the admin UI is served from.
34133
+ */
34134
+ var HA_NOTIFICATION_ICON_URL = `/addon/${HA_ROUTE_ID}/icons/${HA_ICON_KIND}`;
34135
+ /** The icon's identity bytes plus every content coding we may serve, computed
34136
+ * once at module load (the icon is immutable). */
34137
+ var HA_ICON_IDENTITY = Buffer.from(HOME_ASSISTANT_SVG, "utf8");
34138
+ var HA_ICON_ENCODED = {
34139
+ br: brotliCompressSync(HA_ICON_IDENTITY),
34140
+ gzip: gzipSync(HA_ICON_IDENTITY),
34141
+ deflate: deflateSync(HA_ICON_IDENTITY)
34142
+ };
34143
+ /** Pick the best coding the client accepts (br › gzip › deflate), or `null` for
34144
+ * identity when the header names none of them. */
34145
+ function negotiateEncoding(acceptEncoding) {
34146
+ const tokens = acceptEncoding.toLowerCase().split(",").map((part) => part.trim().split(";")[0]?.trim());
34147
+ for (const coding of [
34148
+ "br",
34149
+ "gzip",
34150
+ "deflate"
34151
+ ]) if (tokens.includes(coding)) return coding;
34152
+ return null;
34153
+ }
34154
+ /** Build the `addon-routes` provider that serves the bundled Home Assistant icon. */
34155
+ function createHaIconRouteProvider() {
34156
+ return buildAddonRouteProvider(HA_ROUTE_ID, [{
34157
+ method: "GET",
34158
+ path: `/icons/${HA_ICON_KIND}`,
34159
+ access: "public",
34160
+ description: "Official brand icon (SVG) for the 'homeassistant' notifier kind.",
34161
+ handler: async (request, reply) => {
34162
+ const coding = negotiateEncoding(String(request.headers["accept-encoding"] ?? ""));
34163
+ reply.code(200).type("image/svg+xml").header("cache-control", CACHE_CONTROL).header("vary", "accept-encoding");
34164
+ if (coding !== null) reply.header("content-encoding", coding);
34165
+ reply.send(coding !== null ? HA_ICON_ENCODED[coding] : HA_ICON_IDENTITY);
34166
+ }
34167
+ }]);
34168
+ }
34169
+ //#endregion
32929
34170
  //#region src/ha-notification-output.ts
32930
34171
  /**
32931
34172
  * `homeassistant` notification-output kind — hosted BY the HA provider addon.
@@ -33115,6 +34356,13 @@ function parseNotifyServices(result, brokerId) {
33115
34356
  }
33116
34357
  }));
33117
34358
  }
34359
+ /** KV JSON-blob shape — a single `data` column routes the row through the
34360
+ * settings backend's canonical key/value path (id TEXT PK, data TEXT). */
34361
+ var KV_BLOB_COLUMNS = [{
34362
+ name: "data",
34363
+ type: "TEXT",
34364
+ notNull: true
34365
+ }];
33118
34366
  function rowToTarget(row) {
33119
34367
  const parsed = TargetSchema.safeParse({
33120
34368
  id: row.id,
@@ -33139,6 +34387,12 @@ var HaTargetStore = class {
33139
34387
  this.store = store;
33140
34388
  this.collection = collection;
33141
34389
  }
34390
+ /** Declare the backing collection before any read/write. The SQLite settings
34391
+ * backend rejects undeclared collections (fail-fast), so this MUST run before
34392
+ * `list`/`getById`/`upsert`/`delete` — mirrors addon-ai's ProfileStore.init. */
34393
+ async init() {
34394
+ await this.store.declareCollection(this.collection, KV_BLOB_COLUMNS);
34395
+ }
33142
34396
  async list() {
33143
34397
  const rows = await this.store.query(this.collection);
33144
34398
  const out = [];
@@ -33205,6 +34459,12 @@ function createApiHaSettingsStorePort(api) {
33205
34459
  collection,
33206
34460
  key: id
33207
34461
  });
34462
+ },
34463
+ declareCollection: async (collection, columns) => {
34464
+ await api.settingsStore.declareCollection.mutate({
34465
+ collection,
34466
+ columns: columns.map((c) => ({ ...c }))
34467
+ });
33208
34468
  }
33209
34469
  };
33210
34470
  }
@@ -33235,6 +34495,9 @@ function createMemoryHaSettingsStorePort() {
33235
34495
  },
33236
34496
  remove: async (collection, id) => {
33237
34497
  bucket(collection).delete(id);
34498
+ },
34499
+ declareCollection: async (collection) => {
34500
+ bucket(collection);
33238
34501
  }
33239
34502
  };
33240
34503
  }
@@ -33304,7 +34567,12 @@ function createHaNotificationOutputProvider(deps) {
33304
34567
  }
33305
34568
  }
33306
34569
  return {
33307
- listTargetKinds: async () => [descriptor],
34570
+ listTargetKinds: async () => {
34571
+ return [deps.iconUrl !== void 0 ? {
34572
+ ...descriptor,
34573
+ iconUrl: deps.iconUrl
34574
+ } : descriptor];
34575
+ },
33308
34576
  listTargets: async () => {
33309
34577
  return (await store.list()).map((target) => ({
33310
34578
  ...target,
@@ -33870,17 +35138,23 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
33870
35138
  },
33871
35139
  {
33872
35140
  capability: notificationOutputCapability,
33873
- provider: this.buildNotificationOutputProvider()
35141
+ provider: await this.buildNotificationOutputProvider()
35142
+ },
35143
+ {
35144
+ capability: addonRoutesCapability,
35145
+ provider: createHaIconRouteProvider()
33874
35146
  }
33875
35147
  ];
33876
35148
  }
33877
- buildNotificationOutputProvider() {
35149
+ async buildNotificationOutputProvider() {
33878
35150
  const api = this.ctx.api;
33879
35151
  const port = api ? createApiHaSettingsStorePort(api) : createMemoryHaSettingsStorePort();
33880
35152
  if (!api) this.ctx.logger.warn("ha notification-output: no ctx.api — targets persist in-memory only");
33881
35153
  const store = new HaTargetStore(port);
35154
+ await store.init();
33882
35155
  return createHaNotificationOutputProvider({
33883
35156
  addonId: this.ctx.id,
35157
+ iconUrl: HA_NOTIFICATION_ICON_URL,
33884
35158
  store,
33885
35159
  publish: async (brokerId, service, serviceData) => {
33886
35160
  await this.requireRegistry().publish(brokerId, {