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