@camstack/addon-decoder-nodeav 1.1.10 → 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 +1118 -413
  2. package/dist/index.mjs +1119 -402
  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-BC9Yqte7.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([
@@ -10678,7 +10748,15 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10678
10748
  image: _instanceof(Uint8Array).optional(),
10679
10749
  referenceImage: string().optional(),
10680
10750
  deviceId: number().optional(),
10681
- sessionId: string().optional()
10751
+ sessionId: string().optional(),
10752
+ /**
10753
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
10754
+ * reference-image, and detail-subtree calls. 'frame' is the live
10755
+ * per-frame dispatch: ONLY root-plane steps run; crop children
10756
+ * (inputClasses ≠ null) are skipped and served per-track via
10757
+ * pipelineRunner.runDetailSubtree (two-plane design).
10758
+ */
10759
+ plane: _enum(["full", "frame"]).optional()
10682
10760
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
10683
10761
  engine: PipelineEngineChoiceSchema.optional(),
10684
10762
  steps: array(PipelineStepInputSchema).min(1),
@@ -10821,6 +10899,28 @@ var NativeCropResultSchema = object({
10821
10899
  width: number().int().positive(),
10822
10900
  height: number().int().positive()
10823
10901
  });
10902
+ /** Parent detection context passed to `runDetailSubtree` — the crop's
10903
+ * originating detection, in FRAME-space coordinates. Reuses
10904
+ * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
10905
+ * the coordinates are frame-space rather than getNativeCrop's
10906
+ * normalized [0,1] convention). */
10907
+ var DetailParentSchema = object({
10908
+ bbox: NativeCropBboxSchema,
10909
+ className: string()
10910
+ });
10911
+ /** One child-step result from `runDetailSubtree` — an embedding, label,
10912
+ * or refined detection produced by running the crop-subtree on a
10913
+ * single tracked detection. */
10914
+ var DetailResultSchema = object({
10915
+ stepId: string(),
10916
+ className: string(),
10917
+ score: number(),
10918
+ /** FRAME-space bbox (already mapped back from crop space). */
10919
+ bbox: NativeCropBboxSchema.optional(),
10920
+ embedding: string().optional(),
10921
+ label: string().optional(),
10922
+ alignedCropJpeg: string().optional()
10923
+ });
10824
10924
  /**
10825
10925
  * Per-camera tunable ranges + defaults. Single source of truth used
10826
10926
  * by both the Zod data schema (validation + default fallback) and
@@ -11081,7 +11181,13 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
11081
11181
  handle: FrameHandleSchema,
11082
11182
  bbox: NativeCropBboxSchema,
11083
11183
  maxWidth: number().int().positive().optional()
11084
- }), NativeCropResultSchema.nullable());
11184
+ }), NativeCropResultSchema.nullable()), method(object({
11185
+ deviceId: number(),
11186
+ frameHandle: FrameHandleSchema.optional(),
11187
+ cropJpeg: string().optional(),
11188
+ parent: DetailParentSchema,
11189
+ steps: array(string()).optional()
11190
+ }), object({ details: array(DetailResultSchema) }).nullable(), { kind: "mutation" });
11085
11191
  object({
11086
11192
  detected: boolean(),
11087
11193
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -11693,6 +11799,140 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
11693
11799
  deviceId: number(),
11694
11800
  status: PtzAutotrackStatusSchema
11695
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
+ });
11696
11936
  object({
11697
11937
  /** Whether the script is currently executing. */
11698
11938
  isRunning: boolean(),
@@ -12100,6 +12340,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
12100
12340
  * with the per-track detail panel and live overlay. */
12101
12341
  trackIds: array(string()).readonly()
12102
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
+ });
12103
12373
  var CameraOccupancySnapshotSchema = object({
12104
12374
  /** Frame timestamp of the inference result that produced this snapshot. */
12105
12375
  ts: number().int(),
@@ -12108,10 +12378,15 @@ var CameraOccupancySnapshotSchema = object({
12108
12378
  frameHeight: number().int().nonnegative(),
12109
12379
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
12110
12380
  zones: array(ZoneScopeBreakdownSchema).readonly(),
12111
- /** 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). */
12112
12383
  frame: PerScopeBreakdownSchema,
12113
12384
  /** Detections that landed outside every zone. Empty when no zones defined. */
12114
- 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()
12115
12390
  });
12116
12391
  /**
12117
12392
  * Time-series resolution. The history methods return one bucket per
@@ -12714,7 +12989,12 @@ var NotificationRuleConditionsSchema = object({
12714
12989
  clipDescription: object({
12715
12990
  text: string().min(1),
12716
12991
  minSimilarity: number().min(0).max(1)
12717
- }).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()
12718
12998
  });
12719
12999
  var NotificationRuleTemplateSchema = object({
12720
13000
  title: string(),
@@ -13009,11 +13289,19 @@ method(object({
13009
13289
  * login page needs NO change.
13010
13290
  *
13011
13291
  * - `widget` — a Module-Federation widget the login page mounts (via
13012
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13013
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13014
- * addon bundle. The referenced widget also declares `preAuth: true` in
13015
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13016
- * 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.
13017
13305
  *
13018
13306
  * Every contribution carries a `stage`:
13019
13307
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -13027,30 +13315,45 @@ method(object({
13027
13315
  */
13028
13316
  /** When a login method renders in the two-phase login flow. */
13029
13317
  var LoginStageEnum = _enum(["primary", "second-factor"]);
13030
- /** One login-method contribution — redirect button OR pre-auth widget. */
13031
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13032
- kind: literal("redirect"),
13033
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13034
- id: string(),
13035
- /** Operator-facing button label. */
13036
- label: string(),
13037
- /** lucide-react icon name. */
13038
- icon: string().optional(),
13039
- /** Addon-owned HTTP route the button navigates to (GET). */
13040
- startUrl: string(),
13041
- stage: LoginStageEnum
13042
- }), object({
13043
- kind: literal("widget"),
13044
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13045
- id: string(),
13046
- /** Owning addon id drives the public bundle URL + the MF namespace. */
13047
- addonId: string(),
13048
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13049
- bundle: string(),
13050
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
13051
- remote: WidgetRemoteSchema,
13052
- stage: LoginStageEnum
13053
- })]);
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
+ ]);
13054
13357
  method(_void(), array(LoginMethodContributionSchema).readonly());
