@camstack/addon-decoder-ffmpeg 1.1.11 → 1.1.13

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 -265
  2. package/dist/index.mjs +1118 -265
  3. package/package.json +1 -2
package/dist/index.mjs CHANGED
@@ -1,7 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { DecoderFrameRingSink, FrameRingReaderCache, RING_BUDGET_MB, SEGMENT_NAME_PREFIX, unlinkSegment } from "@camstack/shm-ring";
3
2
  import { spawn } from "node:child_process";
4
- import { readdirSync } from "node:fs";
5
3
  //#region ../../node_modules/zod/v4/core/core.js
6
4
  var _a$1;
7
5
  function $constructor(name, initializer, params) {
@@ -4290,6 +4288,14 @@ function object(shape, params) {
4290
4288
  ...normalizeParams(params)
4291
4289
  });
4292
4290
  }
4291
+ function looseObject(shape, params) {
4292
+ return new ZodObject({
4293
+ type: "object",
4294
+ shape,
4295
+ catchall: unknown(),
4296
+ ...normalizeParams(params)
4297
+ });
4298
+ }
4293
4299
  var ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
4294
4300
  $ZodUnion.init(inst, def);
4295
4301
  ZodType.init(inst, def);
@@ -4631,7 +4637,7 @@ function _instanceof(cls, params = {}) {
4631
4637
  return inst;
4632
4638
  }
4633
4639
  //#endregion
4634
- //#region ../types/dist/sleep-BC9Yqte7.mjs
4640
+ //#region ../types/dist/sleep-_sv7WKkq.mjs
4635
4641
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4636
4642
  EventCategory["SystemBoot"] = "system.boot";
4637
4643
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5041,10 +5047,51 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5041
5047
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
5042
5048
  EventCategory["EnrichmentSceneStateChanged"] = "enrichment.scene.state-changed";
5043
5049
  EventCategory["EnrichmentActivitySummary"] = "enrichment.activity.summary";
5050
+ /**
5051
+ * Unified track-lifecycle event: ONE category carrying `phase:
5052
+ * 'start' | 'update' | 'end'` with a rich, typed
5053
+ * `PipelineAnalyticsTrackLifecyclePayload`. Supersedes the thin
5054
+ * `PipelineAnalyticsTrackStarted` / `PipelineAnalyticsTrackEnded`
5055
+ * pair — a consumer subscribes once and branches on `phase`.
5056
+ */
5057
+ EventCategory["PipelineAnalyticsTrackLifecycle"] = "pipeline-analytics.track-lifecycle";
5058
+ /**
5059
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5060
+ * (`phase:'start'`). Kept as a soft alias — no known subscribers.
5061
+ */
5044
5062
  EventCategory["PipelineAnalyticsTrackStarted"] = "pipeline-analytics.track-started";
5063
+ /**
5064
+ * @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
5065
+ * (`phase:'end'`). Kept as a soft alias — no known subscribers.
5066
+ */
5045
5067
  EventCategory["PipelineAnalyticsTrackEnded"] = "pipeline-analytics.track-ended";
5046
5068
  EventCategory["PipelineAnalyticsDetectionEvent"] = "pipeline-analytics.detection-event";
5047
5069
  EventCategory["PipelineAnalyticsFrameTracked"] = "pipeline-analytics.frame-tracked";
5070
+ /**
5071
+ * Fired by `addon-post-analysis` when a parked (stationary) object appears
5072
+ * (a track was promoted to a stationary-registry entry) or departs (the
5073
+ * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
5074
+ * the dedicated "Stationary" section — never the live event feed. Payload:
5075
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
5076
+ */
5077
+ EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
5078
+ /**
5079
+ * Fired by `addon-post-analysis` whenever a gallery face row changes:
5080
+ * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
5081
+ * `'unassigned'` its identity link changed, `'deleted'` the row was
5082
+ * removed. Telemetry semantics (D8): a lost event only delays a refresh,
5083
+ * never a correctness issue. Payload `PipelineAnalyticsFaceGalleryChangedPayload`.
5084
+ */
5085
+ EventCategory["PipelineAnalyticsFaceGalleryChanged"] = "pipeline-analytics.face-gallery-changed";
5086
+ /**
5087
+ * Fired by `addon-post-analysis` whenever a gallery plate row changes:
5088
+ * `kind:'buffered'` a new read was persisted (`PlateRecognizer.onTrackEnd`),
5089
+ * `'assigned'` / `'unassigned'` its vehicle link changed
5090
+ * (`PlateGalleryProvider`), `'deleted'` the row was removed. Telemetry
5091
+ * semantics (D8): a lost event only delays a refresh, never a correctness
5092
+ * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
5093
+ */
5094
+ EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
5048
5095
  EventCategory["FrigateLiveEvent"] = "frigate.live-event";
5049
5096
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
5050
5097
  /**
@@ -6885,7 +6932,8 @@ var MODEL_FORMATS = [
6885
6932
  "coreml",
6886
6933
  "openvino",
6887
6934
  "tflite",
6888
- "pt"
6935
+ "pt",
6936
+ "gguf"
6889
6937
  ];
6890
6938
  /**
6891
6939
  * Multi-file format payload.
@@ -6929,7 +6977,8 @@ var ModelFormatsSchema = object({
6929
6977
  coreml: ModelFormatEntrySchema.optional(),
6930
6978
  openvino: ModelFormatEntrySchema.optional(),
6931
6979
  tflite: ModelFormatEntrySchema.optional(),
6932
- pt: ModelFormatEntrySchema.optional()
6980
+ pt: ModelFormatEntrySchema.optional(),
6981
+ gguf: ModelFormatEntrySchema.optional()
6933
6982
  });
6934
6983
  /**
6935
6984
  * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
@@ -6970,6 +7019,15 @@ var ModelCatalogEntrySchema = object({
6970
7019
  width: number(),
6971
7020
  height: number()
6972
7021
  }),
7022
+ /**
7023
+ * Channel count of the model input tensor. Omit ⇒ 3 (RGB), the default for
7024
+ * every detector / classifier / embedder. Set to 1 for a grayscale CTC text
7025
+ * recognizer (EasyOCR VGG plate-OCR: input `[N,1,H,W]`) so the preprocess
7026
+ * feeds a single-channel, EasyOCR-normalized tensor instead of the default
7027
+ * 3-channel RGB one. Threaded through `PoolModelConfig.inputChannels` to the
7028
+ * Python inference pool.
7029
+ */
7030
+ inputChannels: number().int().positive().optional(),
6973
7031
  labels: array(LabelDefinitionSchema).readonly(),
