@camstack/addon-decoder-nodeav 1.1.11 → 1.1.12

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/index.js +1067 -404
  2. package/dist/index.mjs +1068 -393
  3. package/package.json +1 -2
package/dist/index.mjs CHANGED
@@ -1,6 +1,4 @@
1
- import { DecoderFrameRingSink, FrameRingReaderCache, RING_BUDGET_MB, SEGMENT_NAME_PREFIX, makeSegmentName, unlinkSegment } from "@camstack/shm-ring";
2
1
  import { randomUUID } from "node:crypto";
3
- import { readdirSync } from "node:fs";
4
2
  //#region ../../node_modules/zod/v4/core/core.js
5
3
  var _a$1;
6
4
  function $constructor(name, initializer, params) {
@@ -4289,6 +4287,14 @@ function object(shape, params) {
4289
4287
  ...normalizeParams(params)
4290
4288
  });
4291
4289
  }
4290
+ function looseObject(shape, params) {
4291
+ return new ZodObject({
4292
+ type: "object",
4293
+ shape,
4294
+ catchall: unknown(),
4295
+ ...normalizeParams(params)
4296
+ });
4297
+ }
4292
4298
  var ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
4293
4299
  $ZodUnion.init(inst, def);
4294
4300
  ZodType.init(inst, def);
@@ -4630,7 +4636,7 @@ function _instanceof(cls, params = {}) {
4630
4636
  return inst;
4631
4637
  }
4632
4638
  //#endregion
4633
- //#region ../types/dist/sleep-Baang_XW.mjs
4639
+ //#region ../types/dist/sleep-_sv7WKkq.mjs
4634
4640
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4635
4641
  EventCategory["SystemBoot"] = "system.boot";
4636
4642
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5040,10 +5046,51 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5040
5046
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
5041
5047
  EventCategory["EnrichmentSceneStateChanged"] = "enrichment.scene.state-changed";
5042
5048
  EventCategory["EnrichmentActivitySummary"] = "enrichment.activity.summary";
5049
+ /**
5050
+ * Unified track-lifecycle event: ONE category carrying `phase:
5051
+ * 'start' | 'update' | 'end'` with a rich, typed
5052
+ * `PipelineAnalyticsTrackLifecyclePayload`. Supersedes the thin
5053
+ * `PipelineAnalyticsTrackStarted` / `PipelineAnalyticsTrackEnded`
5054
+ * pair — a consumer subscribes once and branches on `phase`.
5055
+ */
5056
+ EventCategory["PipelineAnalyticsTrackLifecycle"] = "pipeline-analytics.track-lifecycle";
5057
+ /**
5058
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5059
+ * (`phase:'start'`). Kept as a soft alias — no known subscribers.
5060
+ */
5043
5061
  EventCategory["PipelineAnalyticsTrackStarted"] = "pipeline-analytics.track-started";
5062
+ /**
5063
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5064
+ * (`phase:'end'`). Kept as a soft alias — no known subscribers.
5065
+ */
5044
5066
  EventCategory["PipelineAnalyticsTrackEnded"] = "pipeline-analytics.track-ended";
5045
5067
  EventCategory["PipelineAnalyticsDetectionEvent"] = "pipeline-analytics.detection-event";
5046
5068
  EventCategory["PipelineAnalyticsFrameTracked"] = "pipeline-analytics.frame-tracked";
5069
+ /**
5070
+ * Fired by `addon-post-analysis` when a parked (stationary) object appears
5071
+ * (a track was promoted to a stationary-registry entry) or departs (the
5072
+ * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
5073
+ * the dedicated "Stationary" section — never the live event feed. Payload:
5074
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
5075
+ */
5076
+ EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
5077
+ /**
5078
+ * Fired by `addon-post-analysis` whenever a gallery face row changes:
5079
+ * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
5080
+ * `'unassigned'` its identity link changed, `'deleted'` the row was
5081
+ * removed. Telemetry semantics (D8): a lost event only delays a refresh,
5082
+ * never a correctness issue. Payload `PipelineAnalyticsFaceGalleryChangedPayload`.
5083
+ */
5084
+ EventCategory["PipelineAnalyticsFaceGalleryChanged"] = "pipeline-analytics.face-gallery-changed";
5085
+ /**
5086
+ * Fired by `addon-post-analysis` whenever a gallery plate row changes:
5087
+ * `kind:'buffered'` a new read was persisted (`PlateRecognizer.onTrackEnd`),
5088
+ * `'assigned'` / `'unassigned'` its vehicle link changed
5089
+ * (`PlateGalleryProvider`), `'deleted'` the row was removed. Telemetry
5090
+ * semantics (D8): a lost event only delays a refresh, never a correctness
5091
+ * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
5092
+ */
5093
+ EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
5047
5094
  EventCategory["FrigateLiveEvent"] = "frigate.live-event";
5048
5095
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
5049
5096
  /**
@@ -6884,7 +6931,8 @@ var MODEL_FORMATS = [
6884
6931
  "coreml",
6885
6932
  "openvino",
6886
6933
  "tflite",
6887
- "pt"
6934
+ "pt",
6935
+ "gguf"
6888
6936
  ];
6889
6937
  /**
6890
6938
  * Multi-file format payload.
@@ -6928,7 +6976,8 @@ var ModelFormatsSchema = object({
6928
6976
  coreml: ModelFormatEntrySchema.optional(),
6929
6977
  openvino: ModelFormatEntrySchema.optional(),
6930
6978
  tflite: ModelFormatEntrySchema.optional(),
6931
- pt: ModelFormatEntrySchema.optional()
6979
+ pt: ModelFormatEntrySchema.optional(),
6980
+ gguf: ModelFormatEntrySchema.optional()
6932
6981
  });
6933
6982
  /**
6934
6983
  * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
@@ -6969,6 +7018,15 @@ var ModelCatalogEntrySchema = object({
6969
7018
  width: number(),
6970
7019
  height: number()
6971
7020
  }),
7021
+ /**
7022
+ * Channel count of the model input tensor. Omit ⇒ 3 (RGB), the default for
7023
+ * every detector / classifier / embedder. Set to 1 for a grayscale CTC text
7024
+ * recognizer (EasyOCR VGG plate-OCR: input `[N,1,H,W]`) so the preprocess
7025
+ * feeds a single-channel, EasyOCR-normalized tensor instead of the default
7026
+ * 3-channel RGB one. Threaded through `PoolModelConfig.inputChannels` to the
7027
+ * Python inference pool.
7028
+ */
7029
+ inputChannels: number().int().positive().optional(),
6972
7030
  labels: array(LabelDefinitionSchema).readonly(),
6973
7031
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
6974
7032
  inputNormalization: _enum([
@@ -6978,6 +7036,16 @@ var ModelCatalogEntrySchema = object({
6978
7036
  ]).optional(),
6979
7037
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
6980
7038
  /**
7039
+ * Per-MODEL postprocessor override. Absent ⇒ the step's own
7040
+ * `StepDefinition.postprocessor` applies (the normal case — every model in a
7041
+ * step shares its decode). Set it when a step hosts models with DIFFERENT raw
7042
+ * output layouts under one slot: e.g. object-detection is `'yolo'` by default,
7043
+ * but a Coral SSD MobileNet build emits the `TFLite_Detection_PostProcess`
7044
+ * 4-tensor layout and needs `'ssd'`. Threaded into `PoolModelConfig.postprocessor`
7045
+ * by the engine factory (`modelEntry.postprocessor ?? def.postprocessor`).
7046
+ */
7047
+ postprocessor: custom().optional(),
7048
+ /**
6981
7049
  * When true, the executor produces a landmark-aligned crop (similarity warp
6982
7050
  * onto the canonical template) before this step runs, instead of a plain
6983
7051
  * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
@@ -10423,7 +10491,8 @@ var ModelFormatSchema$1 = _enum([
10423
10491
  "coreml",
10424
10492
  "openvino",
10425
10493
  "tflite",
10426
- "pt"
10494
+ "pt",
10495
+ "gguf"
10427
10496
  ]);
10428
10497
  var PipelineSlotSchema = _enum([
10429
10498
  "detector",
@@ -10517,7 +10586,8 @@ var EngineProvisioningSchema = object({
10517
10586
  runtimeId: _enum([
10518
10587
  "onnx",
10519
10588
  "openvino",
10520
- "coreml"
10589
+ "coreml",
10590
+ "edgetpu"
10521
10591
  ]).nullable(),
10522
10592
  device: string().nullable(),
10523
10593
  state: _enum([
@@ -11729,6 +11799,140 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
11729
11799
  deviceId: number(),
11730
11800
  status: PtzAutotrackStatusSchema
11731
11801
  });
11802
+ /**
11803
+ * scene-monitor — device-scoped reference-region state cap. An operator marks
11804
+ * a rect ROI on a camera frame and names one or more states; the engine
11805
+ * (pipeline-analytics, designated post-processing node) reports which state is
11806
+ * active on an ongoing basis. Two operator-selectable check modes share every-
11807
+ * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
11808
+ * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
11809
+ *
11810
+ * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) — the framework
11811
+ * derives the device-detail contribution; the provider carries NO hand-written
11812
+ * settings-contribution methods. `status.kind:'push'` — the engine pushes on
11813
+ * every hysteresis flip / availability change; consumers never poll.
11814
+ */
11815
+ /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
11816
+ * be added without a wire break (matching falls back to any-condition refs). */
11817
+ var SceneConditionSchema = string();
11818
+ /** One captured reference — condition-tagged, model-version-gated. `embedding`
11819
+ * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
11820
+ var SceneReferenceSchema = object({
11821
+ embedding: array(number()),
11822
+ modelId: string(),
11823
+ condition: SceneConditionSchema,
11824
+ capturedAt: number(),
11825
+ thumbnailMediaId: string().optional()
11826
+ });
11827
+ var SceneMonitorStateSchema = object({
11828
+ id: string(),
11829
+ label: string(),
11830
+ references: array(SceneReferenceSchema),
11831
+ textPrompts: array(string()).optional(),
11832
+ referenceCount: number(),
11833
+ currentlyMatched: boolean()
11834
+ });
11835
+ /** Per-mode comparator params in a discriminated union so an `llm` scene never
11836
+ * carries similarity knobs and vice-versa. */
11837
+ var SceneCheckSchema = discriminatedUnion("mode", [object({
11838
+ mode: literal("similarity"),
11839
+ threshold: number().min(0).max(1),
11840
+ hysteresisCount: number().int().positive()
11841
+ }), object({
11842
+ mode: literal("llm"),
11843
+ prompt: string(),
11844
+ profileId: string().optional(),
11845
+ hysteresisCount: number().int().positive()
11846
+ })]);
11847
+ var SceneMonitorSchema = object({
11848
+ id: string(),
11849
+ label: string(),
11850
+ roi: MaskRectShapeSchema,
11851
+ enabled: boolean(),
11852
+ triggerMode: _enum([
11853
+ "periodic",
11854
+ "on-motion",
11855
+ "both"
11856
+ ]),
11857
+ checkIntervalSec: number().optional(),
11858
+ check: SceneCheckSchema,
11859
+ states: array(SceneMonitorStateSchema),
11860
+ currentStateId: string().nullable(),
11861
+ lastCheckedAt: number().nullable(),
11862
+ lastConfidence: number().nullable(),
11863
+ currentCondition: SceneConditionSchema.nullable(),
11864
+ availability: _enum(["ok", "unavailable"]),
11865
+ unavailableReason: string().nullable()
11866
+ });
11867
+ var SceneMonitorStatusSchema = object({
11868
+ monitors: array(SceneMonitorSchema),
11869
+ lastFetchedAt: number()
11870
+ });
11871
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
11872
+ deviceId: number(),
11873
+ label: string(),
11874
+ roi: MaskRectShapeSchema,
11875
+ check: SceneCheckSchema,
11876
+ triggerMode: _enum([
11877
+ "periodic",
11878
+ "on-motion",
11879
+ "both"
11880
+ ]).optional(),
11881
+ checkIntervalSec: number().optional()
11882
+ }), SceneMonitorSchema, {
11883
+ kind: "mutation",
11884
+ auth: "admin"
11885
+ }), method(object({
11886
+ deviceId: number(),
11887
+ monitorId: string(),
11888
+ patch: object({
11889
+ label: string().optional(),
11890
+ roi: MaskRectShapeSchema.optional(),
11891
+ enabled: boolean().optional(),
11892
+ triggerMode: _enum([
11893
+ "periodic",
11894
+ "on-motion",
11895
+ "both"
11896
+ ]).optional(),
11897
+ checkIntervalSec: number().optional(),
11898
+ check: SceneCheckSchema.optional()
11899
+ })
11900
+ }), _void(), {
11901
+ kind: "mutation",
11902
+ auth: "admin"
11903
+ }), method(object({
11904
+ deviceId: number(),
11905
+ monitorId: string()
11906
+ }), _void(), {
11907
+ kind: "mutation",
11908
+ auth: "admin"
11909
+ }), method(object({
11910
+ deviceId: number(),
11911
+ monitorId: string(),
11912
+ stateId: string().optional(),
11913
+ label: string().optional(),
11914
+ condition: SceneConditionSchema.optional()
11915
+ }), object({
11916
+ stateId: string(),
11917
+ referenceCount: number()
11918
+ }), {
11919
+ kind: "mutation",
11920
+ auth: "admin"
11921
+ }), method(object({
11922
+ deviceId: number(),
11923
+ monitorId: string(),
11924
+ stateId: string(),
11925
+ index: number()
11926
+ }), _void(), {
11927
+ kind: "mutation",
11928
+ auth: "admin"
11929
+ }), method(object({
11930
+ deviceId: number(),
11931
+ monitorId: string()
11932
+ }), _void(), {
11933
+ kind: "mutation",
11934
+ auth: "admin"
11935
+ });
11732
11936
  object({
11733
11937
  /** Whether the script is currently executing. */
11734
11938
  isRunning: boolean(),
@@ -12136,6 +12340,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
12136
12340
  * with the per-track detail panel and live overlay. */
12137
12341
  trackIds: array(string()).readonly()
12138
12342
  });