13055
13358
  /**
13056
13359
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -14971,14 +15274,14 @@ var TargetKindCapsSchema = object({
14971
15274
  * the union is large and not meant for runtime validation here; the exported
14972
15275
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14973
15276
  */
14974
- var ConfigSchemaPassthrough = unknown();
15277
+ var ConfigSchemaPassthrough$1 = unknown();
14975
15278
  var TargetKindSchema = object({
14976
15279
  kind: string(),
14977
15280
  label: string(),
14978
15281
  icon: string(),
14979
15282
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
14980
15283
  addonId: string(),
14981
- configSchema: ConfigSchemaPassthrough,
15284
+ configSchema: ConfigSchemaPassthrough$1,
14982
15285
  supportsDiscovery: boolean(),
14983
15286
  caps: TargetKindCapsSchema
14984
15287
  });
@@ -15031,6 +15334,298 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
15031
15334
  enabled: boolean()
15032
15335
  }), _void(), { kind: "mutation" });
15033
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
+ /**
15034
15629
  * Zod schemas for persisted record types.
15035
15630
  *
15036
15631
  * These schemas serve as the single source of truth for types that are
@@ -15232,6 +15827,22 @@ var TrackSnapshotSchema = object({
15232
15827
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
15233
15828
  mediaKey: string()
15234
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
+ });
15235
15846
  var TrackSchema = object({
15236
15847
  trackId: string(),
15237
15848
  deviceId: number(),
@@ -15246,6 +15857,10 @@ var TrackSchema = object({
15246
15857
  snapshots: array(TrackSnapshotSchema).readonly(),
15247
15858
  /** Deduplicated zones the track has entered at least once. */
15248
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(),
15249
15864
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15250
15865
  totalDistance: number(),
15251
15866
  state: TrackStateSchema,
@@ -15259,7 +15874,11 @@ var TrackSchema = object({
15259
15874
  bestEventId: string().optional(),
15260
15875
  /** Tag of the importance sub-signal that dominated the score
15261
15876
  * (identity|dwell|proximity|class|confidence|travel|zone). */
15262
- 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()
15263
15882
  });