6974
7032
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
6975
7033
  inputNormalization: _enum([
@@ -6979,6 +7037,16 @@ var ModelCatalogEntrySchema = object({
6979
7037
  ]).optional(),
6980
7038
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
6981
7039
  /**
7040
+ * Per-MODEL postprocessor override. Absent ⇒ the step's own
7041
+ * `StepDefinition.postprocessor` applies (the normal case — every model in a
7042
+ * step shares its decode). Set it when a step hosts models with DIFFERENT raw
7043
+ * output layouts under one slot: e.g. object-detection is `'yolo'` by default,
7044
+ * but a Coral SSD MobileNet build emits the `TFLite_Detection_PostProcess`
7045
+ * 4-tensor layout and needs `'ssd'`. Threaded into `PoolModelConfig.postprocessor`
7046
+ * by the engine factory (`modelEntry.postprocessor ?? def.postprocessor`).
7047
+ */
7048
+ postprocessor: custom().optional(),
7049
+ /**
6982
7050
  * When true, the executor produces a landmark-aligned crop (similarity warp
6983
7051
  * onto the canonical template) before this step runs, instead of a plain
6984
7052
  * axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
@@ -10424,7 +10492,8 @@ var ModelFormatSchema$1 = _enum([
10424
10492
  "coreml",
10425
10493
  "openvino",
10426
10494
  "tflite",
10427
- "pt"
10495
+ "pt",
10496
+ "gguf"
10428
10497
  ]);
10429
10498
  var PipelineSlotSchema = _enum([
10430
10499
  "detector",
@@ -10518,7 +10587,8 @@ var EngineProvisioningSchema = object({
10518
10587
  runtimeId: _enum([
10519
10588
  "onnx",
10520
10589
  "openvino",
10521
- "coreml"
10590
+ "coreml",
10591
+ "edgetpu"
10522
10592
  ]).nullable(),
10523
10593
  device: string().nullable(),
10524
10594
  state: _enum([
@@ -10679,7 +10749,15 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10679
10749
  image: _instanceof(Uint8Array).optional(),
10680
10750
  referenceImage: string().optional(),
10681
10751
  deviceId: number().optional(),
10682
- sessionId: string().optional()
10752
+ sessionId: string().optional(),
10753
+ /**
10754
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
10755
+ * reference-image, and detail-subtree calls. 'frame' is the live
10756
+ * per-frame dispatch: ONLY root-plane steps run; crop children
10757
+ * (inputClasses ≠ null) are skipped and served per-track via
10758
+ * pipelineRunner.runDetailSubtree (two-plane design).
10759
+ */
10760
+ plane: _enum(["full", "frame"]).optional()
10683
10761
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
10684
10762
  engine: PipelineEngineChoiceSchema.optional(),
10685
10763
  steps: array(PipelineStepInputSchema).min(1),
@@ -10822,6 +10900,28 @@ var NativeCropResultSchema = object({
10822
10900
  width: number().int().positive(),
10823
10901
  height: number().int().positive()
10824
10902
  });
10903
+ /** Parent detection context passed to `runDetailSubtree` — the crop's
10904
+ * originating detection, in FRAME-space coordinates. Reuses
10905
+ * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
10906
+ * the coordinates are frame-space rather than getNativeCrop's
10907
+ * normalized [0,1] convention). */
10908
+ var DetailParentSchema = object({
10909
+ bbox: NativeCropBboxSchema,
10910
+ className: string()
10911
+ });
10912
+ /** One child-step result from `runDetailSubtree` — an embedding, label,
10913
+ * or refined detection produced by running the crop-subtree on a
10914
+ * single tracked detection. */
10915
+ var DetailResultSchema = object({
10916
+ stepId: string(),
10917
+ className: string(),
10918
+ score: number(),
10919
+ /** FRAME-space bbox (already mapped back from crop space). */
10920
+ bbox: NativeCropBboxSchema.optional(),
10921
+ embedding: string().optional(),
10922
+ label: string().optional(),
10923
+ alignedCropJpeg: string().optional()
10924
+ });
10825
10925
  /**
10826
10926
  * Per-camera tunable ranges + defaults. Single source of truth used
10827
10927
  * by both the Zod data schema (validation + default fallback) and
@@ -11082,7 +11182,13 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
11082
11182
  handle: FrameHandleSchema,
11083
11183
  bbox: NativeCropBboxSchema,
11084
11184
  maxWidth: number().int().positive().optional()
11085
- }), NativeCropResultSchema.nullable());
11185
+ }), NativeCropResultSchema.nullable()), method(object({
11186
+ deviceId: number(),
11187
+ frameHandle: FrameHandleSchema.optional(),
11188
+ cropJpeg: string().optional(),
11189
+ parent: DetailParentSchema,
11190
+ steps: array(string()).optional()
11191
+ }), object({ details: array(DetailResultSchema) }).nullable(), { kind: "mutation" });
11086
11192
  object({
11087
11193
  detected: boolean(),
11088
11194
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -11694,6 +11800,140 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
11694
11800
  deviceId: number(),
11695
11801
  status: PtzAutotrackStatusSchema
11696
11802
  });
11803
+ /**
11804
+ * scene-monitor — device-scoped reference-region state cap. An operator marks
11805
+ * a rect ROI on a camera frame and names one or more states; the engine
11806
+ * (pipeline-analytics, designated post-processing node) reports which state is
11807
+ * active on an ongoing basis. Two operator-selectable check modes share every-
11808
+ * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
11809
+ * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
11810
+ *
11811
+ * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) — the framework
11812
+ * derives the device-detail contribution; the provider carries NO hand-written
11813
+ * settings-contribution methods. `status.kind:'push'` — the engine pushes on
11814
+ * every hysteresis flip / availability change; consumers never poll.
11815
+ */
11816
+ /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
11817
+ * be added without a wire break (matching falls back to any-condition refs). */
11818
+ var SceneConditionSchema = string();
11819
+ /** One captured reference — condition-tagged, model-version-gated. `embedding`
11820
+ * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
11821
+ var SceneReferenceSchema = object({
11822
+ embedding: array(number()),
11823
+ modelId: string(),
11824
+ condition: SceneConditionSchema,
11825
+ capturedAt: number(),
11826
+ thumbnailMediaId: string().optional()
11827
+ });
11828
+ var SceneMonitorStateSchema = object({
11829
+ id: string(),
11830
+ label: string(),
11831
+ references: array(SceneReferenceSchema),
11832
+ textPrompts: array(string()).optional(),
11833
+ referenceCount: number(),
11834
+ currentlyMatched: boolean()
11835
+ });
11836
+ /** Per-mode comparator params in a discriminated union so an `llm` scene never
11837
+ * carries similarity knobs and vice-versa. */
11838
+ var SceneCheckSchema = discriminatedUnion("mode", [object({
11839
+ mode: literal("similarity"),
11840
+ threshold: number().min(0).max(1),
11841
+ hysteresisCount: number().int().positive()
11842
+ }), object({
11843
+ mode: literal("llm"),
11844
+ prompt: string(),
11845
+ profileId: string().optional(),
11846
+ hysteresisCount: number().int().positive()
11847
+ })]);
11848
+ var SceneMonitorSchema = object({
11849
+ id: string(),
11850
+ label: string(),
11851
+ roi: MaskRectShapeSchema,
11852
+ enabled: boolean(),
11853
+ triggerMode: _enum([
11854
+ "periodic",
11855
+ "on-motion",
11856
+ "both"
11857
+ ]),
11858
+ checkIntervalSec: number().optional(),
11859
+ check: SceneCheckSchema,
11860
+ states: array(SceneMonitorStateSchema),
11861
+ currentStateId: string().nullable(),
11862
+ lastCheckedAt: number().nullable(),
11863
+ lastConfidence: number().nullable(),
11864
+ currentCondition: SceneConditionSchema.nullable(),
11865
+ availability: _enum(["ok", "unavailable"]),
11866
+ unavailableReason: string().nullable()
11867
+ });
11868
+ var SceneMonitorStatusSchema = object({
11869
+ monitors: array(SceneMonitorSchema),
11870
+ lastFetchedAt: number()
11871
+ });
11872
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
11873
+ deviceId: number(),
11874
+ label: string(),
11875
+ roi: MaskRectShapeSchema,
11876
+ check: SceneCheckSchema,
11877
+ triggerMode: _enum([
11878
+ "periodic",
11879
+ "on-motion",
11880
+ "both"
11881
+ ]).optional(),
11882
+ checkIntervalSec: number().optional()
11883
+ }), SceneMonitorSchema, {
11884
+ kind: "mutation",
11885
+ auth: "admin"
11886
+ }), method(object({
11887
+ deviceId: number(),
11888
+ monitorId: string(),
11889
+ patch: object({
11890
+ label: string().optional(),
11891
+ roi: MaskRectShapeSchema.optional(),
11892
+ enabled: boolean().optional(),
11893
+ triggerMode: _enum([
11894
+ "periodic",
11895
+ "on-motion",
11896
+ "both"
11897
+ ]).optional(),
11898
+ checkIntervalSec: number().optional(),
11899
+ check: SceneCheckSchema.optional()
11900
+ })
11901
+ }), _void(), {
11902
+ kind: "mutation",
11903
+ auth: "admin"
11904
+ }), method(object({
11905
+ deviceId: number(),
11906
+ monitorId: string()
11907
+ }), _void(), {
11908
+ kind: "mutation",
11909
+ auth: "admin"
11910
+ }), method(object({
11911
+ deviceId: number(),
11912
+ monitorId: string(),
11913
+ stateId: string().optional(),
11914
+ label: string().optional(),
11915
+ condition: SceneConditionSchema.optional()
11916
+ }), object({
11917
+ stateId: string(),
11918
+ referenceCount: number()
11919
+ }), {
11920
+ kind: "mutation",
11921
+ auth: "admin"
11922
+ }), method(object({
11923
+ deviceId: number(),
11924
+ monitorId: string(),
11925
+ stateId: string(),
11926
+ index: number()
11927
+ }), _void(), {
11928
+ kind: "mutation",
11929
+ auth: "admin"
11930
+ }), method(object({
11931
+ deviceId: number(),
11932
+ monitorId: string()
11933
+ }), _void(), {
11934
+ kind: "mutation",
11935
+ auth: "admin"
11936
+ });
11697
11937
  object({
11698
11938
  /** Whether the script is currently executing. */
11699
11939
  isRunning: boolean(),
@@ -12101,6 +12341,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
12101
12341
  * with the per-track detail panel and live overlay. */
12102
12342
  trackIds: array(string()).readonly()
12103
12343
  });
12344
+ /**
12345
+ * A parked ("stationary") object surfaced alongside occupancy — an object that
12346
+ * settled and stopped moving. It is NO LONGER a tracked object (the tracker was
12347
+ * told to forget it so it stops re-spawning tracks/events), but it IS still
12348
+ * physically present, so it keeps counting toward `frame` occupancy and is
12349
+ * listed here so the UI can show it in a dedicated "Stationary" section instead
12350
+ * of flooding the live event feed.
12351
+ */
12352
+ var StationaryObjectSchema = object({
12353
+ id: string(),
12354
+ className: string(),
12355
+ bbox: object({
12356
+ x: number(),
12357
+ y: number(),
12358
+ w: number(),
12359
+ h: number()
12360
+ }),
12361
+ frameWidth: number().int().nonnegative(),
12362
+ frameHeight: number().int().nonnegative(),
12363
+ /** When the source track was first seen. */
12364
+ firstSeenAt: number().int(),
12365
+ /** When the object was recognised as parked (promotion time). */
12366
+ becameStationaryAt: number().int(),
12367
+ /** Last frame a detection confirmed the object is still there. */
12368
+ lastConfirmedAt: number().int(),
12369
+ /** Enrichment label carried from the source track (identity / plate). */
12370
+ label: string().optional(),
12371
+ /** Native-resolution key-frame media key for the parked object's best image. */
12372
+ keyFrameMediaKey: string().optional()
12373
+ });
12104
12374
  var CameraOccupancySnapshotSchema = object({
12105
12375
  /** Frame timestamp of the inference result that produced this snapshot. */
12106
12376
  ts: number().int(),
@@ -12109,10 +12379,15 @@ var CameraOccupancySnapshotSchema = object({
12109
12379
  frameHeight: number().int().nonnegative(),
12110
12380
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
12111
12381
  zones: array(ZoneScopeBreakdownSchema).readonly(),
12112
- /** Frame-wide aggregate (everywhere, regardless of zone membership). */
12382
+ /** Frame-wide aggregate (everywhere, regardless of zone membership).
12383
+ * INCLUDES currently-confirmed stationary objects (they are still present). */
12113
12384
  frame: PerScopeBreakdownSchema,
12114
12385
  /** Detections that landed outside every zone. Empty when no zones defined. */
12115
- unzoned: PerScopeBreakdownSchema
12386
+ unzoned: PerScopeBreakdownSchema,
12387
+ /** Parked objects on this camera (additive — absent on legacy snapshots).
12388
+ * Surfaced separately so the UI shows them in a dedicated section rather
12389
+ * than as repeated tracks/events. */
12390
+ stationaryObjects: array(StationaryObjectSchema).readonly().optional()
12116
12391
  });
12117
12392
  /**
12118
12393
  * Time-series resolution. The history methods return one bucket per
@@ -12715,7 +12990,12 @@ var NotificationRuleConditionsSchema = object({
12715
12990
  clipDescription: object({
12716
12991
  text: string().min(1),
12717
12992
  minSimilarity: number().min(0).max(1)
12718
- }).optional()
12993
+ }).optional(),
12994
+ /** Match events whose recognized-entity label (face identity name or plate
12995
+ * vehicle name, propagated onto `event.data.label`) is one of these values.
12996
+ * Empty/absent → unaffected (back-compat). Enables "notify me when <named
12997
+ * vehicle/person> is seen". */
12998
+ labels: array(string()).readonly().optional()
12719
12999
  });