12343
+ /**
12344
+ * A parked ("stationary") object surfaced alongside occupancy — an object that
12345
+ * settled and stopped moving. It is NO LONGER a tracked object (the tracker was
12346
+ * told to forget it so it stops re-spawning tracks/events), but it IS still
12347
+ * physically present, so it keeps counting toward `frame` occupancy and is
12348
+ * listed here so the UI can show it in a dedicated "Stationary" section instead
12349
+ * of flooding the live event feed.
12350
+ */
12351
+ var StationaryObjectSchema = object({
12352
+ id: string(),
12353
+ className: string(),
12354
+ bbox: object({
12355
+ x: number(),
12356
+ y: number(),
12357
+ w: number(),
12358
+ h: number()
12359
+ }),
12360
+ frameWidth: number().int().nonnegative(),
12361
+ frameHeight: number().int().nonnegative(),
12362
+ /** When the source track was first seen. */
12363
+ firstSeenAt: number().int(),
12364
+ /** When the object was recognised as parked (promotion time). */
12365
+ becameStationaryAt: number().int(),
12366
+ /** Last frame a detection confirmed the object is still there. */
12367
+ lastConfirmedAt: number().int(),
12368
+ /** Enrichment label carried from the source track (identity / plate). */
12369
+ label: string().optional(),
12370
+ /** Native-resolution key-frame media key for the parked object's best image. */
12371
+ keyFrameMediaKey: string().optional()
12372
+ });
12139
12373
  var CameraOccupancySnapshotSchema = object({
12140
12374
  /** Frame timestamp of the inference result that produced this snapshot. */
12141
12375
  ts: number().int(),
@@ -12144,10 +12378,15 @@ var CameraOccupancySnapshotSchema = object({
12144
12378
  frameHeight: number().int().nonnegative(),
12145
12379
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
12146
12380
  zones: array(ZoneScopeBreakdownSchema).readonly(),
12147
- /** Frame-wide aggregate (everywhere, regardless of zone membership). */
12381
+ /** Frame-wide aggregate (everywhere, regardless of zone membership).
12382
+ * INCLUDES currently-confirmed stationary objects (they are still present). */
12148
12383
  frame: PerScopeBreakdownSchema,
12149
12384
  /** Detections that landed outside every zone. Empty when no zones defined. */
12150
- unzoned: PerScopeBreakdownSchema
12385
+ unzoned: PerScopeBreakdownSchema,
12386
+ /** Parked objects on this camera (additive — absent on legacy snapshots).
12387
+ * Surfaced separately so the UI shows them in a dedicated section rather
12388
+ * than as repeated tracks/events. */
12389
+ stationaryObjects: array(StationaryObjectSchema).readonly().optional()
12151
12390
  });
12152
12391
  /**
12153
12392
  * Time-series resolution. The history methods return one bucket per
@@ -12750,7 +12989,12 @@ var NotificationRuleConditionsSchema = object({
12750
12989
  clipDescription: object({
12751
12990
  text: string().min(1),
12752
12991
  minSimilarity: number().min(0).max(1)
12753
- }).optional()
12992
+ }).optional(),
12993
+ /** Match events whose recognized-entity label (face identity name or plate
12994
+ * vehicle name, propagated onto `event.data.label`) is one of these values.
12995
+ * Empty/absent → unaffected (back-compat). Enables "notify me when <named
12996
+ * vehicle/person> is seen". */
12997
+ labels: array(string()).readonly().optional()
12754
12998
  });
12755
12999
  var NotificationRuleTemplateSchema = object({
12756
13000
  title: string(),
@@ -13045,11 +13289,19 @@ method(object({
13045
13289
  * login page needs NO change.
13046
13290
  *
13047
13291
  * - `widget` — a Module-Federation widget the login page mounts (via
13048
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13049
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13050
- * addon bundle. The referenced widget also declares `preAuth: true` in
13051
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13052
- * stamps a public `bundleUrl` from `addonId` + `bundle`.
13292
+ * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
13293
+ * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
13294
+ * mechanism kept for future use; no shipped addon uses it on the login
13295
+ * page (the passkey ceremony below runs natively in the shell instead).
13296
+ *
13297
+ * - `passkey` — a declarative WebAuthn ceremony the shell renders
13298
+ * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
13299
+ * a remotely-loaded bundle). Carries the addon's effective `rpId` /
13300
+ * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
13301
+ * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
13302
+ * fetching any remote code pre-auth. Contribution stays unconditional —
13303
+ * enrollment state is never leaked pre-auth; visibility is a shell
13304
+ * decision.
13053
13305
  *
13054
13306
  * Every contribution carries a `stage`:
13055
13307
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -13063,30 +13315,45 @@ method(object({
13063
13315
  */
13064
13316
  /** When a login method renders in the two-phase login flow. */
13065
13317
  var LoginStageEnum = _enum(["primary", "second-factor"]);
13066
- /** One login-method contribution — redirect button OR pre-auth widget. */
13067
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13068
- kind: literal("redirect"),
13069
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13070
- id: string(),
13071
- /** Operator-facing button label. */
13072
- label: string(),
13073
- /** lucide-react icon name. */
13074
- icon: string().optional(),
13075
- /** Addon-owned HTTP route the button navigates to (GET). */
13076
- startUrl: string(),
13077
- stage: LoginStageEnum
13078
- }), object({
13079
- kind: literal("widget"),
13080
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13081
- id: string(),
13082
- /** Owning addon id drives the public bundle URL + the MF namespace. */
13083
- addonId: string(),
13084
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13085
- bundle: string(),
13086
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
13087
- remote: WidgetRemoteSchema,
13088
- stage: LoginStageEnum
13089
- })]);
13318
+ /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
13319
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [
13320
+ object({
13321
+ kind: literal("redirect"),
13322
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13323
+ id: string(),
13324
+ /** Operator-facing button label. */
13325
+ label: string(),
13326
+ /** lucide-react icon name. */
13327
+ icon: string().optional(),
13328
+ /** Addon-owned HTTP route the button navigates to (GET). */
13329
+ startUrl: string(),
13330
+ stage: LoginStageEnum
13331
+ }),
13332
+ object({
13333
+ kind: literal("widget"),
13334
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13335
+ id: string(),
13336
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13337
+ addonId: string(),
13338
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13339
+ bundle: string(),
13340
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13341
+ remote: WidgetRemoteSchema,
13342
+ stage: LoginStageEnum
13343
+ }),
13344
+ object({
13345
+ kind: literal("passkey"),
13346
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
13347
+ id: string(),
13348
+ /** Operator-facing button label. */
13349
+ label: string(),
13350
+ stage: LoginStageEnum,
13351
+ /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
13352
+ rpId: string(),
13353
+ /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
13354
+ origin: string().nullable()
13355
+ })
13356
+ ]);
13090
13357
  method(_void(), array(LoginMethodContributionSchema).readonly());
13091
13358
  /**
13092
13359
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -15007,14 +15274,14 @@ var TargetKindCapsSchema = object({
15007
15274
  * the union is large and not meant for runtime validation here; the exported
15008
15275
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
15009
15276
  */
15010
- var ConfigSchemaPassthrough = unknown();
15277
+ var ConfigSchemaPassthrough$1 = unknown();
15011
15278
  var TargetKindSchema = object({
15012
15279
  kind: string(),
15013
15280
  label: string(),
15014
15281
  icon: string(),
15015
15282
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
15016
15283
  addonId: string(),
15017
- configSchema: ConfigSchemaPassthrough,
15284
+ configSchema: ConfigSchemaPassthrough$1,
15018
15285
  supportsDiscovery: boolean(),
15019
15286
  caps: TargetKindCapsSchema
15020
15287
  });
@@ -15067,6 +15334,298 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
15067
15334
  enabled: boolean()
15068
15335
  }), _void(), { kind: "mutation" });