15264
15883
  var BaseEventFields = {
15265
15884
  id: string(),
@@ -15407,7 +16026,7 @@ var KeyEventSchema = object({
15407
16026
  /** Track lifetime in ms (lastSeen - firstSeen). */
15408
16027
  windowMs: number().optional()
15409
16028
  });
15410
- var TrackedDetectionSchema = object({
16029
+ object({
15411
16030
  trackId: string(),
15412
16031
  className: string(),
15413
16032
  confidence: number(),
@@ -15415,6 +16034,23 @@ var TrackedDetectionSchema = object({
15415
16034
  zones: array(string()).readonly(),
15416
16035
  state: TrackStateSchema
15417
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
+ });
15418
16054
  var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
15419
16055
  var SearchObjectEventsInput = object({
15420
16056
  text: string(),
@@ -15425,6 +16061,20 @@ var SearchObjectEventsInput = object({
15425
16061
  limit: number().default(50),
15426
16062
  minScore: number().min(0).max(1).default(.2)
15427
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
+ });
15428
16078
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
15429
16079
  deviceId: number(),
15430
16080
  trackId: string()
@@ -15457,15 +16107,33 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15457
16107
  kind: "mutation",
15458
16108
  auth: "admin"
15459
16109
  }), method(object({
15460
- eventId: string(),
15461
- kind: MediaFileKindEnum.optional()
15462
- }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
15463
16110
  deviceId: number(),
15464
- timestamp: number(),
15465
- frameWidth: number(),
15466
- frameHeight: number(),
15467
- detections: array(TrackedDetectionSchema).readonly()
15468
- }), object({
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"
16127
+ }), method(object({
16128
+ eventId: string(),
16129
+ kind: MediaFileKindEnum.optional()
16130
+ }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
16131
+ deviceId: number(),
16132
+ timestamp: number(),
16133
+ frameWidth: number(),
16134
+ frameHeight: number(),
16135
+ detections: array(OverlayDetectionSchema).readonly()
16136
+ }), object({
15469
16137
  deviceId: number(),
15470
16138
  trackId: string(),
15471
16139
  className: string()
@@ -18233,6 +18901,24 @@ DeviceType.Camera, method(object({
18233
18901
  deviceId: number(),
18234
18902
  status: OsdStatusSchema
18235
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
+ });
18236
18922
  var PlateInfoSchema = object({
18237
18923
  plateId: string(),
18238
18924
  deviceId: number().int(),
@@ -18244,6 +18930,16 @@ var PlateInfoSchema = object({
18244
18930
  score: number(),
18245
18931
  /** True when the text was manually corrected (exempt from retention). */
18246
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(),
18247
18943
  base64: string().optional()
18248
18944
  });
18249
18945
  var MediaFileLiteSchema = object({
@@ -18283,6 +18979,48 @@ method(object({
18283
18979
  }), method(object({ plateId: string() }), _void(), {
18284
18980
  kind: "mutation",
18285
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"
18286
19024
  });
18287
19025
  var ModelFormatSchema = _enum(MODEL_FORMATS);
18288
19026
  var HwAccelBackendInputSchema = _enum([
@@ -18361,6 +19099,10 @@ var GpuInfoSchema = object({
18361
19099
  memoryMB: number().optional()
18362
19100
  });
18363
19101
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
19102
+ var CoralInfoSchema = object({
19103
+ type: literal("coral-edgetpu"),
19104
+ bus: string().optional()
19105
+ });
18364
19106
  var HardwareInfoSchema = object({
18365
19107
  platform: HardwarePlatformSchema,
18366
19108
  arch: HardwareArchSchema,
@@ -18369,7 +19111,8 @@ var HardwareInfoSchema = object({
18369
19111
  totalRAM_MB: number(),
18370
19112
  availableRAM_MB: number(),
18371
19113
  gpu: GpuInfoSchema.nullable(),
18372
- npu: NpuInfoSchema.nullable()
19114
+ npu: NpuInfoSchema.nullable(),
19115
+ coral: CoralInfoSchema.nullable().optional()
18373
19116
  });
18374
19117
  var PlatformScoreSchema = object({
18375
19118
  runtime: _enum(["node", "python"]),
@@ -20828,6 +21571,168 @@ Object.freeze({
20828
21571
  addonId: null,
20829
21572
  access: "create"
20830
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
+ },
20831
21736
  "localNetwork.getAllowedAddresses": {
20832
21737
  capName: "local-network",
20833
21738
  capScope: "system",
@@ -21458,6 +22363,12 @@ Object.freeze({
21458
22363
  addonId: null,
21459
22364
  access: "delete"
21460
22365
  },
22366
+ "pipelineAnalytics.deleteTracks": {
22367
+ capName: "pipeline-analytics",
22368
+ capScope: "device",
22369
+ addonId: null,
22370
+ access: "delete"
22371
+ },
21461
22372
  "pipelineAnalytics.getActiveTracks": {
21462
22373
  capName: "pipeline-analytics",
21463
22374
  capScope: "device",
@@ -21524,12 +22435,24 @@ Object.freeze({
21524
22435
  addonId: null,
21525
22436
  access: "create"
21526
22437
  },
22438
+ "pipelineAnalytics.pruneTracksBefore": {
22439
+ capName: "pipeline-analytics",
22440
+ capScope: "device",
22441
+ addonId: null,
22442
+ access: "create"
22443
+ },
21527
22444
  "pipelineAnalytics.searchObjectEvents": {
21528
22445
  capName: "pipeline-analytics",
21529
22446
  capScope: "device",
21530
22447
  addonId: null,
21531
22448
  access: "view"
21532
22449
  },
22450
+ "pipelineAnalytics.wipeAllAnalytics": {
22451
+ capName: "pipeline-analytics",
22452
+ capScope: "device",
22453
+ addonId: null,
22454
+ access: "delete"
22455
+ },
21533
22456
  "pipelineExecutor.cacheFrameInPool": {
21534
22457
  capName: "pipeline-executor",
21535
22458
  capScope: "system",
@@ -22028,18 +22951,48 @@ Object.freeze({
22028
22951
  addonId: null,
22029
22952
  access: "create"
22030
22953
  },
22954
+ "pipelineRunner.runDetailSubtree": {
22955
+ capName: "pipeline-runner",
22956
+ capScope: "system",
22957
+ addonId: null,
22958
+ access: "create"
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
+ },
22031
22972
  "plateGallery.correctPlateText": {
22032
22973
  capName: "plate-gallery",
22033
22974
  capScope: "system",
22034
22975
  addonId: null,
22035
22976
  access: "create"
22036
22977
  },
22978
+ "plateGallery.createVehicle": {
22979
+ capName: "plate-gallery",
22980
+ capScope: "system",
22981
+ addonId: null,
22982
+ access: "create"
22983
+ },
22037
22984
  "plateGallery.deletePlate": {
22038
22985
  capName: "plate-gallery",
22039
22986
  capScope: "system",
22040
22987
  addonId: null,
22041
22988
  access: "delete"
22042
22989
  },
22990
+ "plateGallery.deleteVehicle": {
22991
+ capName: "plate-gallery",
22992
+ capScope: "system",
22993
+ addonId: null,
22994
+ access: "delete"
22995
+ },
22043
22996
  "plateGallery.getPlateByTrack": {
22044
22997
  capName: "plate-gallery",
22045
22998
  capScope: "system",
@@ -22058,6 +23011,30 @@ Object.freeze({
22058
23011
  addonId: null,
22059
23012
  access: "view"
22060
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
+ },
22061
23038
  "plateGallery.searchPlates": {
22062
23039
  capName: "plate-gallery",
22063
23040
  capScope: "system",
@@ -22070,6 +23047,18 @@ Object.freeze({
22070
23047
  addonId: null,
22071
23048
  access: "view"
22072
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
+ },
22073
23062
  "platformProbe.getCapabilities": {
22074
23063
  capName: "platform-probe",
22075
23064
  capScope: "system",
@@ -22262,6 +23251,48 @@ Object.freeze({
22262
23251
  addonId: null,
22263
23252
  access: "create"
22264
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
+ },
22265
23296
  "scriptRunner.run": {
22266
23297
  capName: "script-runner",
22267
23298
  capScope: "device",
@@ -23346,6 +24377,7 @@ Object.freeze({
23346
24377
  Object.freeze({
23347
24378
  "broker": "broker",
23348
24379
  "device-export": "device-export",
24380
+ "llm": "ai",
23349
24381
  "mesh-network": "mesh",
23350
24382
  "mqtt-broker": "broker",
23351
24383
  "network-access": "ingress",
@@ -23480,77 +24512,12 @@ function rankDecodeHwAccels(preferred, supportedMethods) {
23480
24512
  return candidates.sort((a, b) => decodeHwAccelRankIndex(a) - decodeHwAccelRankIndex(b));
23481
24513
  }
23482
24514
  //#endregion
23483
- //#region src/shm-orphan-purge.ts
23484
- /**
23485
- * Startup reclamation of orphaned shared-memory segments.
23486
- *
23487
- * A decoder writes frames into named `/dev/shm` segments and unlinks each one
23488
- * on graceful session teardown (`DecoderFrameRingSink.destroy`). When the
23489
- * decoder process dies *ungracefully* — SIGBUS, OOM-kill, or a SIGKILL during
23490
- * redeploy — that teardown never runs and the segment is orphaned: it stays in
23491
- * the tmpfs forever, since nothing else knows its name. Across many
23492
- * crashes/redeploys these accumulate until `/dev/shm` fills, at which point the
23493
- * next `mmap` write faults with an uncatchable SIGBUS and the decoder
23494
- * crash-loops into its circuit breaker (which is exactly the incident this
23495
- * guards against).
23496
- *
23497
- * The reclamation is safe *at process startup*: a freshly-booting decoder owns
23498
- * no live sessions, so every pre-existing segment with its prefix is by
23499
- * definition an orphan from a dead instance. `shm_unlink` only removes the
23500
- * name — any consumer still holding a mapping keeps reading valid memory until
23501
- * it closes (POSIX deferred reclaim + the ring seqlock), so unlinking is safe
23502
- * even if a stale reader is momentarily still attached.
23503
- *
23504
- * POSIX-only: segments surface as files under `/dev/shm` on Linux. On platforms
23505
- * without that directory (Windows, macOS) the scan finds nothing — no-op.
23506
- *
23507
- * NOTE: this lives inside the decoder addon (not `@camstack/shm-ring`) so it
23508
- * ships in the self-contained addon bundle via `camstack deploy`, reusing the
23509
- * already-deployed `unlinkSegment`; no host base-image rebuild required.
23510
- */
23511
- /** Default tmpfs directory where POSIX shared-memory segments appear on Linux. */
23512
- var DEFAULT_SHM_DIR = "/dev/shm";
23513
- /**
23514
- * Unlink every shared-memory segment whose name starts with `prefix`.
23515
- *
23516
- * Intended to run ONCE at decoder startup, before any session is created, to
23517
- * reclaim segments orphaned by a previously-crashed instance. A per-file unlink
23518
- * failure is swallowed so one stuck segment cannot block reclaiming the rest.
23519
- */
23520
- function purgeOrphanSegments(prefix, options = {}) {
23521
- const dir = options.dir ?? DEFAULT_SHM_DIR;
23522
- const unlink = options.unlink ?? unlinkSegment;
23523
- let entries;
23524
- try {
23525
- entries = readdirSync(dir);
23526
- } catch {
23527
- return {
23528
- scanned: 0,
23529
- removed: 0,
23530
- names: []
23531
- };
23532
- }
23533
- const names = [];
23534
- for (const name of entries) {
23535
- if (!name.startsWith(prefix)) continue;
23536
- try {
23537
- unlink(name);
23538
- names.push(name);
23539
- } catch {}
23540
- }
23541
- return {
23542
- scanned: entries.length,
23543
- removed: names.length,
23544
- names
23545
- };
23546
- }
23547
- //#endregion
23548
24515
  //#region src/shared/notifying-ring-buffer.ts
23549
24516
  /**
23550
24517
  * A {@link RingBuffer} that can notify a single blocked consumer the moment an
23551
- * item commits — the server side of the `pullFrames`/`pullHandles` long-poll.
24518
+ * item commits — the server side of the `pullFrames` long-poll.
23552
24519
  *
23553
- * The decoder's `onFrame`/`onFrameHandle` callback drives `push()`; a consumer
24520
+ * The decoder's `onFrame` callback drives `push()`; a consumer
23554
24521
  * that finds the ring momentarily empty calls {@link waitForItem} and is woken
23555
24522
  * on the very next `push()` (a real commit signal) rather than spinning at
23556
24523
  * ~1kHz on `setTimeout(…,1)`. If nothing commits it resolves after `waitMs`,
@@ -23888,19 +24855,7 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
23888
24855
  config;
23889
24856
  logger;
23890
24857
  frameCallbacks = /* @__PURE__ */ new Set();
23891
- handleCallbacks = /* @__PURE__ */ new Set();
23892
24858
  destroyed = false;
23893
- /**
23894
- * Frame delivery mode (see {@link DecoderFrameSink}). `'shm'` lazily
23895
- * constructs `frameRingSink` and routes every decoded frame through it.
23896
- */
23897
- frameSink;
23898
- /**
23899
- * The shared-memory ring writer for this stream. Created lazily on the
23900
- * first decoded frame in `'shm'` mode (the segment cannot be sized until
23901
- * the output geometry is known) and torn down in `destroy`.
23902
- */
23903
- frameRingSink = null;
23904
24859
  parser = null;
23905
24860
  codecCtx = null;
23906
24861
  scaler = null;
@@ -24021,8 +24976,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24021
24976
  startTime = Date.now();
24022
24977
  hwaccelPref;
24023
24978
  hwaccelResolver;
24024
- /** Cluster node id stamped into every `FrameHandle` the `'shm'` sink emits. */
24025
- nodeId;
24026
24979
  /** The backend that actually initialised successfully — `'none'` = software fallback. */
24027
24980
  activeHwAccel = "none";
24028
24981
  hwDevice = null;
@@ -24054,35 +25007,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24054
25007
  this.outputMode = NodeAvDecoderSession.resolveOutputMode(config.outputFormat);
24055
25008
  this.hwaccelPref = options?.hwaccel ?? "auto";
24056
25009
  this.hwaccelResolver = options?.hwaccelResolver ?? null;
24057
- this.frameSink = options?.frameSink ?? "callback";
24058
- this.nodeId = options?.nodeId ?? "local";
24059
- }
24060
- /**
24061
- * The shared-memory ring sink for this stream, or `null` before the first
24062
- * `'shm'`-mode frame lazily creates it. Exposed so the owning addon can
24063
- * surface ring stats via `decoder.getShmStats`.
24064
- */
24065
- get frameRingSinkOrNull() {
24066
- return this.frameRingSink;
24067
- }
24068
- /**
24069
- * Lazily build the shared-memory ring sink for this stream. The segment
24070
- * itself is still created lazily by the sink on its first `writeFrame`,
24071
- * once the decoded geometry is known.
24072
- */
24073
- ensureFrameRingSink() {
24074
- if (this.frameRingSink === null) {
24075
- const seedParts = [];
24076
- if (typeof this.config.deviceId === "number") seedParts.push(String(this.config.deviceId));
24077
- if (typeof this.config.tag === "string" && this.config.tag.length > 0) seedParts.push(this.config.tag);
24078
- const seed = seedParts.length > 0 ? seedParts.join(":") : "anon";
24079
- this.frameRingSink = new DecoderFrameRingSink({
24080
- seed,
24081
- logger: this.logger,
24082
- nodeId: this.nodeId
24083
- });
24084
- }
24085
- return this.frameRingSink;
24086
25010
  }
24087
25011
  /**
24088
25012
  * Resolve the backend preference list and try each one against
@@ -24706,10 +25630,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24706
25630
  }
24707
25631
  if (!this.dstFrame || !this.scaler) return;
24708
25632
  const decodeStart = performance.now();
24709
- if (this.frameSink === "shm" && this.outputMode !== "jpeg") {
24710
- this.scaleIntoRingSlot(frame, decodeStart);
24711
- return;
24712
- }
24713
25633
  try {
24714
25634
  this.dstFrame.makeWritable();
24715
25635
  this.scaler.scaleFrameSync(this.dstFrame, frame);
@@ -24754,100 +25674,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24754
25674
  else this.emitRawFrame(rawBuf, "gray", decodeStart);
24755
25675
  }
24756
25676
  /**
24757
- * Scale a decoded frame **directly into a shared-memory ring slot** — the
24758
- * zero write-side copy path (Phase 5 / D9 Task 7c).
24759
- *
24760
- * `beginFrame` reserves the slot (its seqlock open / odd — readers skip it);
24761
- * `SoftwareScaleContext.scaleSync` (the low-level `sws_scale` mapping) writes
24762
- * the packed pixels straight into the slot buffer with `linesize = width ×
24763
- * bpp` — no linesize padding, so there is nothing to strip and nothing to
24764
- * memcpy; `commitFrame` writes the metadata, closes the seqlock and publishes
24765
- * the slot. The decoded source planes feed `scaleSync` as `srcSlice` +
24766
- * `srcStride` (`frame.data` / `frame.linesize`).
24767
- *
24768
- * Used only for raw output formats (rgb/bgr/gray). The jpeg path keeps the
24769
- * `dstFrame` + sharp encode route because sharp must consume an RGB buffer
24770
- * before the final (variable-length) jpeg bytes exist.
24771
- */
24772
- scaleIntoRingSlot(frame, decodeStart) {
24773
- if (!this.scaler) return;
24774
- const format = this.outputMode === "gray" ? "gray" : "rgb";
24775
- const channels = this.outputMode === "gray" ? 1 : 3;
24776
- const sink = this.ensureFrameRingSink();
24777
- const reserved = sink.beginFrame(this.outWidth, this.outHeight, format);
24778
- if (reserved === null) {
24779
- this.droppedFrames++;
24780
- return;
24781
- }
24782
- const srcPlanes = frame.data;
24783
- if (!srcPlanes || srcPlanes.length === 0) {
24784
- sink.abortFrame(reserved.slot);
24785
- this.droppedFrames++;
24786
- return;
24787
- }
24788
- const dstStride = this.outWidth * channels;
24789
- const needed = dstStride * this.outHeight;
24790
- if (reserved.buffer.length < needed) {
24791
- this.logger.warn("node-av: ring slot too small for scaled frame — dropping", { meta: {
24792
- needed,
24793
- slotBytes: reserved.buffer.length,
24794
- width: this.outWidth,
24795
- height: this.outHeight
24796
- } });
24797
- sink.abortFrame(reserved.slot);
24798
- this.droppedFrames++;
24799
- return;
24800
- }
24801
- try {
24802
- const srcStrides = Array.from(frame.linesize).slice(0, srcPlanes.length);
24803
- this.scaler.scaleSync(Array.from(srcPlanes), srcStrides, 0, frame.height, [reserved.buffer], [dstStride]);
24804
- } catch (err) {
24805
- this.logger.warn("node-av scale-into-slot error", { meta: { error: errMsg(err) } });
24806
- sink.abortFrame(reserved.slot);
24807
- this.droppedFrames++;
24808
- return;
24809
- }
24810
- const decodeMs = performance.now() - decodeStart;
24811
- this.totalDecodeTimeMs += decodeMs;
24812
- this.outputFrames++;
24813
- this.lastEmitTime = performance.now();
24814
- if (!this.firstFrameLogged) {
24815
- this.firstFrameLogged = true;
24816
- this.logger.info("node-av: scaled directly into shm ring slot", { meta: {
24817
- phase: "frame-debug",
24818
- width: this.outWidth,
24819
- height: this.outHeight,
24820
- dstStride,
24821
- format
24822
- } });
24823
- }
24824
- if (this.outputFrames === 1 || this.outputFrames % 500 === 0) this.logger.info("node-av frame emitted", { meta: {
24825
- frameNumber: this.outputFrames,
24826
- width: this.outWidth,
24827
- height: this.outHeight,
24828
- format,
24829
- decodeMs,
24830
- sink: "shm",
24831
- subs: this.handleCallbacks.size
24832
- } });
24833
- const handle = sink.commitFrame(reserved.slot, {
24834
- width: this.outWidth,
24835
- height: this.outHeight,
24836
- format,
24837
- pts: performance.now(),
24838
- byteLength: dstStride * this.outHeight
24839
- });
24840
- if (handle === null) {
24841
- this.droppedFrames++;
24842
- return;
24843
- }
24844
- const delivered = {
24845
- handle,
24846
- timestamp: Date.now()
24847
- };
24848
- for (const cb of this.handleCallbacks) cb(delivered);
24849
- }
24850
- /**
24851
25677
  * Extract packed pixel buffer from a decoded frame.
24852
25678
  * FFmpeg's av_frame_get_buffer() may pad each row to alignment (32/64 bytes).
24853
25679
  * Sharp and WASM consumers expect tightly-packed rows (stride = width * channels).
@@ -24906,13 +25732,8 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24906
25732
  height: this.outHeight,
24907
25733
  format,
24908
25734
  decodeMs,
24909
- sink: this.frameSink,
24910
- subs: this.frameSink === "shm" ? this.handleCallbacks.size : this.frameCallbacks.size
25735
+ subs: this.frameCallbacks.size
24911
25736
  } });
24912
- if (this.frameSink === "shm") {
24913
- this.emitShmFrame(data, format);
24914
- return;
24915
- }
24916
25737
  const decodedFrame = {
24917
25738
  data,
24918
25739
  width: this.outWidth,
@@ -24922,47 +25743,12 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
24922
25743
  };
24923
25744
  for (const cb of this.frameCallbacks) cb(decodedFrame);
24924
25745
  }
24925
- /**
24926
- * Write a decoded frame into this stream's shared-memory ring and deliver
24927
- * the resulting `FrameHandle` to `onFrameHandle` subscribers. No pixel
24928
- * `Buffer` is handed to callbacks — consumers open the segment and read the
24929
- * pixels zero-copy via a `FrameRingReader`.
24930
- */
24931
- emitShmFrame(data, format) {
24932
- const handle = this.ensureFrameRingSink().writeFrame(data, {
24933
- width: this.outWidth,
24934
- height: this.outHeight,
24935
- format,
24936
- pts: performance.now(),
24937
- byteLength: data.byteLength
24938
- });
24939
- if (handle === null) {
24940
- this.droppedFrames++;
24941
- return;
24942
- }
24943
- const frame = {
24944
- handle,
24945
- timestamp: Date.now()
24946
- };
24947
- for (const cb of this.handleCallbacks) cb(frame);
24948
- }
24949
25746
  onFrame(callback) {
24950
25747
  this.frameCallbacks.add(callback);
24951
25748
  return () => {
24952
25749
  this.frameCallbacks.delete(callback);
24953
25750
  };
24954
25751
  }
24955
- /**
24956
- * Subscribe to shared-memory frame handles. Fires only when the session was
24957
- * created with `frameSink: 'shm'`; in the legacy `'callback'` mode no
24958
- * handles are produced and this subscription never fires.
24959
- */
24960
- onFrameHandle(callback) {
24961
- this.handleCallbacks.add(callback);
24962
- return () => {
24963
- this.handleCallbacks.delete(callback);
24964
- };
24965
- }
24966
25752
  updateConfig(update) {
24967
25753
  const prevFormat = this.config.outputFormat;
24968
25754
  this.config = {
@@ -25000,7 +25786,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
25000
25786
  if (this.destroyed) return;
25001
25787
  this.destroyed = true;
25002
25788
  this.frameCallbacks.clear();
25003
- this.handleCallbacks.clear();
25004
25789
  this.pullActive = false;
25005
25790
  if (this.pullRedialTimer) {
25006
25791
  clearTimeout(this.pullRedialTimer);
@@ -25016,8 +25801,6 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
25016
25801
  if (!this.pullHwContext.isDisposed) this.pullHwContext.dispose();
25017
25802
  this.pullHwContext = null;
25018
25803
  }
25019
- this.frameRingSink?.destroy();
25020
- this.frameRingSink = null;
25021
25804
  this.dstFrame?.[Symbol.dispose]?.();
25022
25805
  this.avFrame?.[Symbol.dispose]?.();
25023
25806
  this.avPacket?.[Symbol.dispose]?.();
@@ -25845,27 +26628,14 @@ var FRAME_BUFFER_CAPACITY = 32;
25845
26628
  var DecoderNodeAvAddon = class extends BaseAddon {
25846
26629
  /**
25847
26630
  * Sessions are stored as the concrete `NodeAvDecoderSession` (the only type
25848
- * this addon ever creates) so `getShmStats` can reach the per-session shm
25849
- * ring sink — `IDecoderSession` does not expose it.
26631
+ * this addon ever creates).
25850
26632
  */
25851
26633
  sessions = /* @__PURE__ */ new Map();
25852
- /** Pixel-frame buffers populated only for `frameSink: 'callback'` sessions. */
26634
+ /** Pixel-frame buffers drained by `pullFrames`. */
25853
26635
  frameBuffers = /* @__PURE__ */ new Map();
25854
- /** `FrameHandle` buffers — populated only for `frameSink: 'shm'` sessions. */
25855
- handleBuffers = /* @__PURE__ */ new Map();
25856
26636
  unsubscribers = /* @__PURE__ */ new Map();
25857
26637
  sessionMeta = /* @__PURE__ */ new Map();
25858
26638
  /**
25859
- * Per-shm-segment `FrameRingReader` cache backing `getFrame`. Opens each
25860
- * named segment once and reuses the reader for every later handle on it;
25861
- * built in `onInitialize` (it needs the scoped logger) and closed in
25862
- * `onShutdown`.
25863
- */
25864
- frameReaders = null;
25865
- /** Running `getFrame` hit/miss counters surfaced via `getShmStats`. */
25866
- getFrameHits = 0;
25867
- getFrameMisses = 0;
25868
- /**
25869
26639
  * node-av audio-codec provider — registered independently of the video
25870
26640
  * decoder backend gate whenever node-av loads on this node, so this single
25871
26641
  * addon provides both the `decoder` and `audio-codec` caps.
@@ -25930,12 +26700,6 @@ var DecoderNodeAvAddon = class extends BaseAddon {
25930
26700
  return registrations;
25931
26701
  }
25932
26702
  this.ctx.logger.info("node-av decoder addon initialized", { meta: { selectedBackend: backend } });
25933
- const purged = purgeOrphanSegments(SEGMENT_NAME_PREFIX);
25934
- if (purged.removed > 0) this.ctx.logger.warn("node-av decoder: reclaimed orphaned shm segments at startup", { meta: {
25935
- removed: purged.removed,
25936
- scanned: purged.scanned
25937
- } });
25938
- this.frameReaders = new FrameRingReaderCache(this.ctx.logger);
25939
26703
  if (!this.config.probedBestHwaccel) this.reprobeHwaccel().catch((err) => {
25940
26704
  this.ctx.logger.warn("nodeav: auto-reprobe hwaccel failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
25941
26705
  });
@@ -26002,11 +26766,11 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26002
26766
  }
26003
26767
  /**
26004
26768
  * The cluster node id of this decoder — the Moleculer nodeID (`hub`,
26005
- * `dev-agent-0`, …), not the addon id. Stamped into session-owned
26006
- * `FrameHandle`s so a downstream consumer routes `getFrame` to the node
26007
- * that holds the shm ring. A hierarchical broker nodeID (`hub/...`) is
26008
- * collapsed to the cluster-visible parent; in-process boot is left as-is.
26009
- * 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.
26010
26774
  */
26011
26775
  resolveLocalNodeId() {
26012
26776
  const raw = this.ctx.kernel.localNodeId ?? this.ctx.id;
@@ -26015,15 +26779,12 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26015
26779
  async createSession(config) {
26016
26780
  const sessionId = randomUUID();
26017
26781
  const hwaccel = await this.resolveHwAccelPref();
26018
- const { frameSink } = config;
26019
26782
  const nodeId = this.resolveLocalNodeId();
26020
26783
  const session = new NodeAvDecoderSession(config, this.ctx.logger, {
26021
26784
  hwaccel,
26022
- hwaccelResolver: this.ctx.kernel.hwaccel,
26023
- frameSink,
26024
- nodeId
26785
+ hwaccelResolver: this.ctx.kernel.hwaccel
26025
26786
  });
26026
- const unsub = frameSink === "shm" ? this.wireShmSink(sessionId, session) : this.wireCallbackSink(sessionId, session);
26787
+ const unsub = this.wireCallbackSink(sessionId, session);
26027
26788
  this.sessions.set(sessionId, session);
26028
26789
  this.unsubscribers.set(sessionId, unsub);
26029
26790
  this.sessionMeta.set(sessionId, {
@@ -26038,7 +26799,6 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26038
26799
  sessionId,
26039
26800
  codec: config.codec,
26040
26801
  hwaccelPref: hwaccel,
26041
- frameSink,
26042
26802
  nodeId
26043
26803
  }
26044
26804
  });
@@ -26069,18 +26829,6 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26069
26829
  ringBuffer.push(capFrame);
26070
26830
  });
26071
26831
  }
26072
- /**
26073
- * Subscribe a `'shm'` session's `FrameHandle`s into a per-session ring
26074
- * buffer drained by `pullHandles`. No pixel bytes cross the cap boundary
26075
- * — the broker opens the named segment and reads the pixels zero-copy.
26076
- */
26077
- wireShmSink(sessionId, session) {
26078
- const handleRing = new NotifyingRingBuffer(FRAME_BUFFER_CAPACITY);
26079
- this.handleBuffers.set(sessionId, handleRing);
26080
- return session.onFrameHandle((frame) => {
26081
- handleRing.push(frame.handle);
26082
- });
26083
- }
26084
26832
  async destroySession(input) {
26085
26833
  const { sessionId } = input;
26086
26834
  const session = this.sessions.get(sessionId);
@@ -26088,12 +26836,10 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26088
26836
  const unsub = this.unsubscribers.get(sessionId);
26089
26837
  if (unsub) unsub();
26090
26838
  this.frameBuffers.get(sessionId)?.cancel();
26091
- this.handleBuffers.get(sessionId)?.cancel();
26092
26839
  const deviceId = this.sessionMeta.get(sessionId)?.deviceId;
26093
26840
  await session.destroy();
26094
26841
  this.sessions.delete(sessionId);
26095
26842
  this.frameBuffers.delete(sessionId);
26096
- this.handleBuffers.delete(sessionId);
26097
26843
  this.unsubscribers.delete(sessionId);
26098
26844
  this.sessionMeta.delete(sessionId);
26099
26845
  this.ctx.logger.info("node-av: destroyed session", {
@@ -26133,12 +26879,14 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26133
26879
  if (ringBuffer.size === 0 && input.waitMs) await ringBuffer.waitForItem(input.waitMs);
26134
26880
  return ringBuffer.drain(input.maxCount);
26135
26881
  }
26136
- async pullHandles(input) {
26137
- if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`);
26138
- const handleRing = this.handleBuffers.get(input.sessionId);
26139
- if (!handleRing) return [];
26140
- if (handleRing.size === 0 && input.waitMs) await handleRing.waitForItem(input.waitMs);
26141
- 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 [];
26142
26890
  }
26143
26891
  async updateConfig(input) {
26144
26892
  const session = this.sessions.get(input.sessionId);
@@ -26151,50 +26899,22 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26151
26899
  return session.getStats();
26152
26900
  }
26153
26901
  /**
26154
- * Read back the pixels a `FrameHandle` refers to from this node's shm ring
26155
- * (Phase 5 / D9 downstream access). Returns `null` when the slot was
26156
- * already recycled (latest-wins drop) or the segment could not be opened.
26157
- * The reader cache opens each named segment once and reuses the reader.
26158
- *
26159
- * The reader hands back a `Buffer` view over its **reusable scratch
26160
- * buffer**, only valid until the next `read` on the same reader; the
26161
- * cap caller may keep the frame across reads, so the pixels are copied
26162
- * into a fresh detached `ArrayBuffer` here. This matches the existing
26163
- * 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.
26164
26907
  */
26165
- async getFrame(input) {
26166
- const frame = this.frameReaders?.read(input.handle) ?? null;
26167
- if (!frame) {
26168
- this.getFrameMisses += 1;
26169
- return null;
26170
- }
26171
- this.getFrameHits += 1;
26172
- const arrayBuf = new ArrayBuffer(frame.data.byteLength);
26173
- new Uint8Array(arrayBuf).set(frame.data);
26174
- return {
26175
- data: new Uint8Array(arrayBuf),
26176
- width: frame.width,
26177
- height: frame.height,
26178
- format: frame.format,
26179
- timestamp: frame.timestamp
26180
- };
26908
+ async getFrame() {
26909
+ return null;
26181
26910
  }
26182
26911
  /**
26183
- * shm ring usage stats for a `frameSink: 'shm'` session — slot geometry,
26184
- * frames written, byte budget, plus this addon's running `getFrame`
26185
- * hit/miss counters. Returns `null` for an unknown session or one whose
26186
- * 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.
26187
26915
  */
26188
- async getShmStats(input) {
26189
- const stats = this.sessions.get(input.sessionId)?.frameRingSinkOrNull?.getShmStats() ?? null;
26190
- if (!stats) return null;
26191
- return {
26192
- sessionId: input.sessionId,
26193
- ...stats,
26194
- budgetMb: RING_BUDGET_MB,
26195
- getFrameHits: this.getFrameHits,
26196
- getFrameMisses: this.getFrameMisses
26197
- };
26916
+ async getShmStats() {
26917
+ return null;
26198
26918
  }
26199
26919
  async onShutdown() {
26200
26920
  this.audioProvider?.stop();
@@ -26209,12 +26929,9 @@ var DecoderNodeAvAddon = class extends BaseAddon {
26209
26929
  await Promise.all(destroyPromises);
26210
26930
  this.sessions.clear();
26211
26931
  this.frameBuffers.clear();
26212
- this.handleBuffers.clear();
26213
26932
  this.unsubscribers.clear();
26214
26933
  this.sessionMeta.clear();
26215
- this.frameReaders?.close();
26216
- this.frameReaders = null;
26217
26934
  }
26218
26935
  };
26219
26936
  //#endregion
26220
- 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 };