12720
13000
  var NotificationRuleTemplateSchema = object({
12721
13001
  title: string(),
@@ -13010,11 +13290,19 @@ method(object({
13010
13290
  * login page needs NO change.
13011
13291
  *
13012
13292
  * - `widget` — a Module-Federation widget the login page mounts (via
13013
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13014
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13015
- * addon bundle. The referenced widget also declares `preAuth: true` in
13016
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13017
- * stamps a public `bundleUrl` from `addonId` + `bundle`.
13293
+ * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
13294
+ * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
13295
+ * mechanism kept for future use; no shipped addon uses it on the login
13296
+ * page (the passkey ceremony below runs natively in the shell instead).
13297
+ *
13298
+ * - `passkey` — a declarative WebAuthn ceremony the shell renders
13299
+ * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
13300
+ * a remotely-loaded bundle). Carries the addon's effective `rpId` /
13301
+ * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
13302
+ * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
13303
+ * fetching any remote code pre-auth. Contribution stays unconditional —
13304
+ * enrollment state is never leaked pre-auth; visibility is a shell
13305
+ * decision.
13018
13306
  *
13019
13307
  * Every contribution carries a `stage`:
13020
13308
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -13028,30 +13316,45 @@ method(object({
13028
13316
  */
13029
13317
  /** When a login method renders in the two-phase login flow. */
13030
13318
  var LoginStageEnum = _enum(["primary", "second-factor"]);
13031
- /** One login-method contribution — redirect button OR pre-auth widget. */
13032
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13033
- kind: literal("redirect"),
13034
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13035
- id: string(),
13036
- /** Operator-facing button label. */
13037
- label: string(),
13038
- /** lucide-react icon name. */
13039
- icon: string().optional(),
13040
- /** Addon-owned HTTP route the button navigates to (GET). */
13041
- startUrl: string(),
13042
- stage: LoginStageEnum
13043
- }), object({
13044
- kind: literal("widget"),
13045
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13046
- id: string(),
13047
- /** Owning addon id drives the public bundle URL + the MF namespace. */
13048
- addonId: string(),
13049
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13050
- bundle: string(),
13051
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
13052
- remote: WidgetRemoteSchema,
13053
- stage: LoginStageEnum
13054
- })]);
13319
+ /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
13320
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [
13321
+ object({
13322
+ kind: literal("redirect"),
13323
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13324
+ id: string(),
13325
+ /** Operator-facing button label. */
13326
+ label: string(),
13327
+ /** lucide-react icon name. */
13328
+ icon: string().optional(),
13329
+ /** Addon-owned HTTP route the button navigates to (GET). */
13330
+ startUrl: string(),
13331
+ stage: LoginStageEnum
13332
+ }),
13333
+ object({
13334
+ kind: literal("widget"),
13335
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13336
+ id: string(),
13337
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13338
+ addonId: string(),
13339
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13340
+ bundle: string(),
13341
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13342
+ remote: WidgetRemoteSchema,
13343
+ stage: LoginStageEnum
13344
+ }),
13345
+ object({
13346
+ kind: literal("passkey"),
13347
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
13348
+ id: string(),
13349
+ /** Operator-facing button label. */
13350
+ label: string(),
13351
+ stage: LoginStageEnum,
13352
+ /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
13353
+ rpId: string(),
13354
+ /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
13355
+ origin: string().nullable()
13356
+ })
13357
+ ]);
13055
13358
  method(_void(), array(LoginMethodContributionSchema).readonly());
13056
13359
  /**
13057
13360
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -14972,14 +15275,14 @@ var TargetKindCapsSchema = object({
14972
15275
  * the union is large and not meant for runtime validation here; the exported
14973
15276
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14974
15277
  */
14975
- var ConfigSchemaPassthrough = unknown();
15278
+ var ConfigSchemaPassthrough$1 = unknown();
14976
15279
  var TargetKindSchema = object({
14977
15280
  kind: string(),
14978
15281
  label: string(),
14979
15282
  icon: string(),
14980
15283
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
14981
15284
  addonId: string(),
14982
- configSchema: ConfigSchemaPassthrough,
15285
+ configSchema: ConfigSchemaPassthrough$1,
14983
15286
  supportsDiscovery: boolean(),
14984
15287
  caps: TargetKindCapsSchema
14985
15288
  });
@@ -15032,6 +15335,298 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
15032
15335
  enabled: boolean()
15033
15336
  }), _void(), { kind: "mutation" });