15069
15336
  /**
15337
+ * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15338
+ * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15339
+ * caps stay wire-compatible without a circular cap→cap import.
15340
+ *
15341
+ * Errors are a discriminated-union RESULT, never thrown: the shape survives
15342
+ * every transport tier structurally, and failed calls still write usage rows.
15343
+ * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15344
+ */
15345
+ var LlmUsageSchema = object({
15346
+ inputTokens: number(),
15347
+ outputTokens: number()
15348
+ });
15349
+ var LlmErrorCodeSchema = _enum([
15350
+ "timeout",
15351
+ "rate-limited",
15352
+ "auth",
15353
+ "refusal",
15354
+ "bad-request",
15355
+ "unavailable",
15356
+ "no-profile",
15357
+ "budget-exceeded",
15358
+ "adapter-error"
15359
+ ]);
15360
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15361
+ ok: literal(true),
15362
+ text: string(),
15363
+ model: string(),
15364
+ usage: LlmUsageSchema,
15365
+ truncated: boolean(),
15366
+ latencyMs: number()
15367
+ }), object({
15368
+ ok: literal(false),
15369
+ code: LlmErrorCodeSchema,
15370
+ message: string(),
15371
+ retryAfterMs: number().optional()
15372
+ })]);
15373
+ /**
15374
+ * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
15375
+ * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15376
+ * notification-output.cap.ts:27-31 precedents).
15377
+ */
15378
+ var LlmImageSchema = object({
15379
+ bytes: _instanceof(Uint8Array),
15380
+ mimeType: string()
15381
+ });
15382
+ var LlmGenerateBaseInputSchema = object({
15383
+ /** Collection routing (the notification-output posture). */
15384
+ addonId: string().optional(),
15385
+ /** Explicit profile; else the resolution chain (spec §3). */
15386
+ profileId: string().optional(),
15387
+ /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15388
+ consumer: string(),
15389
+ system: string().optional(),
15390
+ /** v1: single-turn. `messages[]` is a v2 additive field. */
15391
+ prompt: string(),
15392
+ /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15393
+ jsonSchema: record(string(), unknown()).optional(),
15394
+ /** Per-call override of the profile default. */
15395
+ maxTokens: number().int().positive().optional(),
15396
+ temperature: number().optional()
15397
+ });
15398
+ /**
15399
+ * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
15400
+ * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15401
+ * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
15402
+ * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15403
+ * this only through the `llm` cap's methods.
15404
+ *
15405
+ * One running llama-server child per node in v1 (models are RAM-heavy).
15406
+ * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15407
+ * watchdog — operator decision #3).
15408
+ */
15409
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
15410
+ object({
15411
+ kind: literal("catalog"),
15412
+ catalogId: string()
15413
+ }),
15414
+ object({
15415
+ kind: literal("url"),
15416
+ url: string(),
15417
+ sha256: string().optional()
15418
+ }),
15419
+ object({
15420
+ kind: literal("path"),
15421
+ path: string()
15422
+ })
15423
+ ]);
15424
+ var ManagedRuntimeConfigSchema = object({
15425
+ /** WHERE the runtime lives — hub or any agent. */
15426
+ nodeId: string(),
15427
+ /** Closed for v1; 'ollama' is a v2 candidate. */
15428
+ engine: _enum(["llama-cpp"]),
15429
+ model: ManagedModelRefSchema,
15430
+ contextSize: number().int().default(4096),
15431
+ /** 0 = CPU-only. */
15432
+ gpuLayers: number().int().default(0),
15433
+ /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15434
+ threads: number().int().optional(),
15435
+ /** Concurrent slots. */
15436
+ parallel: number().int().default(1),
15437
+ /** Else lazy: first generate boots it. */
15438
+ autoStart: boolean().default(false),
15439
+ /** 0 = never; frees RAM after quiet periods. */
15440
+ idleStopMinutes: number().int().default(30)
15441
+ });
15442
+ var LlmRuntimeStatusSchema = object({
15443
+ /** Status is ALWAYS node-qualified. */
15444
+ nodeId: string(),
15445
+ state: _enum([
15446
+ "stopped",
15447
+ "downloading",
15448
+ "starting",
15449
+ "ready",
15450
+ "crashed",
15451
+ "failed"
15452
+ ]),
15453
+ pid: number().optional(),
15454
+ port: number().optional(),
15455
+ modelPath: string().optional(),
15456
+ modelId: string().optional(),
15457
+ downloadProgress: number().min(0).max(1).optional(),
15458
+ lastError: string().optional(),
15459
+ crashesInWindow: number(),
15460
+ /** Child RSS (sampled best-effort). */
15461
+ memoryBytes: number().optional(),
15462
+ vramBytes: number().optional()
15463
+ });
15464
+ var LlmNodeModelSchema = object({
15465
+ file: string(),
15466
+ sizeBytes: number(),
15467
+ catalogId: string().optional(),
15468
+ installedAt: number().optional()
15469
+ });
15470
+ var LlmRuntimeDiskUsageSchema = object({
15471
+ nodeId: string(),
15472
+ modelsBytes: number(),
15473
+ freeBytes: number().optional()
15474
+ });
15475
+ method(LlmGenerateBaseInputSchema.extend({
15476
+ images: array(LlmImageSchema).optional(),
15477
+ runtime: ManagedRuntimeConfigSchema,
15478
+ /** The managed profile's timeout, threaded by the hub provider. */
15479
+ timeoutMs: number().int().positive().optional()
15480
+ }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15481
+ kind: "mutation",
15482
+ auth: "admin"
15483
+ }), method(object({}), _void(), {
15484
+ kind: "mutation",
15485
+ auth: "admin"
15486
+ }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15487
+ kind: "mutation",
15488
+ auth: "admin"
15489
+ }), method(object({ file: string() }), _void(), {
15490
+ kind: "mutation",
15491
+ auth: "admin"
15492
+ }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15493
+ /**
15494
+ * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15495
+ * methods concat-fan across providers; single-row methods route to ONE
15496
+ * provider by the `addonId` in the call input (the notification-output
15497
+ * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15498
+ * (hub-placed); the cap stays open for future providers.
15499
+ *
15500
+ * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15501
+ * `apiKey` is a password field — providers REDACT it on read and merge on
15502
+ * write; a stored key NEVER round-trips to a client.
15503
+ */
15504
+ var LlmProfileKindSchema = _enum([
15505
+ "openai-compatible",
15506
+ "openai",
15507
+ "anthropic",
15508
+ "google",
15509
+ "managed-local"
15510
+ ]);
15511
+ var LlmProfileSchema = object({
15512
+ id: string(),
15513
+ name: string(),
15514
+ kind: LlmProfileKindSchema,
15515
+ /** Stamped by the provider — keeps the fanned catalog routable. */
15516
+ addonId: string(),
15517
+ enabled: boolean(),
15518
+ /** Vendor model id, or the managed runtime's loaded model. */
15519
+ model: string(),
15520
+ /** Required for openai-compatible; override for cloud kinds. */
15521
+ baseUrl: string().optional(),
15522
+ /** ConfigUISchema type:'password' — never round-trips (spec §5). */
15523
+ apiKey: string().optional(),
15524
+ supportsVision: boolean(),
15525
+ temperature: number().min(0).max(2).optional(),
15526
+ maxTokens: number().int().positive().optional(),
15527
+ timeoutMs: number().int().positive().default(6e4),
15528
+ extraHeaders: record(string(), string()).optional(),
15529
+ /** kind === 'managed-local' only (spec §4). */
15530
+ runtime: ManagedRuntimeConfigSchema.optional()
15531
+ });
15532
+ /** ConfigUISchema tree passed through untyped on the wire (the
15533
+ * notification-output `ConfigSchemaPassthrough` precedent at
15534
+ * notification-output.cap.ts:151); the exported TS type re-tightens it. */
15535
+ var ConfigSchemaPassthrough = unknown();
15536
+ var LlmProfileKindDescriptorSchema = object({
15537
+ kind: LlmProfileKindSchema,
15538
+ label: string(),
15539
+ icon: string(),
15540
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
15541
+ addonId: string(),
15542
+ configSchema: ConfigSchemaPassthrough
15543
+ });
15544
+ var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
15545
+ var LlmDefaultSchema = object({
15546
+ selector: LlmDefaultSelectorSchema,
15547
+ profileId: string()
15548
+ });
15549
+ /** Server-side rollup row — getUsage never dumps raw call rows (spec §6). */
15550
+ var LlmUsageRollupSchema = object({
15551
+ day: string(),
15552
+ consumer: string(),
15553
+ profileId: string(),
15554
+ calls: number(),
15555
+ okCalls: number(),
15556
+ errorCalls: number(),
15557
+ inputTokens: number(),
15558
+ outputTokens: number(),
15559
+ avgLatencyMs: number()
15560
+ });
15561
+ /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
15562
+ var ManagedModelCatalogEntrySchema = object({
15563
+ id: string(),
15564
+ label: string(),
15565
+ family: string(),
15566
+ purpose: _enum(["text", "vision"]),
15567
+ url: string(),
15568
+ sha256: string(),
15569
+ sizeBytes: number(),
15570
+ quantization: string(),
15571
+ /** Load-time guidance shown in the picker. */
15572
+ minRamBytes: number(),
15573
+ contextSizeDefault: number().int(),
15574
+ /** Vision models: companion projector file. */
15575
+ mmprojUrl: string().optional()
15576
+ });
15577
+ var LlmRuntimeNodeSchema = object({
15578
+ nodeId: string(),
15579
+ reachable: boolean(),
15580
+ status: LlmRuntimeStatusSchema.optional(),
15581
+ disk: LlmRuntimeDiskUsageSchema.optional(),
15582
+ error: string().optional()
15583
+ });
15584
+ var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15585
+ var ProfileRefInputSchema = object({
15586
+ addonId: string(),
15587
+ profileId: string()
15588
+ });
15589
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
15590
+ kind: "mutation",
15591
+ auth: "admin"
15592
+ }), method(ProfileRefInputSchema, _void(), {
15593
+ kind: "mutation",
15594
+ auth: "admin"
15595
+ }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
15596
+ kind: "mutation",
15597
+ auth: "admin"
15598
+ }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
15599
+ selector: LlmDefaultSelectorSchema,
15600
+ profileId: string().nullable()
15601
+ }), _void(), {
15602
+ kind: "mutation",
15603
+ auth: "admin"
15604
+ }), method(object({
15605
+ since: number().optional(),
15606
+ until: number().optional(),
15607
+ consumer: string().optional(),
15608
+ profileId: string().optional()
15609
+ }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
15610
+ nodeId: string(),
15611
+ model: ManagedModelRefSchema
15612
+ }), _void(), {
15613
+ kind: "mutation",
15614
+ auth: "admin"
15615
+ }), method(object({
15616
+ nodeId: string(),
15617
+ file: string()
15618
+ }), _void(), {
15619
+ kind: "mutation",
15620
+ auth: "admin"
15621
+ }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
15622
+ kind: "mutation",
15623
+ auth: "admin"
15624
+ }), method(ProfileRefInputSchema, _void(), {
15625
+ kind: "mutation",
15626
+ auth: "admin"
15627
+ });
15628
+ /**
15070
15629
  * Zod schemas for persisted record types.
15071
15630
  *
15072
15631
  * These schemas serve as the single source of truth for types that are
@@ -15268,6 +15827,22 @@ var TrackSnapshotSchema = object({
15268
15827
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
15269
15828
  mediaKey: string()
15270
15829
  });
15830
+ /**
15831
+ * One audio-classification label heard on the track's camera while the
15832
+ * track was alive, aggregated per label. An "episode" is one persisted
15833
+ * audio event (the confident-classification path: score ≥ the device's
15834
+ * `classificationMinScore`, class-change-or-heartbeat coalesced) — NOT
15835
+ * one 32 ms inference chunk, so counts stay human-scaled.
15836
+ */
15837
+ var TrackAudioLabelSchema = object({
15838
+ label: string(),
15839
+ /** Highest classification score observed across the label's episodes. */
15840
+ peakScore: number(),
15841
+ /** Number of coalesced audio-event episodes carrying this label. */
15842
+ count: number(),
15843
+ firstAt: number(),
15844
+ lastAt: number()
15845
+ });
15271
15846
  var TrackSchema = object({
15272
15847
  trackId: string(),
15273
15848
  deviceId: number(),
@@ -15282,6 +15857,10 @@ var TrackSchema = object({
15282
15857
  snapshots: array(TrackSnapshotSchema).readonly(),
15283
15858
  /** Deduplicated zones the track has entered at least once. */
15284
15859
  zonesVisited: array(string()).readonly(),
15860
+ /** Deduplicated set of detector classes observed for this track over its
15861
+ * life (a track may be reclassified, e.g. person→vehicle). Absent on
15862
+ * legacy rows written before class accumulation shipped. */
15863
+ classes: array(string()).readonly().optional(),
15285
15864
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15286
15865
  totalDistance: number(),
15287
15866
  state: TrackStateSchema,
@@ -15295,7 +15874,11 @@ var TrackSchema = object({
15295
15874
  bestEventId: string().optional(),
15296
15875
  /** Tag of the importance sub-signal that dominated the score
15297
15876
  * (identity|dwell|proximity|class|confidence|travel|zone). */
15298
- importanceReason: string().optional()
15877
+ importanceReason: string().optional(),
15878
+ /** Audio-classification labels heard on the camera during the track's
15879
+ * life (score ≥ device `classificationMinScore`), aggregated per label.
15880
+ * Absent on legacy rows / tracks with no confident audio. */
15881
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15299
15882
  });
15300
15883
  var BaseEventFields = {
15301
15884
  id: string(),
@@ -15443,7 +16026,7 @@ var KeyEventSchema = object({
15443
16026
  /** Track lifetime in ms (lastSeen - firstSeen). */
15444
16027
  windowMs: number().optional()
15445
16028
  });
15446
- var TrackedDetectionSchema = object({
16029
+ object({
15447
16030
  trackId: string(),
15448
16031
  className: string(),
15449
16032
  confidence: number(),
@@ -15451,6 +16034,23 @@ var TrackedDetectionSchema = object({
15451
16034
  zones: array(string()).readonly(),
15452
16035
  state: TrackStateSchema
15453
16036
  });
16037
+ var OverlayDetectionSchema = looseObject({
16038
+ id: string(),
16039
+ kind: _enum(["first-level", "detail"]),
16040
+ macroClass: string(),
16041
+ score: number(),
16042
+ bbox: object({
16043
+ x: number(),
16044
+ y: number(),
16045
+ width: number(),
16046
+ height: number()
16047
+ }),
16048
+ labels: array(looseObject({
16049
+ label: string(),
16050
+ score: number()
16051
+ })).readonly(),
16052
+ parentId: string().optional()
16053
+ });
15454
16054
  var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
15455
16055
  var SearchObjectEventsInput = object({
15456
16056
  text: string(),
@@ -15461,6 +16061,20 @@ var SearchObjectEventsInput = object({
15461
16061
  limit: number().default(50),
15462
16062
  minScore: number().min(0).max(1).default(.2)
15463
16063
  });
16064
+ var TrackCascadeCountsSchema = object({
16065
+ /** Persisted track roots deleted (authoritative). */
16066
+ tracks: number().int(),
16067
+ /** Object events removed with their tracks (best-effort; see note above). */
16068
+ events: number().int(),
16069
+ /** Track/face/plate-owned media removed (best-effort). Never identity media. */
16070
+ media: number().int(),
16071
+ /** Unassigned (non-enrolled) face reads removed (best-effort). */
16072
+ faces: number().int(),
16073
+ /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16074
+ plates: number().int(),
16075
+ /** Per-track CLIP search vectors removed (best-effort). */
16076
+ embeddings: number().int()
16077
+ });
15464
16078
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
15465
16079
  deviceId: number(),
15466
16080
  trackId: string()
@@ -15492,6 +16106,24 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15492
16106
  }), {
15493
16107
  kind: "mutation",
15494
16108
  auth: "admin"
16109
+ }), method(object({
16110
+ deviceId: number(),
16111
+ cutoffMs: number()
16112
+ }), TrackCascadeCountsSchema, {
16113
+ kind: "mutation",
16114
+ auth: "admin"
16115
+ }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
16116
+ kind: "mutation",
16117
+ auth: "admin"
16118
+ }), method(object({
16119
+ deviceId: number(),
16120
+ trackIds: array(string()).min(1)
16121
+ }), object({
16122
+ deleted: number().int(),
16123
+ failed: array(string()).readonly()
16124
+ }), {
16125
+ kind: "mutation",
16126
+ auth: "admin"
15495
16127
  }), method(object({
15496
16128
  eventId: string(),
15497
16129
  kind: MediaFileKindEnum.optional()
@@ -15500,7 +16132,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15500
16132
  timestamp: number(),
15501
16133
  frameWidth: number(),
15502
16134
  frameHeight: number(),
15503
- detections: array(TrackedDetectionSchema).readonly()
16135
+ detections: array(OverlayDetectionSchema).readonly()
15504
16136
  }), object({
15505
16137
  deviceId: number(),
15506
16138
  trackId: string(),
@@ -18269,6 +18901,24 @@ DeviceType.Camera, method(object({
18269
18901
  deviceId: number(),
18270
18902
  status: OsdStatusSchema
18271
18903
  });
18904
+ var VehicleSchema = object({
18905
+ id: string(),
18906
+ name: string(),
18907
+ sampleCount: number().int().nonnegative(),
18908
+ coverMediaKey: string().optional(),
18909
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
18910
+ coverBase64: string().optional()
18911
+ });
18912
+ var VehicleSampleInfoSchema = object({
18913
+ id: string(),
18914
+ /** The plate text this sample enrolled (self-labeling — no embedding). */
18915
+ text: string(),
18916
+ /** OCR confidence of the enrolled read (0..1). */
18917
+ score: number(),
18918
+ addedAt: number().int(),
18919
+ deviceId: number().int().optional(),
18920
+ base64: string().optional()
18921
+ });
18272
18922
  var PlateInfoSchema = object({
18273
18923
  plateId: string(),
18274
18924
  deviceId: number().int(),
@@ -18280,6 +18930,16 @@ var PlateInfoSchema = object({
18280
18930
  score: number(),
18281
18931
  /** True when the text was manually corrected (exempt from retention). */
18282
18932
  corrected: boolean(),
18933
+ /** Recognized vehicle id when a matcher lookup named this read (SQL NULL → absent). */
18934
+ recognizedVehicleId: string().optional(),
18935
+ /** Resolved vehicle name for `recognizedVehicleId` (UI convenience). */
18936
+ vehicleName: string().optional(),
18937
+ /** True once the read was assigned to a vehicle (enrolled as a sample). */
18938
+ assigned: boolean(),
18939
+ /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
18940
+ plateBbox: BoundingBoxSchema.optional(),
18941
+ /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
18942
+ keyFrameMediaKey: string().optional(),
18283
18943
  base64: string().optional()
18284
18944
  });
18285
18945
  var MediaFileLiteSchema = object({
@@ -18319,6 +18979,48 @@ method(object({
18319
18979
  }), method(object({ plateId: string() }), _void(), {
18320
18980
  kind: "mutation",
18321
18981
  auth: "admin"
18982
+ }), method(_void(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
18983
+ kind: "mutation",
18984
+ auth: "admin"
18985
+ }), method(object({
18986
+ id: string(),
18987
+ name: string().min(1)
18988
+ }), _void(), {
18989
+ kind: "mutation",
18990
+ auth: "admin"
18991
+ }), method(object({ id: string() }), _void(), {
18992
+ kind: "mutation",
18993
+ auth: "admin"
18994
+ }), method(object({ vehicleId: string() }), array(VehicleSampleInfoSchema).readonly()), method(object({
18995
+ vehicleId: string(),
18996
+ sampleId: string()
18997
+ }), _void(), {
18998
+ kind: "mutation",
18999
+ auth: "admin"
19000
+ }), method(object({
19001
+ plateId: string(),
19002
+ vehicleId: string()
19003
+ }), _void(), {
19004
+ kind: "mutation",
19005
+ auth: "admin"
19006
+ }), method(object({ plateId: string() }), _void(), {
19007
+ kind: "mutation",
19008
+ auth: "admin"
19009
+ }), method(object({
19010
+ plateIds: array(string()).min(1),
19011
+ vehicleId: string()
19012
+ }), object({
19013
+ assigned: number().int(),
19014
+ failed: array(string()).readonly()
19015
+ }), {
19016
+ kind: "mutation",
19017
+ auth: "admin"
19018
+ }), method(object({ plateIds: array(string()).min(1) }), object({
19019
+ unassigned: number().int(),
19020
+ failed: array(string()).readonly()
19021
+ }), {
19022
+ kind: "mutation",
19023
+ auth: "admin"
18322
19024
  });
18323
19025
  var ModelFormatSchema = _enum(MODEL_FORMATS);
18324
19026
  var HwAccelBackendInputSchema = _enum([
@@ -18397,6 +19099,10 @@ var GpuInfoSchema = object({
18397
19099
  memoryMB: number().optional()
18398
19100
  });
18399
19101
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
19102
+ var CoralInfoSchema = object({
19103
+ type: literal("coral-edgetpu"),
19104
+ bus: string().optional()
19105
+ });
18400
19106
  var HardwareInfoSchema = object({
18401
19107
  platform: HardwarePlatformSchema,
18402
19108
  arch: HardwareArchSchema,
@@ -18405,7 +19111,8 @@ var HardwareInfoSchema = object({
18405
19111
  totalRAM_MB: number(),
18406
19112
  availableRAM_MB: number(),
18407
19113
  gpu: GpuInfoSchema.nullable(),
18408
- npu: NpuInfoSchema.nullable()
19114
+ npu: NpuInfoSchema.nullable(),
19115
+ coral: CoralInfoSchema.nullable().optional()
18409
19116
  });
18410
19117
  var PlatformScoreSchema = object({
18411
19118
  runtime: _enum(["node", "python"]),
@@ -20864,6 +21571,168 @@ Object.freeze({
20864
21571
  addonId: null,
20865
21572
  access: "create"
20866
21573
  },
21574
+ "llm.deleteModel": {
21575
+ capName: "llm",
21576
+ capScope: "system",
21577
+ addonId: null,
21578
+ access: "delete"
21579
+ },
21580
+ "llm.deleteProfile": {
21581
+ capName: "llm",
21582
+ capScope: "system",
21583
+ addonId: null,
21584
+ access: "delete"
21585
+ },
21586
+ "llm.generate": {
21587
+ capName: "llm",
21588
+ capScope: "system",
21589
+ addonId: null,
21590
+ access: "create"
21591
+ },
21592
+ "llm.generateVision": {
21593
+ capName: "llm",
21594
+ capScope: "system",
21595
+ addonId: null,
21596
+ access: "create"
21597
+ },
21598
+ "llm.getDefaults": {
21599
+ capName: "llm",
21600
+ capScope: "system",
21601
+ addonId: null,
21602
+ access: "view"
21603
+ },
21604
+ "llm.getRuntimeStatus": {
21605
+ capName: "llm",
21606
+ capScope: "system",
21607
+ addonId: null,
21608
+ access: "view"
21609
+ },
21610
+ "llm.getUsage": {
21611
+ capName: "llm",
21612
+ capScope: "system",
21613
+ addonId: null,
21614
+ access: "view"
21615
+ },
21616
+ "llm.installModel": {
21617
+ capName: "llm",
21618
+ capScope: "system",
21619
+ addonId: null,
21620
+ access: "create"
21621
+ },
21622
+ "llm.listModelCatalog": {
21623
+ capName: "llm",
21624
+ capScope: "system",
21625
+ addonId: null,
21626
+ access: "view"
21627
+ },
21628
+ "llm.listModels": {
21629
+ capName: "llm",
21630
+ capScope: "system",
21631
+ addonId: null,
21632
+ access: "view"
21633
+ },
21634
+ "llm.listNodeModels": {
21635
+ capName: "llm",
21636
+ capScope: "system",
21637
+ addonId: null,
21638
+ access: "view"
21639
+ },
21640
+ "llm.listProfileKinds": {
21641
+ capName: "llm",
21642
+ capScope: "system",
21643
+ addonId: null,
21644
+ access: "view"
21645
+ },
21646
+ "llm.listProfiles": {
21647
+ capName: "llm",
21648
+ capScope: "system",
21649
+ addonId: null,
21650
+ access: "view"
21651
+ },
21652
+ "llm.listRuntimeNodes": {
21653
+ capName: "llm",
21654
+ capScope: "system",
21655
+ addonId: null,
21656
+ access: "view"
21657
+ },
21658
+ "llm.setDefault": {
21659
+ capName: "llm",
21660
+ capScope: "system",
21661
+ addonId: null,
21662
+ access: "create"
21663
+ },
21664
+ "llm.startRuntime": {
21665
+ capName: "llm",
21666
+ capScope: "system",
21667
+ addonId: null,
21668
+ access: "create"
21669
+ },
21670
+ "llm.stopRuntime": {
21671
+ capName: "llm",
21672
+ capScope: "system",
21673
+ addonId: null,
21674
+ access: "create"
21675
+ },
21676
+ "llm.testProfile": {
21677
+ capName: "llm",
21678
+ capScope: "system",
21679
+ addonId: null,
21680
+ access: "create"
21681
+ },
21682
+ "llm.upsertProfile": {
21683
+ capName: "llm",
21684
+ capScope: "system",
21685
+ addonId: null,
21686
+ access: "create"
21687
+ },
21688
+ "llmRuntime.complete": {
21689
+ capName: "llm-runtime",
21690
+ capScope: "system",
21691
+ addonId: null,
21692
+ access: "create"
21693
+ },
21694
+ "llmRuntime.deleteModel": {
21695
+ capName: "llm-runtime",
21696
+ capScope: "system",
21697
+ addonId: null,
21698
+ access: "delete"
21699
+ },
21700
+ "llmRuntime.ensureStarted": {
21701
+ capName: "llm-runtime",
21702
+ capScope: "system",
21703
+ addonId: null,
21704
+ access: "create"
21705
+ },
21706
+ "llmRuntime.getDiskUsage": {
21707
+ capName: "llm-runtime",
21708
+ capScope: "system",
21709
+ addonId: null,
21710
+ access: "view"
21711
+ },
21712
+ "llmRuntime.installModel": {
21713
+ capName: "llm-runtime",
21714
+ capScope: "system",
21715
+ addonId: null,
21716
+ access: "create"
21717
+ },
21718
+ "llmRuntime.listLocalModels": {
21719
+ capName: "llm-runtime",
21720
+ capScope: "system",
21721
+ addonId: null,
21722
+ access: "view"
21723
+ },
21724
+ "llmRuntime.status": {
21725
+ capName: "llm-runtime",
21726
+ capScope: "system",
21727
+ addonId: null,
21728
+ access: "view"
21729
+ },
21730
+ "llmRuntime.stop": {
21731
+ capName: "llm-runtime",
21732
+ capScope: "system",
21733
+ addonId: null,
21734
+ access: "create"
21735
+ },
20867
21736
  "localNetwork.getAllowedAddresses": {
20868
21737
  capName: "local-network",
20869
21738
  capScope: "system",
@@ -21494,6 +22363,12 @@ Object.freeze({
21494
22363
  addonId: null,
21495
22364
  access: "delete"
21496
22365
  },
22366
+ "pipelineAnalytics.deleteTracks": {
22367
+ capName: "pipeline-analytics",
22368
+ capScope: "device",
22369
+ addonId: null,
22370
+ access: "delete"
22371
+ },
21497
22372
  "pipelineAnalytics.getActiveTracks": {
21498
22373
  capName: "pipeline-analytics",
21499
22374
  capScope: "device",
@@ -21560,12 +22435,24 @@ Object.freeze({
21560
22435
  addonId: null,
21561
22436
  access: "create"
21562
22437
  },
22438
+ "pipelineAnalytics.pruneTracksBefore": {
22439
+ capName: "pipeline-analytics",
22440
+ capScope: "device",
22441
+ addonId: null,
22442
+ access: "create"
22443
+ },
21563
22444
  "pipelineAnalytics.searchObjectEvents": {
21564
22445
  capName: "pipeline-analytics",
21565
22446
  capScope: "device",
21566
22447
  addonId: null,
21567
22448
  access: "view"
21568
22449
  },
22450
+ "pipelineAnalytics.wipeAllAnalytics": {
22451
+ capName: "pipeline-analytics",
22452
+ capScope: "device",
22453
+ addonId: null,
22454
+ access: "delete"
22455
+ },
21569
22456
  "pipelineExecutor.cacheFrameInPool": {
21570
22457
  capName: "pipeline-executor",
21571
22458
  capScope: "system",
@@ -22070,18 +22957,42 @@ Object.freeze({
22070
22957
  addonId: null,
22071
22958
  access: "create"
22072
22959
  },
22960
+ "plateGallery.assignPlate": {
22961
+ capName: "plate-gallery",
22962
+ capScope: "system",
22963
+ addonId: null,
22964
+ access: "create"
22965
+ },
22966
+ "plateGallery.assignPlates": {
22967
+ capName: "plate-gallery",
22968
+ capScope: "system",
22969
+ addonId: null,
22970
+ access: "create"
22971
+ },
22073
22972
  "plateGallery.correctPlateText": {
22074
22973
  capName: "plate-gallery",
22075
22974
  capScope: "system",
22076
22975
  addonId: null,
22077
22976
  access: "create"
22078
22977
  },
22978
+ "plateGallery.createVehicle": {
22979
+ capName: "plate-gallery",
22980
+ capScope: "system",
22981
+ addonId: null,
22982
+ access: "create"
22983
+ },
22079
22984
  "plateGallery.deletePlate": {
22080
22985
  capName: "plate-gallery",
22081
22986
  capScope: "system",
22082
22987
  addonId: null,
22083
22988
  access: "delete"
22084
22989
  },
22990
+ "plateGallery.deleteVehicle": {
22991
+ capName: "plate-gallery",
22992
+ capScope: "system",
22993
+ addonId: null,
22994
+ access: "delete"
22995
+ },
22085
22996
  "plateGallery.getPlateByTrack": {
22086
22997
  capName: "plate-gallery",
22087
22998
  capScope: "system",
@@ -22100,6 +23011,30 @@ Object.freeze({
22100
23011
  addonId: null,
22101
23012
  access: "view"
22102
23013
  },
23014
+ "plateGallery.listVehicles": {
23015
+ capName: "plate-gallery",
23016
+ capScope: "system",
23017
+ addonId: null,
23018
+ access: "view"
23019
+ },
23020
+ "plateGallery.listVehicleSamples": {
23021
+ capName: "plate-gallery",
23022
+ capScope: "system",
23023
+ addonId: null,
23024
+ access: "view"
23025
+ },
23026
+ "plateGallery.removeVehicleSample": {
23027
+ capName: "plate-gallery",
23028
+ capScope: "system",
23029
+ addonId: null,
23030
+ access: "delete"
23031
+ },
23032
+ "plateGallery.renameVehicle": {
23033
+ capName: "plate-gallery",
23034
+ capScope: "system",
23035
+ addonId: null,
23036
+ access: "create"
23037
+ },
22103
23038
  "plateGallery.searchPlates": {
22104
23039
  capName: "plate-gallery",
22105
23040
  capScope: "system",
@@ -22112,6 +23047,18 @@ Object.freeze({
22112
23047
  addonId: null,
22113
23048
  access: "view"
22114
23049
  },
23050
+ "plateGallery.unassignPlate": {
23051
+ capName: "plate-gallery",
23052
+ capScope: "system",
23053
+ addonId: null,
23054
+ access: "create"
23055
+ },
23056
+ "plateGallery.unassignPlates": {
23057
+ capName: "plate-gallery",
23058
+ capScope: "system",
23059
+ addonId: null,
23060
+ access: "create"
23061
+ },
22115
23062
  "platformProbe.getCapabilities": {
22116
23063
  capName: "platform-probe",
22117
23064
  capScope: "system",
@@ -22304,6 +23251,48 @@ Object.freeze({
22304
23251
  addonId: null,
22305
23252
  access: "create"
22306
23253
  },
23254
+ "sceneMonitor.captureReference": {
23255
+ capName: "scene-monitor",
23256
+ capScope: "device",
23257
+ addonId: null,
23258
+ access: "create"
23259
+ },
23260
+ "sceneMonitor.createScene": {
23261
+ capName: "scene-monitor",
23262
+ capScope: "device",
23263
+ addonId: null,
23264
+ access: "create"
23265
+ },
23266
+ "sceneMonitor.deleteReference": {
23267
+ capName: "scene-monitor",
23268
+ capScope: "device",
23269
+ addonId: null,
23270
+ access: "delete"
23271
+ },
23272
+ "sceneMonitor.deleteScene": {
23273
+ capName: "scene-monitor",
23274
+ capScope: "device",
23275
+ addonId: null,
23276
+ access: "delete"
23277
+ },
23278
+ "sceneMonitor.listScenes": {
23279
+ capName: "scene-monitor",
23280
+ capScope: "device",
23281
+ addonId: null,
23282
+ access: "view"
23283
+ },
23284
+ "sceneMonitor.recheckNow": {
23285
+ capName: "scene-monitor",
23286
+ capScope: "device",
23287
+ addonId: null,
23288
+ access: "create"
23289
+ },
23290
+ "sceneMonitor.updateScene": {
23291
+ capName: "scene-monitor",
23292
+ capScope: "device",
23293
+ addonId: null,
23294
+ access: "create"
23295
+ },
22307
23296
  "scriptRunner.run": {
22308
23297
  capName: "script-runner",
22309
23298
  capScope: "device",
@@ -23388,6 +24377,7 @@ Object.freeze({
23388
24377
  Object.freeze({
23389
24378
  "broker": "broker",
23390
24379
  "device-export": "device-export",
24380
+ "llm": "ai",
23391
24381
  "mesh-network": "mesh",
23392
24382
  "mqtt-broker": "broker",
23393
24383
  "network-access": "ingress",
@@ -23522,77 +24512,12 @@ function rankDecodeHwAccels(preferred, supportedMethods) {
23522
24512
  return candidates.sort((a, b) => decodeHwAccelRankIndex(a) - decodeHwAccelRankIndex(b));
23523
24513
  }
23524
24514
  //#endregion
23525
- //#region src/shm-orphan-purge.ts
23526
- /**
23527
- * Startup reclamation of orphaned shared-memory segments.
23528
- *
23529
- * A decoder writes frames into named `/dev/shm` segments and unlinks each one
23530
- * on graceful session teardown (`DecoderFrameRingSink.destroy`). When the
23531
- * decoder process dies *ungracefully* — SIGBUS, OOM-kill, or a SIGKILL during
23532
- * redeploy — that teardown never runs and the segment is orphaned: it stays in
23533
- * the tmpfs forever, since nothing else knows its name. Across many
23534
- * crashes/redeploys these accumulate until `/dev/shm` fills, at which point the
23535
- * next `mmap` write faults with an uncatchable SIGBUS and the decoder
23536
- * crash-loops into its circuit breaker (which is exactly the incident this
23537
- * guards against).
23538
- *
23539
- * The reclamation is safe *at process startup*: a freshly-booting decoder owns
23540
- * no live sessions, so every pre-existing segment with its prefix is by
23541
- * definition an orphan from a dead instance. `shm_unlink` only removes the
23542
- * name — any consumer still holding a mapping keeps reading valid memory until
23543
- * it closes (POSIX deferred reclaim + the ring seqlock), so unlinking is safe
23544
- * even if a stale reader is momentarily still attached.
23545
- *
23546
- * POSIX-only: segments surface as files under `/dev/shm` on Linux. On platforms
23547
- * without that directory (Windows, macOS) the scan finds nothing — no-op.
23548
- *
23549
- * NOTE: this lives inside the decoder addon (not `@camstack/shm-ring`) so it
23550
- * ships in the self-contained addon bundle via `camstack deploy`, reusing the
23551
- * already-deployed `unlinkSegment`; no host base-image rebuild required.
23552
- */
23553
- /** Default tmpfs directory where POSIX shared-memory segments appear on Linux. */
23554
- var DEFAULT_SHM_DIR = "/dev/shm";
23555
- /**
23556
- * Unlink every shared-memory segment whose name starts with `prefix`.
23557
- *
23558
- * Intended to run ONCE at decoder startup, before any session is created, to
23559
- * reclaim segments orphaned by a previously-crashed instance. A per-file unlink
23560
- * failure is swallowed so one stuck segment cannot block reclaiming the rest.
23561
- */
23562
- function purgeOrphanSegments(prefix, options = {}) {
23563
- const dir = options.dir ?? DEFAULT_SHM_DIR;
23564
- const unlink = options.unlink ?? unlinkSegment;
23565
- let entries;
23566
- try {
23567
- entries = readdirSync(dir);
23568
- } catch {
23569
- return {
23570
- scanned: 0,
23571
- removed: 0,
23572
- names: []
23573
- };
23574
- }
23575
- const names = [];
23576
- for (const name of entries) {
23577
- if (!name.startsWith(prefix)) continue;
23578
- try {
23579
- unlink(name);
23580
- names.push(name);
23581
- } catch {}
23582
- }
23583
- return {
23584
- scanned: entries.length,
23585
- removed: names.length,
23586
- names
23587
- };
23588
- }
23589
- //#endregion
23590
24515
  //#region src/shared/notifying-ring-buffer.ts
23591
24516
  /**
23592
24517
  * A {@link RingBuffer} that can notify a single blocked consumer the moment an
23593
- * item commits — the server side of the `pullFrames`/`pullHandles` long-poll.
24518
+ * item commits — the server side of the `pullFrames` long-poll.
23594
24519
  *
23595
- * The decoder's `onFrame`/`onFrameHandle` callback drives `push()`; a consumer
24520
+ * The decoder's `onFrame` callback drives `push()`; a consumer
23596
24521
  * that finds the ring momentarily empty calls {@link waitForItem} and is woken
23597
24522
  * on the very next `push()` (a real commit signal) rather than spinning at
23598
24523
  * ~1kHz on `setTimeout(…,1)`. If nothing commits it resolves after `waitMs`,
@@ -23930,19 +24855,7 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
23930
24855
  config;
23931
24856
  logger;
23932
24857
  frameCallbacks = /* @__PURE__ */ new Set();
23933
- handleCallbacks = /* @__PURE__ */ new Set();
23934
24858
  destroyed = false;
23935
- /**
23936
- * Frame delivery mode (see {@link DecoderFrameSink}). `'shm'` lazily
23937
- * constructs `frameRingSink` and routes every decoded frame through it.
23938
- */
23939
- frameSink;
23940
- /**
23941
- * The shared-memory ring writer for this stream. Created lazily on the
23942
- * first decoded frame in `'shm'` mode (the segment cannot be sized until
23943
- * the output geometry is known) and torn down in `destroy`.
23944
- */
23945
- frameRingSink = null;
23946
24859
  parser = null;
23947
24860
  codecCtx = null;
23948
24861
  scaler = null;
@@ -24063,8 +24976,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24063
24976
  startTime = Date.now();
24064
24977
  hwaccelPref;
24065
24978
  hwaccelResolver;
24066
- /** Cluster node id stamped into every `FrameHandle` the `'shm'` sink emits. */
24067
- nodeId;
24068
24979
  /** The backend that actually initialised successfully — `'none'` = software fallback. */
24069
24980
  activeHwAccel = "none";
24070
24981
  hwDevice = null;
@@ -24096,35 +25007,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24096
25007
  this.outputMode = NodeAvDecoderSession.resolveOutputMode(config.outputFormat);
24097
25008
  this.hwaccelPref = options?.hwaccel ?? "auto";
24098
25009
  this.hwaccelResolver = options?.hwaccelResolver ?? null;
24099
- this.frameSink = options?.frameSink ?? "callback";
24100
- this.nodeId = options?.nodeId ?? "local";
24101
- }
24102
- /**
24103
- * The shared-memory ring sink for this stream, or `null` before the first
24104
- * `'shm'`-mode frame lazily creates it. Exposed so the owning addon can
24105
- * surface ring stats via `decoder.getShmStats`.
24106
- */
24107
- get frameRingSinkOrNull() {
24108
- return this.frameRingSink;
24109
- }
24110
- /**
24111
- * Lazily build the shared-memory ring sink for this stream. The segment
24112
- * itself is still created lazily by the sink on its first `writeFrame`,
24113
- * once the decoded geometry is known.
24114
- */
24115
- ensureFrameRingSink() {
24116
- if (this.frameRingSink === null) {
24117
- const seedParts = [];
24118
- if (typeof this.config.deviceId === "number") seedParts.push(String(this.config.deviceId));
24119
- if (typeof this.config.tag === "string" && this.config.tag.length > 0) seedParts.push(this.config.tag);
24120
- const seed = seedParts.length > 0 ? seedParts.join(":") : "anon";
24121
- this.frameRingSink = new DecoderFrameRingSink({
24122
- seed,
24123
- logger: this.logger,
24124
- nodeId: this.nodeId
24125
- });
24126
- }
24127
- return this.frameRingSink;
24128
25010
  }
24129
25011
  /**
24130
25012
  * Resolve the backend preference list and try each one against
@@ -24748,10 +25630,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24748
25630
  }
24749
25631
  if (!this.dstFrame || !this.scaler) return;
24750
25632
  const decodeStart = performance.now();
24751
- if (this.frameSink === "shm" && this.outputMode !== "jpeg") {
24752
- this.scaleIntoRingSlot(frame, decodeStart);
24753
- return;
24754
- }
24755
25633
  try {
24756
25634
  this.dstFrame.makeWritable();
24757
25635
  this.scaler.scaleFrameSync(this.dstFrame, frame);
@@ -24796,100 +25674,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24796
25674
  else this.emitRawFrame(rawBuf, "gray", decodeStart);
24797
25675
  }
24798
25676
  /**
24799
- * Scale a decoded frame **directly into a shared-memory ring slot** — the
24800
- * zero write-side copy path (Phase 5 / D9 Task 7c).
24801
- *
24802
- * `beginFrame` reserves the slot (its seqlock open / odd — readers skip it);
24803
- * `SoftwareScaleContext.scaleSync` (the low-level `sws_scale` mapping) writes
24804
- * the packed pixels straight into the slot buffer with `linesize = width ×
24805
- * bpp` — no linesize padding, so there is nothing to strip and nothing to
24806
- * memcpy; `commitFrame` writes the metadata, closes the seqlock and publishes
24807
- * the slot. The decoded source planes feed `scaleSync` as `srcSlice` +
24808
- * `srcStride` (`frame.data` / `frame.linesize`).
24809
- *
24810
- * Used only for raw output formats (rgb/bgr/gray). The jpeg path keeps the
24811
- * `dstFrame` + sharp encode route because sharp must consume an RGB buffer
24812
- * before the final (variable-length) jpeg bytes exist.
24813
- */
24814
- scaleIntoRingSlot(frame, decodeStart) {
24815
- if (!this.scaler) return;
24816
- const format = this.outputMode === "gray" ? "gray" : "rgb";
24817
- const channels = this.outputMode === "gray" ? 1 : 3;
24818
- const sink = this.ensureFrameRingSink();
24819
- const reserved = sink.beginFrame(this.outWidth, this.outHeight, format);
24820
- if (reserved === null) {
24821
- this.droppedFrames++;
24822
- return;
24823
- }
24824
- const srcPlanes = frame.data;
24825
- if (!srcPlanes || srcPlanes.length === 0) {
24826
- sink.abortFrame(reserved.slot);
24827
- this.droppedFrames++;
24828
- return;
24829
- }
24830
- const dstStride = this.outWidth * channels;
24831
- const needed = dstStride * this.outHeight;
24832
- if (reserved.buffer.length < needed) {
24833
- this.logger.warn("node-av: ring slot too small for scaled frame — dropping", { meta: {
24834
- needed,
24835
- slotBytes: reserved.buffer.length,
24836
- width: this.outWidth,
24837
- height: this.outHeight
24838
- } });
24839
- sink.abortFrame(reserved.slot);
24840
- this.droppedFrames++;
24841
- return;
24842
- }
24843
- try {
24844
- const srcStrides = Array.from(frame.linesize).slice(0, srcPlanes.length);
24845
- this.scaler.scaleSync(Array.from(srcPlanes), srcStrides, 0, frame.height, [reserved.buffer], [dstStride]);
24846
- } catch (err) {
24847
- this.logger.warn("node-av scale-into-slot error", { meta: { error: errMsg(err) } });
24848
- sink.abortFrame(reserved.slot);
24849
- this.droppedFrames++;
24850
- return;
24851
- }
24852
- const decodeMs = performance.now() - decodeStart;
24853
- this.totalDecodeTimeMs += decodeMs;
24854
- this.outputFrames++;
24855
- this.lastEmitTime = performance.now();
24856
- if (!this.firstFrameLogged) {
24857
- this.firstFrameLogged = true;
24858
- this.logger.info("node-av: scaled directly into shm ring slot", { meta: {
24859
- phase: "frame-debug",
24860
- width: this.outWidth,
24861
- height: this.outHeight,
24862
- dstStride,
24863
- format
24864
- } });
24865
- }
24866
- if (this.outputFrames === 1 || this.outputFrames % 500 === 0) this.logger.info("node-av frame emitted", { meta: {
24867
- frameNumber: this.outputFrames,
24868
- width: this.outWidth,
24869
- height: this.outHeight,
24870
- format,
24871
- decodeMs,
24872
- sink: "shm",
24873
- subs: this.handleCallbacks.size
24874
- } });
24875
- const handle = sink.commitFrame(reserved.slot, {
24876
- width: this.outWidth,
24877
- height: this.outHeight,
24878
- format,
24879
- pts: performance.now(),
24880
- byteLength: dstStride * this.outHeight
24881
- });
24882
- if (handle === null) {
24883
- this.droppedFrames++;
24884
- return;
24885
- }
24886
- const delivered = {
24887
- handle,
24888
- timestamp: Date.now()
24889
- };
24890
- for (const cb of this.handleCallbacks) cb(delivered);
24891
- }
24892
- /**
24893
25677
  * Extract packed pixel buffer from a decoded frame.
24894
25678
  * FFmpeg's av_frame_get_buffer() may pad each row to alignment (32/64 bytes).
24895
25679
  * Sharp and WASM consumers expect tightly-packed rows (stride = width * channels).
@@ -24948,13 +25732,8 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24948
25732
  height: this.outHeight,
24949
25733
  format,
24950
25734
  decodeMs,
24951
- sink: this.frameSink,
24952
- subs: this.frameSink === "shm" ? this.handleCallbacks.size : this.frameCallbacks.size
25735
+ subs: this.frameCallbacks.size
24953
25736
  } });
24954
- if (this.frameSink === "shm") {
24955
- this.emitShmFrame(data, format);
24956
- return;
24957
- }
24958
25737
  const decodedFrame = {
24959
25738
  data,
24960
25739
  width: this.outWidth,
@@ -24964,47 +25743,12 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24964
25743
  };
24965
25744
  for (const cb of this.frameCallbacks) cb(decodedFrame);
24966
25745
  }
24967
- /**
24968
- * Write a decoded frame into this stream's shared-memory ring and deliver
24969
- * the resulting `FrameHandle` to `onFrameHandle` subscribers. No pixel
24970
- * `Buffer` is handed to callbacks — consumers open the segment and read the
24971
- * pixels zero-copy via a `FrameRingReader`.
24972
- */
24973
- emitShmFrame(data, format) {
24974
- const handle = this.ensureFrameRingSink().writeFrame(data, {
24975
- width: this.outWidth,
24976
- height: this.outHeight,
24977
- format,
24978
- pts: performance.now(),
24979
- byteLength: data.byteLength
24980
- });
24981
- if (handle === null) {
24982
- this.droppedFrames++;
24983
- return;
24984
- }
24985
- const frame = {
24986
- handle,
24987
- timestamp: Date.now()
24988
- };
24989
- for (const cb of this.handleCallbacks) cb(frame);
24990
- }
24991
25746
  onFrame(callback) {
24992
25747
  this.frameCallbacks.add(callback);
24993
25748
  return () => {
24994
25749
  this.frameCallbacks.delete(callback);
24995
25750
  };
24996
25751
  }
24997
- /**
24998
- * Subscribe to shared-memory frame handles. Fires only when the session was
24999
- * created with `frameSink: 'shm'`; in the legacy `'callback'` mode no
25000
- * handles are produced and this subscription never fires.
25001
- */
25002
- onFrameHandle(callback) {
25003
- this.handleCallbacks.add(callback);
25004
- return () => {
25005
- this.handleCallbacks.delete(callback);
25006
- };
25007
- }
25008
25752
  updateConfig(update) {
25009
25753
  const prevFormat = this.config.outputFormat;
25010
25754
  this.config = {
@@ -25042,7 +25786,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
25042
25786
  if (this.destroyed) return;
25043
25787
  this.destroyed = true;
25044
25788
  this.frameCallbacks.clear();
25045
- this.handleCallbacks.clear();
25046
25789
  this.pullActive = false;
25047
25790
  if (this.pullRedialTimer) {
25048
25791
  clearTimeout(this.pullRedialTimer);
@@ -25058,8 +25801,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
25058
25801
  if (!this.pullHwContext.isDisposed) this.pullHwContext.dispose();
25059
25802
  this.pullHwContext = null;
25060
25803
  }
25061
- this.frameRingSink?.destroy();
25062
- this.frameRingSink = null;
25063
25804
  this.dstFrame?.[Symbol.dispose]?.();
25064
25805
  this.avFrame?.[Symbol.dispose]?.();
25065
25806
  this.avPacket?.[Symbol.dispose]?.();
@@ -25887,27 +26628,14 @@ var FRAME_BUFFER_CAPACITY = 32;
25887
26628
  var DecoderNodeAvAddon = class extends BaseAddon {
25888
26629
  /**
25889
26630
  * Sessions are stored as the concrete `NodeAvDecoderSession` (the only type
25890
- * this addon ever creates) so `getShmStats` can reach the per-session shm
25891
- * ring sink — `IDecoderSession` does not expose it.
26631
+ * this addon ever creates).
25892
26632
  */
25893
26633
  sessions = /* @__PURE__ */ new Map();
25894
- /** Pixel-frame buffers populated only for `frameSink: 'callback'` sessions. */
26634
+ /** Pixel-frame buffers drained by `pullFrames`. */
25895
26635
  frameBuffers = /* @__PURE__ */ new Map();
25896
- /** `FrameHandle` buffers — populated only for `frameSink: 'shm'` sessions. */
25897
- handleBuffers = /* @__PURE__ */ new Map();
25898
26636
  unsubscribers = /* @__PURE__ */ new Map();
25899
26637
  sessionMeta = /* @__PURE__ */ new Map();
25900
26638
  /**
25901
- * Per-shm-segment `FrameRingReader` cache backing `getFrame`. Opens each
25902
- * named segment once and reuses the reader for every later handle on it;
25903
- * built in `onInitialize` (it needs the scoped logger) and closed in
25904
- * `onShutdown`.
25905
- */
25906
- frameReaders = null;
25907
- /** Running `getFrame` hit/miss counters surfaced via `getShmStats`. */
25908
- getFrameHits = 0;
25909
- getFrameMisses = 0;
25910
- /**
25911
26639
  * node-av audio-codec provider — registered independently of the video
25912
26640
  * decoder backend gate whenever node-av loads on this node, so this single
25913
26641
  * addon provides both the `decoder` and `audio-codec` caps.
@@ -25972,12 +26700,6 @@ var DecoderNodeAvAddon = class extends BaseAddon {
25972
26700
  return registrations;
25973
26701
  }
25974
26702
  this.ctx.logger.info("node-av decoder addon initialized", { meta: { selectedBackend: backend } });
25975
- const purged = purgeOrphanSegments(SEGMENT_NAME_PREFIX);
25976
- if (purged.removed > 0) this.ctx.logger.warn("node-av decoder: reclaimed orphaned shm segments at startup", { meta: {
25977
- removed: purged.removed,
25978
- scanned: purged.scanned
25979
- } });
25980
- this.frameReaders = new FrameRingReaderCache(this.ctx.logger);
25981
26703
  if (!this.config.probedBestHwaccel) this.reprobeHwaccel().catch((err) => {
25982
26704
  this.ctx.logger.warn("nodeav: auto-reprobe hwaccel failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
25983
26705
  });
@@ -26044,11 +26766,11 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26044
26766
  }
26045
26767
  /**
26046
26768
  * The cluster node id of this decoder — the Moleculer nodeID (`hub`,
26047
- * `dev-agent-0`, …), not the addon id. Stamped into session-owned
26048
- * `FrameHandle`s so a downstream consumer routes `getFrame` to the node
26049
- * that holds the shm ring. A hierarchical broker nodeID (`hub/...`) is
26050
- * collapsed to the cluster-visible parent; in-process boot is left as-is.
26051
- * Mirrors the resolution `PipelineRunnerAddon.onInitialize` performs.
26769
+ * `dev-agent-0`, …), not the addon id. Returned from `createSession` so the
26770
+ * caller can route session-scoped calls to the node that owns the session.
26771
+ * A hierarchical broker nodeID (`hub/...`) is collapsed to the
26772
+ * cluster-visible parent; in-process boot is left as-is. Mirrors the
26773
+ * resolution `PipelineRunnerAddon.onInitialize` performs.
26052
26774
  */
26053
26775
  resolveLocalNodeId() {
26054
26776
  const raw = this.ctx.kernel.localNodeId ?? this.ctx.id;
@@ -26057,15 +26779,12 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26057
26779
  async createSession(config) {
26058
26780
  const sessionId = randomUUID();
26059
26781
  const hwaccel = await this.resolveHwAccelPref();
26060
- const { frameSink } = config;
26061
26782
  const nodeId = this.resolveLocalNodeId();
26062
26783
  const session = new NodeAvDecoderSession(config, this.ctx.logger, {
26063
26784
  hwaccel,
26064
- hwaccelResolver: this.ctx.kernel.hwaccel,
26065
- frameSink,
26066
- nodeId
26785
+ hwaccelResolver: this.ctx.kernel.hwaccel
26067
26786
  });
26068
- const unsub = frameSink === "shm" ? this.wireShmSink(sessionId, session) : this.wireCallbackSink(sessionId, session);
26787
+ const unsub = this.wireCallbackSink(sessionId, session);
26069
26788
  this.sessions.set(sessionId, session);
26070
26789
  this.unsubscribers.set(sessionId, unsub);
26071
26790
  this.sessionMeta.set(sessionId, {
@@ -26080,7 +26799,6 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26080
26799
  sessionId,
26081
26800
  codec: config.codec,
26082
26801
  hwaccelPref: hwaccel,
26083
- frameSink,
26084
26802
  nodeId
26085
26803
  }
26086
26804
  });
@@ -26111,18 +26829,6 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26111
26829
  ringBuffer.push(capFrame);
26112
26830
  });
26113
26831
  }
26114
- /**
26115
- * Subscribe a `'shm'` session's `FrameHandle`s into a per-session ring
26116
- * buffer drained by `pullHandles`. No pixel bytes cross the cap boundary
26117
- * — the broker opens the named segment and reads the pixels zero-copy.
26118
- */
26119
- wireShmSink(sessionId, session) {
26120
- const handleRing = new NotifyingRingBuffer(FRAME_BUFFER_CAPACITY);
26121
- this.handleBuffers.set(sessionId, handleRing);
26122
- return session.onFrameHandle((frame) => {
26123
- handleRing.push(frame.handle);
26124
- });
26125
- }
26126
26832
  async destroySession(input) {
26127
26833
  const { sessionId } = input;
26128
26834
  const session = this.sessions.get(sessionId);
@@ -26130,12 +26836,10 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26130
26836
  const unsub = this.unsubscribers.get(sessionId);
26131
26837
  if (unsub) unsub();
26132
26838
  this.frameBuffers.get(sessionId)?.cancel();
26133
- this.handleBuffers.get(sessionId)?.cancel();
26134
26839
  const deviceId = this.sessionMeta.get(sessionId)?.deviceId;
26135
26840
  await session.destroy();
26136
26841
  this.sessions.delete(sessionId);
26137
26842
  this.frameBuffers.delete(sessionId);
26138
- this.handleBuffers.delete(sessionId);
26139
26843
  this.unsubscribers.delete(sessionId);
26140
26844
  this.sessionMeta.delete(sessionId);
26141
26845
  this.ctx.logger.info("node-av: destroyed session", {
@@ -26175,12 +26879,14 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26175
26879
  if (ringBuffer.size === 0 && input.waitMs) await ringBuffer.waitForItem(input.waitMs);
26176
26880
  return ringBuffer.drain(input.maxCount);
26177
26881
  }
26178
- async pullHandles(input) {
26179
- if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`);
26180
- const handleRing = this.handleBuffers.get(input.sessionId);
26181
- if (!handleRing) return [];
26182
- if (handleRing.size === 0 && input.waitMs) await handleRing.waitForItem(input.waitMs);
26183
- return handleRing.drain(input.maxCount);
26882
+ /**
26883
+ * DORMANT the shm `FrameHandle` frame plane is retired. Every session now
26884
+ * delivers pixels over the callback path drained by `pullFrames`, so no
26885
+ * handles are ever produced. Kept to satisfy the (still-defined) cap method;
26886
+ * always returns an empty list. Removal rides a later framework train.
26887
+ */
26888
+ async pullHandles() {
26889
+ return [];
26184
26890
  }
26185
26891
  async updateConfig(input) {
26186
26892
  const session = this.sessions.get(input.sessionId);
@@ -26193,50 +26899,22 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26193
26899
  return session.getStats();
26194
26900
  }
26195
26901
  /**
26196
- * Read back the pixels a `FrameHandle` refers to from this node's shm ring
26197
- * (Phase 5 / D9 downstream access). Returns `null` when the slot was
26198
- * already recycled (latest-wins drop) or the segment could not be opened.
26199
- * The reader cache opens each named segment once and reuses the reader.
26200
- *
26201
- * The reader hands back a `Buffer` view over its **reusable scratch
26202
- * buffer**, only valid until the next `read` on the same reader; the
26203
- * cap caller may keep the frame across reads, so the pixels are copied
26204
- * into a fresh detached `ArrayBuffer` here. This matches the existing
26205
- * callback-path copy in `wireCallbackSink`.
26902
+ * DORMANT shm handle read-back is retired (the decoder no longer owns a
26903
+ * `/dev/shm` ring). Kept to satisfy the (still-defined) cap method; always
26904
+ * returns `null`. Full-frame retrieval by handle now flows through the
26905
+ * runner's RAM-backed `pipelineRunner.getNativeCrop`. Removal rides a later
26906
+ * framework train.
26206
26907
  */
26207
- async getFrame(input) {
26208
- const frame = this.frameReaders?.read(input.handle) ?? null;
26209
- if (!frame) {
26210
- this.getFrameMisses += 1;
26211
- return null;
26212
- }
26213
- this.getFrameHits += 1;
26214
- const arrayBuf = new ArrayBuffer(frame.data.byteLength);
26215
- new Uint8Array(arrayBuf).set(frame.data);
26216
- return {
26217
- data: new Uint8Array(arrayBuf),
26218
- width: frame.width,
26219
- height: frame.height,
26220
- format: frame.format,
26221
- timestamp: frame.timestamp
26222
- };
26908
+ async getFrame() {
26909
+ return null;
26223
26910
  }
26224
26911
  /**
26225
- * shm ring usage stats for a `frameSink: 'shm'` session — slot geometry,
26226
- * frames written, byte budget, plus this addon's running `getFrame`
26227
- * hit/miss counters. Returns `null` for an unknown session or one whose
26228
- * shm ring has not yet been armed by a first decoded frame.
26912
+ * DORMANT — no shm ring exists to report stats for. Kept to satisfy the
26913
+ * (still-defined) cap method; always returns `null`. Removal rides a later
26914
+ * framework train.
26229
26915
  */
26230
- async getShmStats(input) {
26231
- const stats = this.sessions.get(input.sessionId)?.frameRingSinkOrNull?.getShmStats() ?? null;
26232
- if (!stats) return null;
26233
- return {
26234
- sessionId: input.sessionId,
26235
- ...stats,
26236
- budgetMb: RING_BUDGET_MB,
26237
- getFrameHits: this.getFrameHits,
26238
- getFrameMisses: this.getFrameMisses
26239
- };
26916
+ async getShmStats() {
26917
+ return null;
26240
26918
  }
26241
26919
  async onShutdown() {
26242
26920
  this.audioProvider?.stop();
@@ -26251,12 +26929,9 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26251
26929
  await Promise.all(destroyPromises);
26252
26930
  this.sessions.clear();
26253
26931
  this.frameBuffers.clear();
26254
- this.handleBuffers.clear();
26255
26932
  this.unsubscribers.clear();
26256
26933
  this.sessionMeta.clear();
26257
- this.frameReaders?.close();
26258
- this.frameReaders = null;
26259
26934
  }
26260
26935
  };
26261
26936
  //#endregion
26262
- export { DecoderFrameRingSink, DecoderNodeAvAddon, DecoderNodeAvAddon as default, NodeAvAudioCodecProvider, NodeAvAudioDecodeSession, NodeAvAudioEncodeSession, NodeAvDecoderSession, loadNodeAvRuntime, makeSegmentName, peekNodeAvRuntime };
26937
+ export { DecoderNodeAvAddon, DecoderNodeAvAddon as default, NodeAvAudioCodecProvider, NodeAvAudioDecodeSession, NodeAvAudioEncodeSession, NodeAvDecoderSession, loadNodeAvRuntime, peekNodeAvRuntime };