15034
15337
  /**
15338
+ * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15339
+ * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15340
+ * caps stay wire-compatible without a circular cap→cap import.
15341
+ *
15342
+ * Errors are a discriminated-union RESULT, never thrown: the shape survives
15343
+ * every transport tier structurally, and failed calls still write usage rows.
15344
+ * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15345
+ */
15346
+ var LlmUsageSchema = object({
15347
+ inputTokens: number(),
15348
+ outputTokens: number()
15349
+ });
15350
+ var LlmErrorCodeSchema = _enum([
15351
+ "timeout",
15352
+ "rate-limited",
15353
+ "auth",
15354
+ "refusal",
15355
+ "bad-request",
15356
+ "unavailable",
15357
+ "no-profile",
15358
+ "budget-exceeded",
15359
+ "adapter-error"
15360
+ ]);
15361
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15362
+ ok: literal(true),
15363
+ text: string(),
15364
+ model: string(),
15365
+ usage: LlmUsageSchema,
15366
+ truncated: boolean(),
15367
+ latencyMs: number()
15368
+ }), object({
15369
+ ok: literal(false),
15370
+ code: LlmErrorCodeSchema,
15371
+ message: string(),
15372
+ retryAfterMs: number().optional()
15373
+ })]);
15374
+ /**
15375
+ * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
15376
+ * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15377
+ * notification-output.cap.ts:27-31 precedents).
15378
+ */
15379
+ var LlmImageSchema = object({
15380
+ bytes: _instanceof(Uint8Array),
15381
+ mimeType: string()
15382
+ });
15383
+ var LlmGenerateBaseInputSchema = object({
15384
+ /** Collection routing (the notification-output posture). */
15385
+ addonId: string().optional(),
15386
+ /** Explicit profile; else the resolution chain (spec §3). */
15387
+ profileId: string().optional(),
15388
+ /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15389
+ consumer: string(),
15390
+ system: string().optional(),
15391
+ /** v1: single-turn. `messages[]` is a v2 additive field. */
15392
+ prompt: string(),
15393
+ /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15394
+ jsonSchema: record(string(), unknown()).optional(),
15395
+ /** Per-call override of the profile default. */
15396
+ maxTokens: number().int().positive().optional(),
15397
+ temperature: number().optional()
15398
+ });
15399
+ /**
15400
+ * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
15401
+ * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15402
+ * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
15403
+ * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15404
+ * this only through the `llm` cap's methods.
15405
+ *
15406
+ * One running llama-server child per node in v1 (models are RAM-heavy).
15407
+ * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15408
+ * watchdog — operator decision #3).
15409
+ */
15410
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
15411
+ object({
15412
+ kind: literal("catalog"),
15413
+ catalogId: string()
15414
+ }),
15415
+ object({
15416
+ kind: literal("url"),
15417
+ url: string(),
15418
+ sha256: string().optional()
15419
+ }),
15420
+ object({
15421
+ kind: literal("path"),
15422
+ path: string()
15423
+ })
15424
+ ]);
15425
+ var ManagedRuntimeConfigSchema = object({
15426
+ /** WHERE the runtime lives — hub or any agent. */
15427
+ nodeId: string(),
15428
+ /** Closed for v1; 'ollama' is a v2 candidate. */
15429
+ engine: _enum(["llama-cpp"]),
15430
+ model: ManagedModelRefSchema,
15431
+ contextSize: number().int().default(4096),
15432
+ /** 0 = CPU-only. */
15433
+ gpuLayers: number().int().default(0),
15434
+ /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15435
+ threads: number().int().optional(),
15436
+ /** Concurrent slots. */
15437
+ parallel: number().int().default(1),
15438
+ /** Else lazy: first generate boots it. */
15439
+ autoStart: boolean().default(false),
15440
+ /** 0 = never; frees RAM after quiet periods. */
15441
+ idleStopMinutes: number().int().default(30)
15442
+ });
15443
+ var LlmRuntimeStatusSchema = object({
15444
+ /** Status is ALWAYS node-qualified. */
15445
+ nodeId: string(),
15446
+ state: _enum([
15447
+ "stopped",
15448
+ "downloading",
15449
+ "starting",
15450
+ "ready",
15451
+ "crashed",
15452
+ "failed"
15453
+ ]),
15454
+ pid: number().optional(),
15455
+ port: number().optional(),
15456
+ modelPath: string().optional(),
15457
+ modelId: string().optional(),
15458
+ downloadProgress: number().min(0).max(1).optional(),
15459
+ lastError: string().optional(),
15460
+ crashesInWindow: number(),
15461
+ /** Child RSS (sampled best-effort). */
15462
+ memoryBytes: number().optional(),
15463
+ vramBytes: number().optional()
15464
+ });
15465
+ var LlmNodeModelSchema = object({
15466
+ file: string(),
15467
+ sizeBytes: number(),
15468
+ catalogId: string().optional(),
15469
+ installedAt: number().optional()
15470
+ });
15471
+ var LlmRuntimeDiskUsageSchema = object({
15472
+ nodeId: string(),
15473
+ modelsBytes: number(),
15474
+ freeBytes: number().optional()
15475
+ });
15476
+ method(LlmGenerateBaseInputSchema.extend({
15477
+ images: array(LlmImageSchema).optional(),
15478
+ runtime: ManagedRuntimeConfigSchema,
15479
+ /** The managed profile's timeout, threaded by the hub provider. */
15480
+ timeoutMs: number().int().positive().optional()
15481
+ }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15482
+ kind: "mutation",
15483
+ auth: "admin"
15484
+ }), method(object({}), _void(), {
15485
+ kind: "mutation",
15486
+ auth: "admin"
15487
+ }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15488
+ kind: "mutation",
15489
+ auth: "admin"
15490
+ }), method(object({ file: string() }), _void(), {
15491
+ kind: "mutation",
15492
+ auth: "admin"
15493
+ }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15494
+ /**
15495
+ * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15496
+ * methods concat-fan across providers; single-row methods route to ONE
15497
+ * provider by the `addonId` in the call input (the notification-output
15498
+ * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15499
+ * (hub-placed); the cap stays open for future providers.
15500
+ *
15501
+ * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15502
+ * `apiKey` is a password field — providers REDACT it on read and merge on
15503
+ * write; a stored key NEVER round-trips to a client.
15504
+ */
15505
+ var LlmProfileKindSchema = _enum([
15506
+ "openai-compatible",
15507
+ "openai",
15508
+ "anthropic",
15509
+ "google",
15510
+ "managed-local"
15511
+ ]);
15512
+ var LlmProfileSchema = object({
15513
+ id: string(),
15514
+ name: string(),
15515
+ kind: LlmProfileKindSchema,
15516
+ /** Stamped by the provider — keeps the fanned catalog routable. */
15517
+ addonId: string(),
15518
+ enabled: boolean(),
15519
+ /** Vendor model id, or the managed runtime's loaded model. */
15520
+ model: string(),
15521
+ /** Required for openai-compatible; override for cloud kinds. */
15522
+ baseUrl: string().optional(),
15523
+ /** ConfigUISchema type:'password' — never round-trips (spec §5). */
15524
+ apiKey: string().optional(),
15525
+ supportsVision: boolean(),
15526
+ temperature: number().min(0).max(2).optional(),
15527
+ maxTokens: number().int().positive().optional(),
15528
+ timeoutMs: number().int().positive().default(6e4),
15529
+ extraHeaders: record(string(), string()).optional(),
15530
+ /** kind === 'managed-local' only (spec §4). */
15531
+ runtime: ManagedRuntimeConfigSchema.optional()
15532
+ });
15533
+ /** ConfigUISchema tree passed through untyped on the wire (the
15534
+ * notification-output `ConfigSchemaPassthrough` precedent at
15535
+ * notification-output.cap.ts:151); the exported TS type re-tightens it. */
15536
+ var ConfigSchemaPassthrough = unknown();
15537
+ var LlmProfileKindDescriptorSchema = object({
15538
+ kind: LlmProfileKindSchema,
15539
+ label: string(),
15540
+ icon: string(),
15541
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
15542
+ addonId: string(),
15543
+ configSchema: ConfigSchemaPassthrough
15544
+ });
15545
+ var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
15546
+ var LlmDefaultSchema = object({
15547
+ selector: LlmDefaultSelectorSchema,
15548
+ profileId: string()
15549
+ });
15550
+ /** Server-side rollup row — getUsage never dumps raw call rows (spec §6). */
15551
+ var LlmUsageRollupSchema = object({
15552
+ day: string(),
15553
+ consumer: string(),
15554
+ profileId: string(),
15555
+ calls: number(),
15556
+ okCalls: number(),
15557
+ errorCalls: number(),
15558
+ inputTokens: number(),
15559
+ outputTokens: number(),
15560
+ avgLatencyMs: number()
15561
+ });
15562
+ /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
15563
+ var ManagedModelCatalogEntrySchema = object({
15564
+ id: string(),
15565
+ label: string(),
15566
+ family: string(),
15567
+ purpose: _enum(["text", "vision"]),
15568
+ url: string(),
15569
+ sha256: string(),
15570
+ sizeBytes: number(),
15571
+ quantization: string(),
15572
+ /** Load-time guidance shown in the picker. */
15573
+ minRamBytes: number(),
15574
+ contextSizeDefault: number().int(),
15575
+ /** Vision models: companion projector file. */
15576
+ mmprojUrl: string().optional()
15577
+ });
15578
+ var LlmRuntimeNodeSchema = object({
15579
+ nodeId: string(),
15580
+ reachable: boolean(),
15581
+ status: LlmRuntimeStatusSchema.optional(),
15582
+ disk: LlmRuntimeDiskUsageSchema.optional(),
15583
+ error: string().optional()
15584
+ });
15585
+ var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15586
+ var ProfileRefInputSchema = object({
15587
+ addonId: string(),
15588
+ profileId: string()
15589
+ });
15590
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
15591
+ kind: "mutation",
15592
+ auth: "admin"
15593
+ }), method(ProfileRefInputSchema, _void(), {
15594
+ kind: "mutation",
15595
+ auth: "admin"
15596
+ }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
15597
+ kind: "mutation",
15598
+ auth: "admin"
15599
+ }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
15600
+ selector: LlmDefaultSelectorSchema,
15601
+ profileId: string().nullable()
15602
+ }), _void(), {
15603
+ kind: "mutation",
15604
+ auth: "admin"
15605
+ }), method(object({
15606
+ since: number().optional(),
15607
+ until: number().optional(),
15608
+ consumer: string().optional(),
15609
+ profileId: string().optional()
15610
+ }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
15611
+ nodeId: string(),
15612
+ model: ManagedModelRefSchema
15613
+ }), _void(), {
15614
+ kind: "mutation",
15615
+ auth: "admin"
15616
+ }), method(object({
15617
+ nodeId: string(),
15618
+ file: string()
15619
+ }), _void(), {
15620
+ kind: "mutation",
15621
+ auth: "admin"
15622
+ }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
15623
+ kind: "mutation",
15624
+ auth: "admin"
15625
+ }), method(ProfileRefInputSchema, _void(), {
15626
+ kind: "mutation",
15627
+ auth: "admin"
15628
+ });
15629
+ /**
15035
15630
  * Zod schemas for persisted record types.
15036
15631
  *
15037
15632
  * These schemas serve as the single source of truth for types that are
@@ -15233,6 +15828,22 @@ var TrackSnapshotSchema = object({
15233
15828
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
15234
15829
  mediaKey: string()
15235
15830
  });
15831
+ /**
15832
+ * One audio-classification label heard on the track's camera while the
15833
+ * track was alive, aggregated per label. An "episode" is one persisted
15834
+ * audio event (the confident-classification path: score ≥ the device's
15835
+ * `classificationMinScore`, class-change-or-heartbeat coalesced) — NOT
15836
+ * one 32 ms inference chunk, so counts stay human-scaled.
15837
+ */
15838
+ var TrackAudioLabelSchema = object({
15839
+ label: string(),
15840
+ /** Highest classification score observed across the label's episodes. */
15841
+ peakScore: number(),
15842
+ /** Number of coalesced audio-event episodes carrying this label. */
15843
+ count: number(),
15844
+ firstAt: number(),
15845
+ lastAt: number()
15846
+ });
15236
15847
  var TrackSchema = object({
15237
15848
  trackId: string(),
15238
15849
  deviceId: number(),
@@ -15247,6 +15858,10 @@ var TrackSchema = object({
15247
15858
  snapshots: array(TrackSnapshotSchema).readonly(),
15248
15859
  /** Deduplicated zones the track has entered at least once. */
15249
15860
  zonesVisited: array(string()).readonly(),
15861
+ /** Deduplicated set of detector classes observed for this track over its
15862
+ * life (a track may be reclassified, e.g. person→vehicle). Absent on
15863
+ * legacy rows written before class accumulation shipped. */
15864
+ classes: array(string()).readonly().optional(),
15250
15865
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15251
15866
  totalDistance: number(),
15252
15867
  state: TrackStateSchema,
@@ -15260,7 +15875,11 @@ var TrackSchema = object({
15260
15875
  bestEventId: string().optional(),
15261
15876
  /** Tag of the importance sub-signal that dominated the score
15262
15877
  * (identity|dwell|proximity|class|confidence|travel|zone). */
15263
- importanceReason: string().optional()
15878
+ importanceReason: string().optional(),
15879
+ /** Audio-classification labels heard on the camera during the track's
15880
+ * life (score ≥ device `classificationMinScore`), aggregated per label.
15881
+ * Absent on legacy rows / tracks with no confident audio. */
15882
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional()
15264
15883
  });
15265
15884
  var BaseEventFields = {
15266
15885
  id: string(),
@@ -15408,7 +16027,7 @@ var KeyEventSchema = object({
15408
16027
  /** Track lifetime in ms (lastSeen - firstSeen). */
15409
16028
  windowMs: number().optional()
15410
16029
  });
15411
- var TrackedDetectionSchema = object({
16030
+ object({
15412
16031
  trackId: string(),
15413
16032
  className: string(),
15414
16033
  confidence: number(),
@@ -15416,6 +16035,23 @@ var TrackedDetectionSchema = object({
15416
16035
  zones: array(string()).readonly(),
15417
16036
  state: TrackStateSchema
15418
16037
  });
16038
+ var OverlayDetectionSchema = looseObject({
16039
+ id: string(),
16040
+ kind: _enum(["first-level", "detail"]),
16041
+ macroClass: string(),
16042
+ score: number(),
16043
+ bbox: object({
16044
+ x: number(),
16045
+ y: number(),
16046
+ width: number(),
16047
+ height: number()
16048
+ }),
16049
+ labels: array(looseObject({
16050
+ label: string(),
16051
+ score: number()
16052
+ })).readonly(),
16053
+ parentId: string().optional()
16054
+ });
15419
16055
  var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
15420
16056
  var SearchObjectEventsInput = object({
15421
16057
  text: string(),
@@ -15426,6 +16062,20 @@ var SearchObjectEventsInput = object({
15426
16062
  limit: number().default(50),
15427
16063
  minScore: number().min(0).max(1).default(.2)
15428
16064
  });
16065
+ var TrackCascadeCountsSchema = object({
16066
+ /** Persisted track roots deleted (authoritative). */
16067
+ tracks: number().int(),
16068
+ /** Object events removed with their tracks (best-effort; see note above). */
16069
+ events: number().int(),
16070
+ /** Track/face/plate-owned media removed (best-effort). Never identity media. */
16071
+ media: number().int(),
16072
+ /** Unassigned (non-enrolled) face reads removed (best-effort). */
16073
+ faces: number().int(),
16074
+ /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16075
+ plates: number().int(),
16076
+ /** Per-track CLIP search vectors removed (best-effort). */
16077
+ embeddings: number().int()
16078
+ });
15429
16079
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
15430
16080
  deviceId: number(),
15431
16081
  trackId: string()
@@ -15458,15 +16108,33 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15458
16108
  kind: "mutation",
15459
16109
  auth: "admin"
15460
16110
  }), method(object({
15461
- eventId: string(),
15462
- kind: MediaFileKindEnum.optional()
15463
- }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
15464
16111
  deviceId: number(),
15465
- timestamp: number(),
15466
- frameWidth: number(),
15467
- frameHeight: number(),
15468
- detections: array(TrackedDetectionSchema).readonly()
15469
- }), object({
16112
+ cutoffMs: number()
16113
+ }), TrackCascadeCountsSchema, {
16114
+ kind: "mutation",
16115
+ auth: "admin"
16116
+ }), method(object({ deviceId: number() }), TrackCascadeCountsSchema, {
16117
+ kind: "mutation",
16118
+ auth: "admin"
16119
+ }), method(object({
16120
+ deviceId: number(),
16121
+ trackIds: array(string()).min(1)
16122
+ }), object({
16123
+ deleted: number().int(),
16124
+ failed: array(string()).readonly()
16125
+ }), {
16126
+ kind: "mutation",
16127
+ auth: "admin"
16128
+ }), method(object({
16129
+ eventId: string(),
16130
+ kind: MediaFileKindEnum.optional()
16131
+ }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
16132
+ deviceId: number(),
16133
+ timestamp: number(),
16134
+ frameWidth: number(),
16135
+ frameHeight: number(),
16136
+ detections: array(OverlayDetectionSchema).readonly()
16137
+ }), object({
15470
16138
  deviceId: number(),
15471
16139
  trackId: string(),
15472
16140
  className: string()
@@ -18234,6 +18902,24 @@ DeviceType.Camera, method(object({
18234
18902
  deviceId: number(),
18235
18903
  status: OsdStatusSchema
18236
18904
  });
18905
+ var VehicleSchema = object({
18906
+ id: string(),
18907
+ name: string(),
18908
+ sampleCount: number().int().nonnegative(),
18909
+ coverMediaKey: string().optional(),
18910
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
18911
+ coverBase64: string().optional()
18912
+ });
18913
+ var VehicleSampleInfoSchema = object({
18914
+ id: string(),
18915
+ /** The plate text this sample enrolled (self-labeling — no embedding). */
18916
+ text: string(),
18917
+ /** OCR confidence of the enrolled read (0..1). */
18918
+ score: number(),
18919
+ addedAt: number().int(),
18920
+ deviceId: number().int().optional(),
18921
+ base64: string().optional()
18922
+ });
18237
18923
  var PlateInfoSchema = object({
18238
18924
  plateId: string(),
18239
18925
  deviceId: number().int(),
@@ -18245,6 +18931,16 @@ var PlateInfoSchema = object({
18245
18931
  score: number(),
18246
18932
  /** True when the text was manually corrected (exempt from retention). */
18247
18933
  corrected: boolean(),
18934
+ /** Recognized vehicle id when a matcher lookup named this read (SQL NULL → absent). */
18935
+ recognizedVehicleId: string().optional(),
18936
+ /** Resolved vehicle name for `recognizedVehicleId` (UI convenience). */
18937
+ vehicleName: string().optional(),
18938
+ /** True once the read was assigned to a vehicle (enrolled as a sample). */
18939
+ assigned: boolean(),
18940
+ /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
18941
+ plateBbox: BoundingBoxSchema.optional(),
18942
+ /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
18943
+ keyFrameMediaKey: string().optional(),
18248
18944
  base64: string().optional()
18249
18945
  });
18250
18946
  var MediaFileLiteSchema = object({
@@ -18284,6 +18980,48 @@ method(object({
18284
18980
  }), method(object({ plateId: string() }), _void(), {
18285
18981
  kind: "mutation",
18286
18982
  auth: "admin"
18983
+ }), method(_void(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
18984
+ kind: "mutation",
18985
+ auth: "admin"
18986
+ }), method(object({
18987
+ id: string(),
18988
+ name: string().min(1)
18989
+ }), _void(), {
18990
+ kind: "mutation",
18991
+ auth: "admin"
18992
+ }), method(object({ id: string() }), _void(), {
18993
+ kind: "mutation",
18994
+ auth: "admin"
18995
+ }), method(object({ vehicleId: string() }), array(VehicleSampleInfoSchema).readonly()), method(object({
18996
+ vehicleId: string(),
18997
+ sampleId: string()
18998
+ }), _void(), {
18999
+ kind: "mutation",
19000
+ auth: "admin"
19001
+ }), method(object({
19002
+ plateId: string(),
19003
+ vehicleId: string()
19004
+ }), _void(), {
19005
+ kind: "mutation",
19006
+ auth: "admin"
19007
+ }), method(object({ plateId: string() }), _void(), {
19008
+ kind: "mutation",
19009
+ auth: "admin"
19010
+ }), method(object({
19011
+ plateIds: array(string()).min(1),
19012
+ vehicleId: string()
19013
+ }), object({
19014
+ assigned: number().int(),
19015
+ failed: array(string()).readonly()
19016
+ }), {
19017
+ kind: "mutation",
19018
+ auth: "admin"
19019
+ }), method(object({ plateIds: array(string()).min(1) }), object({
19020
+ unassigned: number().int(),
19021
+ failed: array(string()).readonly()
19022
+ }), {
19023
+ kind: "mutation",
19024
+ auth: "admin"
18287
19025
  });
18288
19026
  var ModelFormatSchema = _enum(MODEL_FORMATS);
18289
19027
  var HwAccelBackendInputSchema = _enum([
@@ -18362,6 +19100,10 @@ var GpuInfoSchema = object({
18362
19100
  memoryMB: number().optional()
18363
19101
  });
18364
19102
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
19103
+ var CoralInfoSchema = object({
19104
+ type: literal("coral-edgetpu"),
19105
+ bus: string().optional()
19106
+ });
18365
19107
  var HardwareInfoSchema = object({
18366
19108
  platform: HardwarePlatformSchema,
18367
19109
  arch: HardwareArchSchema,
@@ -18370,7 +19112,8 @@ var HardwareInfoSchema = object({
18370
19112
  totalRAM_MB: number(),
18371
19113
  availableRAM_MB: number(),
18372
19114
  gpu: GpuInfoSchema.nullable(),
18373
- npu: NpuInfoSchema.nullable()
19115
+ npu: NpuInfoSchema.nullable(),
19116
+ coral: CoralInfoSchema.nullable().optional()
18374
19117
  });
18375
19118
  var PlatformScoreSchema = object({
18376
19119
  runtime: _enum(["node", "python"]),
@@ -20829,6 +21572,168 @@ Object.freeze({
20829
21572
  addonId: null,
20830
21573
  access: "create"
20831
21574
  },
21575
+ "llm.deleteModel": {
21576
+ capName: "llm",
21577
+ capScope: "system",
21578
+ addonId: null,
21579
+ access: "delete"
21580
+ },
21581
+ "llm.deleteProfile": {
21582
+ capName: "llm",
21583
+ capScope: "system",
21584
+ addonId: null,
21585
+ access: "delete"
21586
+ },
21587
+ "llm.generate": {
21588
+ capName: "llm",
21589
+ capScope: "system",
21590
+ addonId: null,
21591
+ access: "create"
21592
+ },
21593
+ "llm.generateVision": {
21594
+ capName: "llm",
21595
+ capScope: "system",
21596
+ addonId: null,
21597
+ access: "create"
21598
+ },
21599
+ "llm.getDefaults": {
21600
+ capName: "llm",
21601
+ capScope: "system",
21602
+ addonId: null,
21603
+ access: "view"
21604
+ },
21605
+ "llm.getRuntimeStatus": {
21606
+ capName: "llm",
21607
+ capScope: "system",
21608
+ addonId: null,
21609
+ access: "view"
21610
+ },
21611
+ "llm.getUsage": {
21612
+ capName: "llm",
21613
+ capScope: "system",
21614
+ addonId: null,
21615
+ access: "view"
21616
+ },
21617
+ "llm.installModel": {
21618
+ capName: "llm",
21619
+ capScope: "system",
21620
+ addonId: null,
21621
+ access: "create"
21622
+ },
21623
+ "llm.listModelCatalog": {
21624
+ capName: "llm",
21625
+ capScope: "system",
21626
+ addonId: null,
21627
+ access: "view"
21628
+ },
21629
+ "llm.listModels": {
21630
+ capName: "llm",
21631
+ capScope: "system",
21632
+ addonId: null,
21633
+ access: "view"
21634
+ },
21635
+ "llm.listNodeModels": {
21636
+ capName: "llm",
21637
+ capScope: "system",
21638
+ addonId: null,
21639
+ access: "view"
21640
+ },
21641
+ "llm.listProfileKinds": {
21642
+ capName: "llm",
21643
+ capScope: "system",
21644
+ addonId: null,
21645
+ access: "view"
21646
+ },
21647
+ "llm.listProfiles": {
21648
+ capName: "llm",
21649
+ capScope: "system",
21650
+ addonId: null,
21651
+ access: "view"
21652
+ },
21653
+ "llm.listRuntimeNodes": {
21654
+ capName: "llm",
21655
+ capScope: "system",
21656
+ addonId: null,
21657
+ access: "view"
21658
+ },
21659
+ "llm.setDefault": {
21660
+ capName: "llm",
21661
+ capScope: "system",
21662
+ addonId: null,
21663
+ access: "create"
21664
+ },
21665
+ "llm.startRuntime": {
21666
+ capName: "llm",
21667
+ capScope: "system",
21668
+ addonId: null,
21669
+ access: "create"
21670
+ },
21671
+ "llm.stopRuntime": {
21672
+ capName: "llm",
21673
+ capScope: "system",
21674
+ addonId: null,
21675
+ access: "create"
21676
+ },
21677
+ "llm.testProfile": {
21678
+ capName: "llm",
21679
+ capScope: "system",
21680
+ addonId: null,
21681
+ access: "create"
21682
+ },
21683
+ "llm.upsertProfile": {
21684
+ capName: "llm",
21685
+ capScope: "system",
21686
+ addonId: null,
21687
+ access: "create"
21688
+ },
21689
+ "llmRuntime.complete": {
21690
+ capName: "llm-runtime",
21691
+ capScope: "system",
21692
+ addonId: null,
21693
+ access: "create"
21694
+ },
21695
+ "llmRuntime.deleteModel": {
21696
+ capName: "llm-runtime",
21697
+ capScope: "system",
21698
+ addonId: null,
21699
+ access: "delete"
21700
+ },
21701
+ "llmRuntime.ensureStarted": {
21702
+ capName: "llm-runtime",
21703
+ capScope: "system",
21704
+ addonId: null,
21705
+ access: "create"
21706
+ },
21707
+ "llmRuntime.getDiskUsage": {
21708
+ capName: "llm-runtime",
21709
+ capScope: "system",
21710
+ addonId: null,
21711
+ access: "view"
21712
+ },
21713
+ "llmRuntime.installModel": {
21714
+ capName: "llm-runtime",
21715
+ capScope: "system",
21716
+ addonId: null,
21717
+ access: "create"
21718
+ },
21719
+ "llmRuntime.listLocalModels": {
21720
+ capName: "llm-runtime",
21721
+ capScope: "system",
21722
+ addonId: null,
21723
+ access: "view"
21724
+ },
21725
+ "llmRuntime.status": {
21726
+ capName: "llm-runtime",
21727
+ capScope: "system",
21728
+ addonId: null,
21729
+ access: "view"
21730
+ },
21731
+ "llmRuntime.stop": {
21732
+ capName: "llm-runtime",
21733
+ capScope: "system",
21734
+ addonId: null,
21735
+ access: "create"
21736
+ },
20832
21737
  "localNetwork.getAllowedAddresses": {
20833
21738
  capName: "local-network",
20834
21739
  capScope: "system",
@@ -21459,6 +22364,12 @@ Object.freeze({
21459
22364
  addonId: null,
21460
22365
  access: "delete"
21461
22366
  },
22367
+ "pipelineAnalytics.deleteTracks": {
22368
+ capName: "pipeline-analytics",
22369
+ capScope: "device",
22370
+ addonId: null,
22371
+ access: "delete"
22372
+ },
21462
22373
  "pipelineAnalytics.getActiveTracks": {
21463
22374
  capName: "pipeline-analytics",
21464
22375
  capScope: "device",
@@ -21525,12 +22436,24 @@ Object.freeze({
21525
22436
  addonId: null,
21526
22437
  access: "create"
21527
22438
  },
22439
+ "pipelineAnalytics.pruneTracksBefore": {
22440
+ capName: "pipeline-analytics",
22441
+ capScope: "device",
22442
+ addonId: null,
22443
+ access: "create"
22444
+ },
21528
22445
  "pipelineAnalytics.searchObjectEvents": {
21529
22446
  capName: "pipeline-analytics",
21530
22447
  capScope: "device",
21531
22448
  addonId: null,
21532
22449
  access: "view"
21533
22450
  },
22451
+ "pipelineAnalytics.wipeAllAnalytics": {
22452
+ capName: "pipeline-analytics",
22453
+ capScope: "device",
22454
+ addonId: null,
22455
+ access: "delete"
22456
+ },
21534
22457
  "pipelineExecutor.cacheFrameInPool": {
21535
22458
  capName: "pipeline-executor",
21536
22459
  capScope: "system",
@@ -22029,18 +22952,48 @@ Object.freeze({
22029
22952
  addonId: null,
22030
22953
  access: "create"
22031
22954
  },
22955
+ "pipelineRunner.runDetailSubtree": {
22956
+ capName: "pipeline-runner",
22957
+ capScope: "system",
22958
+ addonId: null,
22959
+ access: "create"
22960
+ },
22961
+ "plateGallery.assignPlate": {
22962
+ capName: "plate-gallery",
22963
+ capScope: "system",
22964
+ addonId: null,
22965
+ access: "create"
22966
+ },
22967
+ "plateGallery.assignPlates": {
22968
+ capName: "plate-gallery",
22969
+ capScope: "system",
22970
+ addonId: null,
22971
+ access: "create"
22972
+ },
22032
22973
  "plateGallery.correctPlateText": {
22033
22974
  capName: "plate-gallery",
22034
22975
  capScope: "system",
22035
22976
  addonId: null,
22036
22977
  access: "create"
22037
22978
  },
22979
+ "plateGallery.createVehicle": {
22980
+ capName: "plate-gallery",
22981
+ capScope: "system",
22982
+ addonId: null,
22983
+ access: "create"
22984
+ },
22038
22985
  "plateGallery.deletePlate": {
22039
22986
  capName: "plate-gallery",
22040
22987
  capScope: "system",
22041
22988
  addonId: null,
22042
22989
  access: "delete"
22043
22990
  },
22991
+ "plateGallery.deleteVehicle": {
22992
+ capName: "plate-gallery",
22993
+ capScope: "system",
22994
+ addonId: null,
22995
+ access: "delete"
22996
+ },
22044
22997
  "plateGallery.getPlateByTrack": {
22045
22998
  capName: "plate-gallery",
22046
22999
  capScope: "system",
@@ -22059,6 +23012,30 @@ Object.freeze({
22059
23012
  addonId: null,
22060
23013
  access: "view"
22061
23014
  },
23015
+ "plateGallery.listVehicles": {
23016
+ capName: "plate-gallery",
23017
+ capScope: "system",
23018
+ addonId: null,
23019
+ access: "view"
23020
+ },
23021
+ "plateGallery.listVehicleSamples": {
23022
+ capName: "plate-gallery",
23023
+ capScope: "system",
23024
+ addonId: null,
23025
+ access: "view"
23026
+ },
23027
+ "plateGallery.removeVehicleSample": {
23028
+ capName: "plate-gallery",
23029
+ capScope: "system",
23030
+ addonId: null,
23031
+ access: "delete"
23032
+ },
23033
+ "plateGallery.renameVehicle": {
23034
+ capName: "plate-gallery",
23035
+ capScope: "system",
23036
+ addonId: null,
23037
+ access: "create"
23038
+ },
22062
23039
  "plateGallery.searchPlates": {
22063
23040
  capName: "plate-gallery",
22064
23041
  capScope: "system",
@@ -22071,6 +23048,18 @@ Object.freeze({
22071
23048
  addonId: null,
22072
23049
  access: "view"
22073
23050
  },
23051
+ "plateGallery.unassignPlate": {
23052
+ capName: "plate-gallery",
23053
+ capScope: "system",
23054
+ addonId: null,
23055
+ access: "create"
23056
+ },
23057
+ "plateGallery.unassignPlates": {
23058
+ capName: "plate-gallery",
23059
+ capScope: "system",
23060
+ addonId: null,
23061
+ access: "create"
23062
+ },
22074
23063
  "platformProbe.getCapabilities": {
22075
23064
  capName: "platform-probe",
22076
23065
  capScope: "system",
@@ -22263,6 +23252,48 @@ Object.freeze({
22263
23252
  addonId: null,
22264
23253
  access: "create"
22265
23254
  },
23255
+ "sceneMonitor.captureReference": {
23256
+ capName: "scene-monitor",
23257
+ capScope: "device",
23258
+ addonId: null,
23259
+ access: "create"
23260
+ },
23261
+ "sceneMonitor.createScene": {
23262
+ capName: "scene-monitor",
23263
+ capScope: "device",
23264
+ addonId: null,
23265
+ access: "create"
23266
+ },
23267
+ "sceneMonitor.deleteReference": {
23268
+ capName: "scene-monitor",
23269
+ capScope: "device",
23270
+ addonId: null,
23271
+ access: "delete"
23272
+ },
23273
+ "sceneMonitor.deleteScene": {
23274
+ capName: "scene-monitor",
23275
+ capScope: "device",
23276
+ addonId: null,
23277
+ access: "delete"
23278
+ },
23279
+ "sceneMonitor.listScenes": {
23280
+ capName: "scene-monitor",
23281
+ capScope: "device",
23282
+ addonId: null,
23283
+ access: "view"
23284
+ },
23285
+ "sceneMonitor.recheckNow": {
23286
+ capName: "scene-monitor",
23287
+ capScope: "device",
23288
+ addonId: null,
23289
+ access: "create"
23290
+ },
23291
+ "sceneMonitor.updateScene": {
23292
+ capName: "scene-monitor",
23293
+ capScope: "device",
23294
+ addonId: null,
23295
+ access: "create"
23296
+ },
22266
23297
  "scriptRunner.run": {
22267
23298
  capName: "script-runner",
22268
23299
  capScope: "device",
@@ -23347,6 +24378,7 @@ Object.freeze({
23347
24378
  Object.freeze({
23348
24379
  "broker": "broker",
23349
24380
  "device-export": "device-export",
24381
+ "llm": "ai",
23350
24382
  "mesh-network": "mesh",
23351
24383
  "mqtt-broker": "broker",
23352
24384
  "network-access": "ingress",
@@ -23533,9 +24565,9 @@ async function resolveDecoderBackend(api, nodeId, logger) {
23533
24565
  //#region src/shared/notifying-ring-buffer.ts
23534
24566
  /**
23535
24567
  * A {@link RingBuffer} that can notify a single blocked consumer the moment an
23536
- * item commits — the server side of the `pullFrames`/`pullHandles` long-poll.
24568
+ * item commits — the server side of the `pullFrames` long-poll.
23537
24569
  *
23538
- * The decoder's `onFrame`/`onFrameHandle` callback drives `push()`; a consumer
24570
+ * The decoder's `onFrame` callback drives `push()`; a consumer
23539
24571
  * that finds the ring momentarily empty calls {@link waitForItem} and is woken
23540
24572
  * on the very next `push()` (a real commit signal) rather than spinning at
23541
24573
  * ~1kHz on `setTimeout(…,1)`. If nothing commits it resolves after `waitMs`,
@@ -23979,8 +25011,7 @@ var FrameStreamSplitter = class {
23979
25011
  //#region src/ffmpeg-decoder-session.ts
23980
25012
  /**
23981
25013
  * FfmpegDecoderSession — an `IDecoderSession` that decodes video in an **ffmpeg
23982
- * subprocess** and feeds the shared-memory frame ring (Phase 5 / D9), the same
23983
- * ring `decoder-nodeav` writes into.
25014
+ * subprocess** and delivers each decoded frame via the `onFrame` callback.
23984
25015
  *
23985
25016
  * Why a subprocess: on the Intel hub, node-av's in-process VA-API decode path
23986
25017
  * SIGBUSes (uncatchable) on 8MP h264 and kills the whole decoder runner. ffmpeg
@@ -23993,15 +25024,13 @@ var FrameStreamSplitter = class {
23993
25024
  * ffmpeg → `-vf <scale_vaapi=…,hwdownload,… | hwdownload,…,scale=…>` (GPU or
23994
25025
  * CPU scale, per {@link buildFfmpegDecodeArgs})
23995
25026
  * raw video → ffmpeg stdout (pipe:1) → a {@link FrameStreamSplitter} that fills
23996
- * each frame's bytes DIRECTLY into its shm ring slot (zero-copy
23997
- * `beginFrame`/`commitFrame`) `FrameHandle` (shm mode), or into a
23998
- * per-frame buffer → `DecodedFrame` via `onFrame` (callback mode).
23999
- * No `Buffer.concat` accumulator, one copy per byte, none for dropped
24000
- * frames.
25027
+ * each frame's bytes into a per-frame buffer `DecodedFrame` via
25028
+ * `onFrame`. No `Buffer.concat` accumulator, one copy per byte, none
25029
+ * for dropped frames.
24001
25030
  *
24002
- * Output geometry: the broker does not pass source width/height for the shm
24003
- * path, so ffmpeg picks the height (`scale=min(iw\,maxW):-2`) and the session
24004
- * learns the exact output W×H by parsing the muxer's `Output #0` stderr block
25031
+ * Output geometry: the broker does not pass source width/height, so ffmpeg picks
25032
+ * the height (`scale=min(iw\,maxW):-2`) and the session learns the exact output
25033
+ * W×H by parsing the muxer's `Output #0` stderr block
24005
25034
  * ({@link parseFfmpegOutputDims}). Until dims are known, stdout is buffered.
24006
25035
  */
24007
25036
  var noopLogger = {
@@ -24041,8 +25070,6 @@ var MAX_GOP_BUFFER_BYTES = 16 * 1024 * 1024;
24041
25070
  var FfmpegDecoderSession = class {
24042
25071
  config;
24043
25072
  logger;
24044
- frameSink;
24045
- nodeId;
24046
25073
  renderDevice;
24047
25074
  ffmpegPath;
24048
25075
  /**
@@ -24092,9 +25119,6 @@ var FfmpegDecoderSession = class {
24092
25119
  /** Stdout chunks received BEFORE geometry is known — replayed into the
24093
25120
  * splitter once `frameSize` is learned. A short-lived list (no concat). */
24094
25121
  pendingStdout = [];
24095
- /** The shm ring slot the splitter is currently filling, if any — aborted on
24096
- * respawn / teardown so a half-filled slot never publishes. */
24097
- openSlot = null;
24098
25122
  stderrAccum = "";
24099
25123
  destroyed = false;
24100
25124
  pixel;
@@ -24104,9 +25128,7 @@ var FfmpegDecoderSession = class {
24104
25128
  outWidth = 0;
24105
25129
  outHeight = 0;
24106
25130
  frameSize = 0;
24107
- frameRingSink = null;
24108
25131
  frameCallbacks = /* @__PURE__ */ new Set();
24109
- handleCallbacks = /* @__PURE__ */ new Set();
24110
25132
  frameDropper;
24111
25133
  inputPackets = 0;
24112
25134
  /**
@@ -24130,8 +25152,6 @@ var FfmpegDecoderSession = class {
24130
25152
  if (typeof config.deviceId === "number") sessionTags["deviceId"] = config.deviceId;
24131
25153
  if (typeof config.tag === "string" && config.tag.length > 0) sessionTags["tag"] = config.tag;
24132
25154
  this.logger = Object.keys(sessionTags).length > 0 ? logger.withTags(sessionTags) : logger;
24133
- this.frameSink = options?.frameSink ?? "callback";
24134
- this.nodeId = options?.nodeId ?? "local";
24135
25155
  this.renderDevice = options?.renderDevice;
24136
25156
  this.ffmpegPath = options?.ffmpegPath ?? "ffmpeg";
24137
25157
  const seededChain = options?.hwaccelChain ?? (options?.hwaccel != null && !isSoftwareHwAccel(options.hwaccel) ? [options.hwaccel] : []);
@@ -24144,10 +25164,6 @@ var FfmpegDecoderSession = class {
24144
25164
  this.frameFormat = this.pixel === "gray" ? "gray" : "rgb";
24145
25165
  this.frameDropper = new FrameDropper(config.maxFps);
24146
25166
  }
24147
- /** Exposed so the owning addon can surface ring stats via `getShmStats`. */
24148
- get frameRingSinkOrNull() {
24149
- return this.frameRingSink;
24150
- }
24151
25167
  get isPullMode() {
24152
25168
  return this.pullUrl !== null;
24153
25169
  }
@@ -24169,26 +25185,11 @@ var FfmpegDecoderSession = class {
24169
25185
  this.logger.info("ffmpeg decoder: pull-mode openStream — dialing input URL", { meta: { url } });
24170
25186
  this.spawnFfmpeg();
24171
25187
  }
24172
- ensureFrameRingSink() {
24173
- if (this.frameRingSink === null) {
24174
- const seedParts = [];
24175
- if (typeof this.config.deviceId === "number") seedParts.push(String(this.config.deviceId));
24176
- if (typeof this.config.tag === "string" && this.config.tag.length > 0) seedParts.push(this.config.tag);
24177
- const seed = seedParts.length > 0 ? seedParts.join(":") : "anon";
24178
- this.frameRingSink = new DecoderFrameRingSink({
24179
- seed,
24180
- logger: this.logger,
24181
- nodeId: this.nodeId
24182
- });
24183
- }
24184
- return this.frameRingSink;
24185
- }
24186
25188
  spawnFfmpeg() {
24187
25189
  if (this.destroyed) return;
24188
25190
  this.spawnAttempted = true;
24189
25191
  this.clearPullRedial();
24190
25192
  this.killFfmpeg();
24191
- this.abortOpenSlot();
24192
25193
  this.splitter = null;
24193
25194
  this.pendingStdout = [];
24194
25195
  this.stderrAccum = "";
@@ -24234,8 +25235,7 @@ var FfmpegDecoderSession = class {
24234
25235
  hwaccel: this.hwaccel,
24235
25236
  gpuScale: this.activeGpuScaleFilter ?? "cpu",
24236
25237
  codec: this.config.codec,
24237
- format: this.frameFormat,
24238
- sink: this.frameSink
25238
+ format: this.frameFormat
24239
25239
  } });
24240
25240
  }
24241
25241
  killFfmpeg() {
@@ -24373,36 +25373,10 @@ var FfmpegDecoderSession = class {
24373
25373
  return {
24374
25374
  startFrame: () => {
24375
25375
  if (!this.frameDropper.shouldKeep()) return null;
24376
- if (this.frameSink === "shm") {
24377
- const reserved = this.ensureFrameRingSink().beginFrame(this.outWidth, this.outHeight, this.frameFormat);
24378
- if (reserved === null) return null;
24379
- this.openSlot = reserved.slot;
24380
- return reserved.buffer;
24381
- }
24382
25376
  return Buffer.allocUnsafe(this.frameSize);
24383
25377
  },
24384
25378
  finishFrame: (target) => {
24385
25379
  this.outputFrames++;
24386
- if (this.frameSink === "shm") {
24387
- const slot = this.openSlot;
24388
- this.openSlot = null;
24389
- const sink = this.frameRingSink;
24390
- if (slot === null || sink === null) return;
24391
- const handle = sink.commitFrame(slot, {
24392
- width: this.outWidth,
24393
- height: this.outHeight,
24394
- format: this.frameFormat,
24395
- pts: performance.now(),
24396
- byteLength: this.frameSize
24397
- });
24398
- if (handle === null) return;
24399
- const delivered = {
24400
- handle,
24401
- timestamp: Date.now()
24402
- };
24403
- for (const cb of this.handleCallbacks) cb(delivered);
24404
- return;
24405
- }
24406
25380
  const frame = {
24407
25381
  data: target,
24408
25382
  width: this.outWidth,
@@ -24417,14 +25391,6 @@ var FfmpegDecoderSession = class {
24417
25391
  }
24418
25392
  };
24419
25393
  }
24420
- /** Abandon a shm slot the splitter left open (respawn / teardown mid-frame),
24421
- * so a half-filled slot is never published. */
24422
- abortOpenSlot() {
24423
- const slot = this.openSlot;
24424
- if (slot === null) return;
24425
- this.openSlot = null;
24426
- this.frameRingSink?.abortFrame(slot);
24427
- }
24428
25394
  pushPacket(packet) {
24429
25395
  if (this.destroyed) return;
24430
25396
  if (this.pullUrl !== null) return;
@@ -24478,13 +25444,6 @@ var FfmpegDecoderSession = class {
24478
25444
  this.frameCallbacks.delete(callback);
24479
25445
  };
24480
25446
  }
24481
- /** Subscribe to shared-memory frame handles — fires only in `'shm'` mode. */
24482
- onFrameHandle(callback) {
24483
- this.handleCallbacks.add(callback);
24484
- return () => {
24485
- this.handleCallbacks.delete(callback);
24486
- };
24487
- }
24488
25447
  updateConfig(update) {
24489
25448
  const needsRestart = update.scale !== void 0 && update.scale !== this.config.scale || update.outputFormat !== void 0 && update.outputFormat !== this.config.outputFormat || update.codec !== void 0 && update.codec !== this.config.codec;
24490
25449
  this.config = {
@@ -24499,12 +25458,8 @@ var FfmpegDecoderSession = class {
24499
25458
  this.destroyed = true;
24500
25459
  this.clearPullRedial();
24501
25460
  this.killFfmpeg();
24502
- this.abortOpenSlot();
24503
25461
  this.splitter = null;
24504
- this.frameRingSink?.destroy();
24505
- this.frameRingSink = null;
24506
25462
  this.frameCallbacks.clear();
24507
- this.handleCallbacks.clear();
24508
25463
  }
24509
25464
  getStats() {
24510
25465
  const uptimeSec = Math.max((Date.now() - this.startTime) / 1e3, 1);
@@ -24575,71 +25530,6 @@ function probeGpuScaleFilters(ffmpegPath, logger) {
24575
25530
  });
24576
25531
  }
24577
25532
  //#endregion
24578
- //#region src/shm-orphan-purge.ts
24579
- /**
24580
- * Startup reclamation of orphaned shared-memory segments.
24581
- *
24582
- * A decoder writes frames into named `/dev/shm` segments and unlinks each one
24583
- * on graceful session teardown (`DecoderFrameRingSink.destroy`). When the
24584
- * decoder process dies *ungracefully* — SIGBUS, OOM-kill, or a SIGKILL during
24585
- * redeploy — that teardown never runs and the segment is orphaned: it stays in
24586
- * the tmpfs forever, since nothing else knows its name. Across many
24587
- * crashes/redeploys these accumulate until `/dev/shm` fills, at which point the
24588
- * next `mmap` write faults with an uncatchable SIGBUS and the decoder
24589
- * crash-loops into its circuit breaker (which is exactly the incident this
24590
- * guards against).
24591
- *
24592
- * The reclamation is safe *at process startup*: a freshly-booting decoder owns
24593
- * no live sessions, so every pre-existing segment with its prefix is by
24594
- * definition an orphan from a dead instance. `shm_unlink` only removes the
24595
- * name — any consumer still holding a mapping keeps reading valid memory until
24596
- * it closes (POSIX deferred reclaim + the ring seqlock), so unlinking is safe
24597
- * even if a stale reader is momentarily still attached.
24598
- *
24599
- * POSIX-only: segments surface as files under `/dev/shm` on Linux. On platforms
24600
- * without that directory (Windows, macOS) the scan finds nothing — no-op.
24601
- *
24602
- * NOTE: this lives inside the decoder addon (not `@camstack/shm-ring`) so it
24603
- * ships in the self-contained addon bundle via `camstack deploy`, reusing the
24604
- * already-deployed `unlinkSegment`; no host base-image rebuild required.
24605
- */
24606
- /** Default tmpfs directory where POSIX shared-memory segments appear on Linux. */
24607
- var DEFAULT_SHM_DIR = "/dev/shm";
24608
- /**
24609
- * Unlink every shared-memory segment whose name starts with `prefix`.
24610
- *
24611
- * Intended to run ONCE at decoder startup, before any session is created, to
24612
- * reclaim segments orphaned by a previously-crashed instance. A per-file unlink
24613
- * failure is swallowed so one stuck segment cannot block reclaiming the rest.
24614
- */
24615
- function purgeOrphanSegments(prefix, options = {}) {
24616
- const dir = options.dir ?? DEFAULT_SHM_DIR;
24617
- const unlink = options.unlink ?? unlinkSegment;
24618
- let entries;
24619
- try {
24620
- entries = readdirSync(dir);
24621
- } catch {
24622
- return {
24623
- scanned: 0,
24624
- removed: 0,
24625
- names: []
24626
- };
24627
- }
24628
- const names = [];
24629
- for (const name of entries) {
24630
- if (!name.startsWith(prefix)) continue;
24631
- try {
24632
- unlink(name);
24633
- names.push(name);
24634
- } catch {}
24635
- }
24636
- return {
24637
- scanned: entries.length,
24638
- removed: names.length,
24639
- names
24640
- };
24641
- }
24642
- //#endregion
24643
25533
  //#region src/audio-codec/ffmpeg-audio-process.ts
24644
25534
  /**
24645
25535
  * Minimal ffmpeg subprocess wrapper shared by the audio decode + encode
@@ -25808,12 +26698,8 @@ var DEFAULT_DECODER_FFMPEG_ADDON_CONFIG = { ...DEFAULT_DECODER_HWACCEL_CONFIG };
25808
26698
  var DecoderFfmpegAddon = class extends BaseAddon {
25809
26699
  sessions = /* @__PURE__ */ new Map();
25810
26700
  frameBuffers = /* @__PURE__ */ new Map();
25811
- handleBuffers = /* @__PURE__ */ new Map();
25812
26701
  unsubscribers = /* @__PURE__ */ new Map();
25813
26702
  sessionMeta = /* @__PURE__ */ new Map();
25814
- frameReaders = null;
25815
- getFrameHits = 0;
25816
- getFrameMisses = 0;
25817
26703
  /**
25818
26704
  * The ffmpeg binary every session spawns — resolved once at init via the
25819
26705
  * node-level `ctx.deps.ensureFfmpeg()`. The SAME binary the broker / recorder
@@ -25896,12 +26782,6 @@ var DecoderFfmpegAddon = class extends BaseAddon {
25896
26782
  return registrations;
25897
26783
  }
25898
26784
  this.ctx.logger.info("ffmpeg decoder addon initialized", { meta: { selectedBackend: backend } });
25899
- const purged = purgeOrphanSegments(SEGMENT_NAME_PREFIX);
25900
- if (purged.removed > 0) this.ctx.logger.warn("ffmpeg decoder: reclaimed orphaned shm segments at startup", { meta: {
25901
- removed: purged.removed,
25902
- scanned: purged.scanned
25903
- } });
25904
- this.frameReaders = new FrameRingReaderCache(this.ctx.logger);
25905
26785
  this.probedGpuScaleFilters = await probeGpuScaleFilters(this.ffmpegPath, this.ctx.logger);
25906
26786
  this.ctx.logger.info("decoder-ffmpeg: probed GPU scale filters", { meta: {
25907
26787
  filters: [...this.probedGpuScaleFilters],
@@ -26043,15 +26923,12 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26043
26923
  }
26044
26924
  async createSession(config) {
26045
26925
  const sessionId = randomUUID();
26046
- const { frameSink } = config;
26047
26926
  const nodeId = this.resolveLocalNodeId();
26048
26927
  const hwaccelChain = await this.resolveDecodeHwaccelChain();
26049
26928
  const availableGpuScaleFilters = new Set([...this.probedGpuScaleFilters].filter((f) => !this.unsupportedGpuScaleFilters.has(f)));
26050
26929
  const session = new FfmpegDecoderSession(config, this.ctx.logger, {
26051
26930
  hwaccelChain,
26052
26931
  ffmpegPath: this.ffmpegPath,
26053
- frameSink,
26054
- nodeId,
26055
26932
  availableGpuScaleFilters,
26056
26933
  onGpuScaleUnsupported: (filter) => {
26057
26934
  if (this.unsupportedGpuScaleFilters.has(filter)) return;
@@ -26059,7 +26936,7 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26059
26936
  this.ctx.logger.warn("decoder-ffmpeg: GPU scaler non-functional on this build — future sessions use CPU scale", { meta: { filter } });
26060
26937
  }
26061
26938
  });
26062
- const unsub = frameSink === "shm" ? this.wireShmSink(sessionId, session) : this.wireCallbackSink(sessionId, session);
26939
+ const unsub = this.wireCallbackSink(sessionId, session);
26063
26940
  this.sessions.set(sessionId, session);
26064
26941
  this.unsubscribers.set(sessionId, unsub);
26065
26942
  this.sessionMeta.set(sessionId, {
@@ -26074,7 +26951,6 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26074
26951
  sessionId,
26075
26952
  codec: config.codec,
26076
26953
  hwaccelChain,
26077
- frameSink,
26078
26954
  nodeId
26079
26955
  }
26080
26956
  });
@@ -26101,13 +26977,6 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26101
26977
  ringBuffer.push(capFrame);
26102
26978
  });
26103
26979
  }
26104
- wireShmSink(sessionId, session) {
26105
- const handleRing = new NotifyingRingBuffer(FRAME_BUFFER_CAPACITY);
26106
- this.handleBuffers.set(sessionId, handleRing);
26107
- return session.onFrameHandle((frame) => {
26108
- handleRing.push(frame.handle);
26109
- });
26110
- }
26111
26980
  async destroySession(input) {
26112
26981
  const { sessionId } = input;
26113
26982
  const session = this.sessions.get(sessionId);
@@ -26115,12 +26984,10 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26115
26984
  const unsub = this.unsubscribers.get(sessionId);
26116
26985
  if (unsub) unsub();
26117
26986
  this.frameBuffers.get(sessionId)?.cancel();
26118
- this.handleBuffers.get(sessionId)?.cancel();
26119
26987
  const deviceId = this.sessionMeta.get(sessionId)?.deviceId;
26120
26988
  await session.destroy();
26121
26989
  this.sessions.delete(sessionId);
26122
26990
  this.frameBuffers.delete(sessionId);
26123
- this.handleBuffers.delete(sessionId);
26124
26991
  this.unsubscribers.delete(sessionId);
26125
26992
  this.sessionMeta.delete(sessionId);
26126
26993
  this.ctx.logger.info("ffmpeg: destroyed session", {
@@ -26160,12 +27027,14 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26160
27027
  if (ringBuffer.size === 0 && input.waitMs) await ringBuffer.waitForItem(input.waitMs);
26161
27028
  return ringBuffer.drain(input.maxCount);
26162
27029
  }
26163
- async pullHandles(input) {
26164
- if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
26165
- const handleRing = this.handleBuffers.get(input.sessionId);
26166
- if (!handleRing) return [];
26167
- if (handleRing.size === 0 && input.waitMs) await handleRing.waitForItem(input.waitMs);
26168
- return handleRing.drain(input.maxCount);
27030
+ /**
27031
+ * DORMANT STUB. The shm `FrameHandle` frame ring was removed (the runner ships
27032
+ * inline pixels via `pullFrames` now). The cap method stays defined for wire
27033
+ * compat until the framework train drops it, but no session ever produces
27034
+ * handles always empty.
27035
+ */
27036
+ async pullHandles(_input) {
27037
+ return [];
26169
27038
  }
26170
27039
  async updateConfig(input) {
26171
27040
  const session = this.sessions.get(input.sessionId);
@@ -26177,33 +27046,20 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26177
27046
  if (!session) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
26178
27047
  return session.getStats();
26179
27048
  }
26180
- async getFrame(input) {
26181
- const frame = this.frameReaders?.read(input.handle) ?? null;
26182
- if (!frame) {
26183
- this.getFrameMisses += 1;
26184
- return null;
26185
- }
26186
- this.getFrameHits += 1;
26187
- const arrayBuf = new ArrayBuffer(frame.data.byteLength);
26188
- new Uint8Array(arrayBuf).set(frame.data);
26189
- return {
26190
- data: new Uint8Array(arrayBuf),
26191
- width: frame.width,
26192
- height: frame.height,
26193
- format: frame.format,
26194
- timestamp: frame.timestamp
26195
- };
27049
+ /**
27050
+ * DORMANT STUB. Frames are no longer retained in a shm ring this addon can
27051
+ * read back by handle — the decode-owning node serves pixels over its own cap
27052
+ * surface. Kept defined for wire compat; always resolves `null`.
27053
+ */
27054
+ async getFrame(_input) {
27055
+ return null;
26196
27056
  }
26197
- async getShmStats(input) {
26198
- const stats = this.sessions.get(input.sessionId)?.frameRingSinkOrNull?.getShmStats() ?? null;
26199
- if (!stats) return null;
26200
- return {
26201
- sessionId: input.sessionId,
26202
- ...stats,
26203
- budgetMb: RING_BUDGET_MB,
26204
- getFrameHits: this.getFrameHits,
26205
- getFrameMisses: this.getFrameMisses
26206
- };
27057
+ /**
27058
+ * DORMANT STUB. No session writes a shm frame ring anymore, so there are no
27059
+ * ring stats to report. Kept defined for wire compat; always `null`.
27060
+ */
27061
+ async getShmStats(_input) {
27062
+ return null;
26207
27063
  }
26208
27064
  async onShutdown() {
26209
27065
  this.ctx.logger.info("ffmpeg decoder addon shutdown — destroying all sessions");
@@ -26218,11 +27074,8 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26218
27074
  await Promise.all(destroyPromises);
26219
27075
  this.sessions.clear();
26220
27076
  this.frameBuffers.clear();
26221
- this.handleBuffers.clear();
26222
27077
  this.unsubscribers.clear();
26223
27078
  this.sessionMeta.clear();
26224
- this.frameReaders?.close();
26225
- this.frameReaders = null;
26226
27079
  }
26227
27080
  };
26228
27081
  //#endregion