@camstack/addon-decoder-ffmpeg 1.1.12 → 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 +1067 -256
  2. package/dist/index.mjs +1067 -256
  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-Baang_XW.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([
@@ -11730,6 +11800,140 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
11730
11800
  deviceId: number(),
11731
11801
  status: PtzAutotrackStatusSchema
11732
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
+ });
11733
11937
  object({
11734
11938
  /** Whether the script is currently executing. */
11735
11939
  isRunning: boolean(),
@@ -12137,6 +12341,36 @@ var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
12137
12341
  * with the per-track detail panel and live overlay. */
12138
12342
  trackIds: array(string()).readonly()
12139
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
+ });
12140
12374
  var CameraOccupancySnapshotSchema = object({
12141
12375
  /** Frame timestamp of the inference result that produced this snapshot. */
12142
12376
  ts: number().int(),
@@ -12145,10 +12379,15 @@ var CameraOccupancySnapshotSchema = object({
12145
12379
  frameHeight: number().int().nonnegative(),
12146
12380
  /** Per-zone breakdown — one entry per defined zone (user + onboard). */
12147
12381
  zones: array(ZoneScopeBreakdownSchema).readonly(),
12148
- /** 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). */
12149
12384
  frame: PerScopeBreakdownSchema,
12150
12385
  /** Detections that landed outside every zone. Empty when no zones defined. */
12151
- 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()
12152
12391
  });
12153
12392
  /**
12154
12393
  * Time-series resolution. The history methods return one bucket per
@@ -12751,7 +12990,12 @@ var NotificationRuleConditionsSchema = object({
12751
12990
  clipDescription: object({
12752
12991
  text: string().min(1),
12753
12992
  minSimilarity: number().min(0).max(1)
12754
- }).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()
12755
12999
  });
12756
13000
  var NotificationRuleTemplateSchema = object({
12757
13001
  title: string(),
@@ -13046,11 +13290,19 @@ method(object({
13046
13290
  * login page needs NO change.
13047
13291
  *
13048
13292
  * - `widget` — a Module-Federation widget the login page mounts (via
13049
- * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
13050
- * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
13051
- * addon bundle. The referenced widget also declares `preAuth: true` in
13052
- * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
13053
- * 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.
13054
13306
  *
13055
13307
  * Every contribution carries a `stage`:
13056
13308
  * - `primary` — shown on the first credentials screen (OIDC /
@@ -13064,30 +13316,45 @@ method(object({
13064
13316
  */
13065
13317
  /** When a login method renders in the two-phase login flow. */
13066
13318
  var LoginStageEnum = _enum(["primary", "second-factor"]);
13067
- /** One login-method contribution — redirect button OR pre-auth widget. */
13068
- var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13069
- kind: literal("redirect"),
13070
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13071
- id: string(),
13072
- /** Operator-facing button label. */
13073
- label: string(),
13074
- /** lucide-react icon name. */
13075
- icon: string().optional(),
13076
- /** Addon-owned HTTP route the button navigates to (GET). */
13077
- startUrl: string(),
13078
- stage: LoginStageEnum
13079
- }), object({
13080
- kind: literal("widget"),
13081
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13082
- id: string(),
13083
- /** Owning addon id drives the public bundle URL + the MF namespace. */
13084
- addonId: string(),
13085
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13086
- bundle: string(),
13087
- /** MF remote descriptor `{ remoteName, exposedModule, componentKey }`. */
13088
- remote: WidgetRemoteSchema,
13089
- stage: LoginStageEnum
13090
- })]);
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
+ ]);
13091
13358
  method(_void(), array(LoginMethodContributionSchema).readonly());
13092
13359
  /**
13093
13360
  * Orchestrator-side destination metadata. The orchestrator computes
@@ -15008,14 +15275,14 @@ var TargetKindCapsSchema = object({
15008
15275
  * the union is large and not meant for runtime validation here; the exported
15009
15276
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
15010
15277
  */
15011
- var ConfigSchemaPassthrough = unknown();
15278
+ var ConfigSchemaPassthrough$1 = unknown();
15012
15279
  var TargetKindSchema = object({
15013
15280
  kind: string(),
15014
15281
  label: string(),
15015
15282
  icon: string(),
15016
15283
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
15017
15284
  addonId: string(),
15018
- configSchema: ConfigSchemaPassthrough,
15285
+ configSchema: ConfigSchemaPassthrough$1,
15019
15286
  supportsDiscovery: boolean(),
15020
15287
  caps: TargetKindCapsSchema
15021
15288
  });
@@ -15068,6 +15335,298 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
15068
15335
  enabled: boolean()
15069
15336
  }), _void(), { kind: "mutation" });
15070
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
+ /**
15071
15630
  * Zod schemas for persisted record types.
15072
15631
  *
15073
15632
  * These schemas serve as the single source of truth for types that are
@@ -15269,6 +15828,22 @@ var TrackSnapshotSchema = object({
15269
15828
  /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
15270
15829
  mediaKey: string()
15271
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
+ });
15272
15847
  var TrackSchema = object({
15273
15848
  trackId: string(),
15274
15849
  deviceId: number(),
@@ -15283,6 +15858,10 @@ var TrackSchema = object({
15283
15858
  snapshots: array(TrackSnapshotSchema).readonly(),
15284
15859
  /** Deduplicated zones the track has entered at least once. */
15285
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(),
15286
15865
  /** Cumulative normalized distance travelled (0..1 units = full frame width). */
15287
15866
  totalDistance: number(),
15288
15867
  state: TrackStateSchema,
@@ -15296,7 +15875,11 @@ var TrackSchema = object({
15296
15875
  bestEventId: string().optional(),
15297
15876
  /** Tag of the importance sub-signal that dominated the score
15298
15877
  * (identity|dwell|proximity|class|confidence|travel|zone). */
15299
- 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()
15300
15883
  });
15301
15884
  var BaseEventFields = {
15302
15885
  id: string(),
@@ -15444,7 +16027,7 @@ var KeyEventSchema = object({
15444
16027
  /** Track lifetime in ms (lastSeen - firstSeen). */
15445
16028
  windowMs: number().optional()
15446
16029
  });
15447
- var TrackedDetectionSchema = object({
16030
+ object({
15448
16031
  trackId: string(),
15449
16032
  className: string(),
15450
16033
  confidence: number(),
@@ -15452,6 +16035,23 @@ var TrackedDetectionSchema = object({
15452
16035
  zones: array(string()).readonly(),
15453
16036
  state: TrackStateSchema
15454
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
+ });
15455
16055
  var ScoredObjectEventSchema = ObjectEventSchema.extend({ score: number() });
15456
16056
  var SearchObjectEventsInput = object({
15457
16057
  text: string(),
@@ -15462,6 +16062,20 @@ var SearchObjectEventsInput = object({
15462
16062
  limit: number().default(50),
15463
16063
  minScore: number().min(0).max(1).default(.2)
15464
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
+ });
15465
16079
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
15466
16080
  deviceId: number(),
15467
16081
  trackId: string()
@@ -15493,6 +16107,24 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15493
16107
  }), {
15494
16108
  kind: "mutation",
15495
16109
  auth: "admin"
16110
+ }), method(object({
16111
+ deviceId: number(),
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"
15496
16128
  }), method(object({
15497
16129
  eventId: string(),
15498
16130
  kind: MediaFileKindEnum.optional()
@@ -15501,7 +16133,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15501
16133
  timestamp: number(),
15502
16134
  frameWidth: number(),
15503
16135
  frameHeight: number(),
15504
- detections: array(TrackedDetectionSchema).readonly()
16136
+ detections: array(OverlayDetectionSchema).readonly()
15505
16137
  }), object({
15506
16138
  deviceId: number(),
15507
16139
  trackId: string(),
@@ -18270,6 +18902,24 @@ DeviceType.Camera, method(object({
18270
18902
  deviceId: number(),
18271
18903
  status: OsdStatusSchema
18272
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
+ });
18273
18923
  var PlateInfoSchema = object({
18274
18924
  plateId: string(),
18275
18925
  deviceId: number().int(),
@@ -18281,6 +18931,16 @@ var PlateInfoSchema = object({
18281
18931
  score: number(),
18282
18932
  /** True when the text was manually corrected (exempt from retention). */
18283
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(),
18284
18944
  base64: string().optional()
18285
18945
  });
18286
18946
  var MediaFileLiteSchema = object({
@@ -18320,6 +18980,48 @@ method(object({
18320
18980
  }), method(object({ plateId: string() }), _void(), {
18321
18981
  kind: "mutation",
18322
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"
18323
19025
  });
18324
19026
  var ModelFormatSchema = _enum(MODEL_FORMATS);
18325
19027
  var HwAccelBackendInputSchema = _enum([
@@ -18398,6 +19100,10 @@ var GpuInfoSchema = object({
18398
19100
  memoryMB: number().optional()
18399
19101
  });
18400
19102
  var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
19103
+ var CoralInfoSchema = object({
19104
+ type: literal("coral-edgetpu"),
19105
+ bus: string().optional()
19106
+ });
18401
19107
  var HardwareInfoSchema = object({
18402
19108
  platform: HardwarePlatformSchema,
18403
19109
  arch: HardwareArchSchema,
@@ -18406,7 +19112,8 @@ var HardwareInfoSchema = object({
18406
19112
  totalRAM_MB: number(),
18407
19113
  availableRAM_MB: number(),
18408
19114
  gpu: GpuInfoSchema.nullable(),
18409
- npu: NpuInfoSchema.nullable()
19115
+ npu: NpuInfoSchema.nullable(),
19116
+ coral: CoralInfoSchema.nullable().optional()
18410
19117
  });
18411
19118
  var PlatformScoreSchema = object({
18412
19119
  runtime: _enum(["node", "python"]),
@@ -20865,6 +21572,168 @@ Object.freeze({
20865
21572
  addonId: null,
20866
21573
  access: "create"
20867
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
+ },
20868
21737
  "localNetwork.getAllowedAddresses": {
20869
21738
  capName: "local-network",
20870
21739
  capScope: "system",
@@ -21495,6 +22364,12 @@ Object.freeze({
21495
22364
  addonId: null,
21496
22365
  access: "delete"
21497
22366
  },
22367
+ "pipelineAnalytics.deleteTracks": {
22368
+ capName: "pipeline-analytics",
22369
+ capScope: "device",
22370
+ addonId: null,
22371
+ access: "delete"
22372
+ },
21498
22373
  "pipelineAnalytics.getActiveTracks": {
21499
22374
  capName: "pipeline-analytics",
21500
22375
  capScope: "device",
@@ -21561,12 +22436,24 @@ Object.freeze({
21561
22436
  addonId: null,
21562
22437
  access: "create"
21563
22438
  },
22439
+ "pipelineAnalytics.pruneTracksBefore": {
22440
+ capName: "pipeline-analytics",
22441
+ capScope: "device",
22442
+ addonId: null,
22443
+ access: "create"
22444
+ },
21564
22445
  "pipelineAnalytics.searchObjectEvents": {
21565
22446
  capName: "pipeline-analytics",
21566
22447
  capScope: "device",
21567
22448
  addonId: null,
21568
22449
  access: "view"
21569
22450
  },
22451
+ "pipelineAnalytics.wipeAllAnalytics": {
22452
+ capName: "pipeline-analytics",
22453
+ capScope: "device",
22454
+ addonId: null,
22455
+ access: "delete"
22456
+ },
21570
22457
  "pipelineExecutor.cacheFrameInPool": {
21571
22458
  capName: "pipeline-executor",
21572
22459
  capScope: "system",
@@ -22071,18 +22958,42 @@ Object.freeze({
22071
22958
  addonId: null,
22072
22959
  access: "create"
22073
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
+ },
22074
22973
  "plateGallery.correctPlateText": {
22075
22974
  capName: "plate-gallery",
22076
22975
  capScope: "system",
22077
22976
  addonId: null,
22078
22977
  access: "create"
22079
22978
  },
22979
+ "plateGallery.createVehicle": {
22980
+ capName: "plate-gallery",
22981
+ capScope: "system",
22982
+ addonId: null,
22983
+ access: "create"
22984
+ },
22080
22985
  "plateGallery.deletePlate": {
22081
22986
  capName: "plate-gallery",
22082
22987
  capScope: "system",
22083
22988
  addonId: null,
22084
22989
  access: "delete"
22085
22990
  },
22991
+ "plateGallery.deleteVehicle": {
22992
+ capName: "plate-gallery",
22993
+ capScope: "system",
22994
+ addonId: null,
22995
+ access: "delete"
22996
+ },
22086
22997
  "plateGallery.getPlateByTrack": {
22087
22998
  capName: "plate-gallery",
22088
22999
  capScope: "system",
@@ -22101,6 +23012,30 @@ Object.freeze({
22101
23012
  addonId: null,
22102
23013
  access: "view"
22103
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
+ },
22104
23039
  "plateGallery.searchPlates": {
22105
23040
  capName: "plate-gallery",
22106
23041
  capScope: "system",
@@ -22113,6 +23048,18 @@ Object.freeze({
22113
23048
  addonId: null,
22114
23049
  access: "view"
22115
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
+ },
22116
23063
  "platformProbe.getCapabilities": {
22117
23064
  capName: "platform-probe",
22118
23065
  capScope: "system",
@@ -22305,6 +23252,48 @@ Object.freeze({
22305
23252
  addonId: null,
22306
23253
  access: "create"
22307
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
+ },
22308
23297
  "scriptRunner.run": {
22309
23298
  capName: "script-runner",
22310
23299
  capScope: "device",
@@ -23389,6 +24378,7 @@ Object.freeze({
23389
24378
  Object.freeze({
23390
24379
  "broker": "broker",
23391
24380
  "device-export": "device-export",
24381
+ "llm": "ai",
23392
24382
  "mesh-network": "mesh",
23393
24383
  "mqtt-broker": "broker",
23394
24384
  "network-access": "ingress",
@@ -23575,9 +24565,9 @@ async function resolveDecoderBackend(api, nodeId, logger) {
23575
24565
  //#region src/shared/notifying-ring-buffer.ts
23576
24566
  /**
23577
24567
  * A {@link RingBuffer} that can notify a single blocked consumer the moment an
23578
- * item commits — the server side of the `pullFrames`/`pullHandles` long-poll.
24568
+ * item commits — the server side of the `pullFrames` long-poll.
23579
24569
  *
23580
- * The decoder's `onFrame`/`onFrameHandle` callback drives `push()`; a consumer
24570
+ * The decoder's `onFrame` callback drives `push()`; a consumer
23581
24571
  * that finds the ring momentarily empty calls {@link waitForItem} and is woken
23582
24572
  * on the very next `push()` (a real commit signal) rather than spinning at
23583
24573
  * ~1kHz on `setTimeout(…,1)`. If nothing commits it resolves after `waitMs`,
@@ -24021,8 +25011,7 @@ var FrameStreamSplitter = class {
24021
25011
  //#region src/ffmpeg-decoder-session.ts
24022
25012
  /**
24023
25013
  * FfmpegDecoderSession — an `IDecoderSession` that decodes video in an **ffmpeg
24024
- * subprocess** and feeds the shared-memory frame ring (Phase 5 / D9), the same
24025
- * ring `decoder-nodeav` writes into.
25014
+ * subprocess** and delivers each decoded frame via the `onFrame` callback.
24026
25015
  *
24027
25016
  * Why a subprocess: on the Intel hub, node-av's in-process VA-API decode path
24028
25017
  * SIGBUSes (uncatchable) on 8MP h264 and kills the whole decoder runner. ffmpeg
@@ -24035,15 +25024,13 @@ var FrameStreamSplitter = class {
24035
25024
  * ffmpeg → `-vf <scale_vaapi=…,hwdownload,… | hwdownload,…,scale=…>` (GPU or
24036
25025
  * CPU scale, per {@link buildFfmpegDecodeArgs})
24037
25026
  * raw video → ffmpeg stdout (pipe:1) → a {@link FrameStreamSplitter} that fills
24038
- * each frame's bytes DIRECTLY into its shm ring slot (zero-copy
24039
- * `beginFrame`/`commitFrame`) `FrameHandle` (shm mode), or into a
24040
- * per-frame buffer → `DecodedFrame` via `onFrame` (callback mode).
24041
- * No `Buffer.concat` accumulator, one copy per byte, none for dropped
24042
- * 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.
24043
25030
  *
24044
- * Output geometry: the broker does not pass source width/height for the shm
24045
- * path, so ffmpeg picks the height (`scale=min(iw\,maxW):-2`) and the session
24046
- * 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
24047
25034
  * ({@link parseFfmpegOutputDims}). Until dims are known, stdout is buffered.
24048
25035
  */
24049
25036
  var noopLogger = {
@@ -24083,8 +25070,6 @@ var MAX_GOP_BUFFER_BYTES = 16 * 1024 * 1024;
24083
25070
  var FfmpegDecoderSession = class {
24084
25071
  config;
24085
25072
  logger;
24086
- frameSink;
24087
- nodeId;
24088
25073
  renderDevice;
24089
25074
  ffmpegPath;
24090
25075
  /**
@@ -24134,9 +25119,6 @@ var FfmpegDecoderSession = class {
24134
25119
  /** Stdout chunks received BEFORE geometry is known — replayed into the
24135
25120
  * splitter once `frameSize` is learned. A short-lived list (no concat). */
24136
25121
  pendingStdout = [];
24137
- /** The shm ring slot the splitter is currently filling, if any — aborted on
24138
- * respawn / teardown so a half-filled slot never publishes. */
24139
- openSlot = null;
24140
25122
  stderrAccum = "";
24141
25123
  destroyed = false;
24142
25124
  pixel;
@@ -24146,9 +25128,7 @@ var FfmpegDecoderSession = class {
24146
25128
  outWidth = 0;
24147
25129
  outHeight = 0;
24148
25130
  frameSize = 0;
24149
- frameRingSink = null;
24150
25131
  frameCallbacks = /* @__PURE__ */ new Set();
24151
- handleCallbacks = /* @__PURE__ */ new Set();
24152
25132
  frameDropper;
24153
25133
  inputPackets = 0;
24154
25134
  /**
@@ -24172,8 +25152,6 @@ var FfmpegDecoderSession = class {
24172
25152
  if (typeof config.deviceId === "number") sessionTags["deviceId"] = config.deviceId;
24173
25153
  if (typeof config.tag === "string" && config.tag.length > 0) sessionTags["tag"] = config.tag;
24174
25154
  this.logger = Object.keys(sessionTags).length > 0 ? logger.withTags(sessionTags) : logger;
24175
- this.frameSink = options?.frameSink ?? "callback";
24176
- this.nodeId = options?.nodeId ?? "local";
24177
25155
  this.renderDevice = options?.renderDevice;
24178
25156
  this.ffmpegPath = options?.ffmpegPath ?? "ffmpeg";
24179
25157
  const seededChain = options?.hwaccelChain ?? (options?.hwaccel != null && !isSoftwareHwAccel(options.hwaccel) ? [options.hwaccel] : []);
@@ -24186,10 +25164,6 @@ var FfmpegDecoderSession = class {
24186
25164
  this.frameFormat = this.pixel === "gray" ? "gray" : "rgb";
24187
25165
  this.frameDropper = new FrameDropper(config.maxFps);
24188
25166
  }
24189
- /** Exposed so the owning addon can surface ring stats via `getShmStats`. */
24190
- get frameRingSinkOrNull() {
24191
- return this.frameRingSink;
24192
- }
24193
25167
  get isPullMode() {
24194
25168
  return this.pullUrl !== null;
24195
25169
  }
@@ -24211,26 +25185,11 @@ var FfmpegDecoderSession = class {
24211
25185
  this.logger.info("ffmpeg decoder: pull-mode openStream — dialing input URL", { meta: { url } });
24212
25186
  this.spawnFfmpeg();
24213
25187
  }
24214
- ensureFrameRingSink() {
24215
- if (this.frameRingSink === null) {
24216
- const seedParts = [];
24217
- if (typeof this.config.deviceId === "number") seedParts.push(String(this.config.deviceId));
24218
- if (typeof this.config.tag === "string" && this.config.tag.length > 0) seedParts.push(this.config.tag);
24219
- const seed = seedParts.length > 0 ? seedParts.join(":") : "anon";
24220
- this.frameRingSink = new DecoderFrameRingSink({
24221
- seed,
24222
- logger: this.logger,
24223
- nodeId: this.nodeId
24224
- });
24225
- }
24226
- return this.frameRingSink;
24227
- }
24228
25188
  spawnFfmpeg() {
24229
25189
  if (this.destroyed) return;
24230
25190
  this.spawnAttempted = true;
24231
25191
  this.clearPullRedial();
24232
25192
  this.killFfmpeg();
24233
- this.abortOpenSlot();
24234
25193
  this.splitter = null;
24235
25194
  this.pendingStdout = [];
24236
25195
  this.stderrAccum = "";
@@ -24276,8 +25235,7 @@ var FfmpegDecoderSession = class {
24276
25235
  hwaccel: this.hwaccel,
24277
25236
  gpuScale: this.activeGpuScaleFilter ?? "cpu",
24278
25237
  codec: this.config.codec,
24279
- format: this.frameFormat,
24280
- sink: this.frameSink
25238
+ format: this.frameFormat
24281
25239
  } });
24282
25240
  }
24283
25241
  killFfmpeg() {
@@ -24415,36 +25373,10 @@ var FfmpegDecoderSession = class {
24415
25373
  return {
24416
25374
  startFrame: () => {
24417
25375
  if (!this.frameDropper.shouldKeep()) return null;
24418
- if (this.frameSink === "shm") {
24419
- const reserved = this.ensureFrameRingSink().beginFrame(this.outWidth, this.outHeight, this.frameFormat);
24420
- if (reserved === null) return null;
24421
- this.openSlot = reserved.slot;
24422
- return reserved.buffer;
24423
- }
24424
25376
  return Buffer.allocUnsafe(this.frameSize);
24425
25377
  },
24426
25378
  finishFrame: (target) => {
24427
25379
  this.outputFrames++;
24428
- if (this.frameSink === "shm") {
24429
- const slot = this.openSlot;
24430
- this.openSlot = null;
24431
- const sink = this.frameRingSink;
24432
- if (slot === null || sink === null) return;
24433
- const handle = sink.commitFrame(slot, {
24434
- width: this.outWidth,
24435
- height: this.outHeight,
24436
- format: this.frameFormat,
24437
- pts: performance.now(),
24438
- byteLength: this.frameSize
24439
- });
24440
- if (handle === null) return;
24441
- const delivered = {
24442
- handle,
24443
- timestamp: Date.now()
24444
- };
24445
- for (const cb of this.handleCallbacks) cb(delivered);
24446
- return;
24447
- }
24448
25380
  const frame = {
24449
25381
  data: target,
24450
25382
  width: this.outWidth,
@@ -24459,14 +25391,6 @@ var FfmpegDecoderSession = class {
24459
25391
  }
24460
25392
  };
24461
25393
  }
24462
- /** Abandon a shm slot the splitter left open (respawn / teardown mid-frame),
24463
- * so a half-filled slot is never published. */
24464
- abortOpenSlot() {
24465
- const slot = this.openSlot;
24466
- if (slot === null) return;
24467
- this.openSlot = null;
24468
- this.frameRingSink?.abortFrame(slot);
24469
- }
24470
25394
  pushPacket(packet) {
24471
25395
  if (this.destroyed) return;
24472
25396
  if (this.pullUrl !== null) return;
@@ -24520,13 +25444,6 @@ var FfmpegDecoderSession = class {
24520
25444
  this.frameCallbacks.delete(callback);
24521
25445
  };
24522
25446
  }
24523
- /** Subscribe to shared-memory frame handles — fires only in `'shm'` mode. */
24524
- onFrameHandle(callback) {
24525
- this.handleCallbacks.add(callback);
24526
- return () => {
24527
- this.handleCallbacks.delete(callback);
24528
- };
24529
- }
24530
25447
  updateConfig(update) {
24531
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;
24532
25449
  this.config = {
@@ -24541,12 +25458,8 @@ var FfmpegDecoderSession = class {
24541
25458
  this.destroyed = true;
24542
25459
  this.clearPullRedial();
24543
25460
  this.killFfmpeg();
24544
- this.abortOpenSlot();
24545
25461
  this.splitter = null;
24546
- this.frameRingSink?.destroy();
24547
- this.frameRingSink = null;
24548
25462
  this.frameCallbacks.clear();
24549
- this.handleCallbacks.clear();
24550
25463
  }
24551
25464
  getStats() {
24552
25465
  const uptimeSec = Math.max((Date.now() - this.startTime) / 1e3, 1);
@@ -24617,71 +25530,6 @@ function probeGpuScaleFilters(ffmpegPath, logger) {
24617
25530
  });
24618
25531
  }
24619
25532
  //#endregion
24620
- //#region src/shm-orphan-purge.ts
24621
- /**
24622
- * Startup reclamation of orphaned shared-memory segments.
24623
- *
24624
- * A decoder writes frames into named `/dev/shm` segments and unlinks each one
24625
- * on graceful session teardown (`DecoderFrameRingSink.destroy`). When the
24626
- * decoder process dies *ungracefully* — SIGBUS, OOM-kill, or a SIGKILL during
24627
- * redeploy — that teardown never runs and the segment is orphaned: it stays in
24628
- * the tmpfs forever, since nothing else knows its name. Across many
24629
- * crashes/redeploys these accumulate until `/dev/shm` fills, at which point the
24630
- * next `mmap` write faults with an uncatchable SIGBUS and the decoder
24631
- * crash-loops into its circuit breaker (which is exactly the incident this
24632
- * guards against).
24633
- *
24634
- * The reclamation is safe *at process startup*: a freshly-booting decoder owns
24635
- * no live sessions, so every pre-existing segment with its prefix is by
24636
- * definition an orphan from a dead instance. `shm_unlink` only removes the
24637
- * name — any consumer still holding a mapping keeps reading valid memory until
24638
- * it closes (POSIX deferred reclaim + the ring seqlock), so unlinking is safe
24639
- * even if a stale reader is momentarily still attached.
24640
- *
24641
- * POSIX-only: segments surface as files under `/dev/shm` on Linux. On platforms
24642
- * without that directory (Windows, macOS) the scan finds nothing — no-op.
24643
- *
24644
- * NOTE: this lives inside the decoder addon (not `@camstack/shm-ring`) so it
24645
- * ships in the self-contained addon bundle via `camstack deploy`, reusing the
24646
- * already-deployed `unlinkSegment`; no host base-image rebuild required.
24647
- */
24648
- /** Default tmpfs directory where POSIX shared-memory segments appear on Linux. */
24649
- var DEFAULT_SHM_DIR = "/dev/shm";
24650
- /**
24651
- * Unlink every shared-memory segment whose name starts with `prefix`.
24652
- *
24653
- * Intended to run ONCE at decoder startup, before any session is created, to
24654
- * reclaim segments orphaned by a previously-crashed instance. A per-file unlink
24655
- * failure is swallowed so one stuck segment cannot block reclaiming the rest.
24656
- */
24657
- function purgeOrphanSegments(prefix, options = {}) {
24658
- const dir = options.dir ?? DEFAULT_SHM_DIR;
24659
- const unlink = options.unlink ?? unlinkSegment;
24660
- let entries;
24661
- try {
24662
- entries = readdirSync(dir);
24663
- } catch {
24664
- return {
24665
- scanned: 0,
24666
- removed: 0,
24667
- names: []
24668
- };
24669
- }
24670
- const names = [];
24671
- for (const name of entries) {
24672
- if (!name.startsWith(prefix)) continue;
24673
- try {
24674
- unlink(name);
24675
- names.push(name);
24676
- } catch {}
24677
- }
24678
- return {
24679
- scanned: entries.length,
24680
- removed: names.length,
24681
- names
24682
- };
24683
- }
24684
- //#endregion
24685
25533
  //#region src/audio-codec/ffmpeg-audio-process.ts
24686
25534
  /**
24687
25535
  * Minimal ffmpeg subprocess wrapper shared by the audio decode + encode
@@ -25850,12 +26698,8 @@ var DEFAULT_DECODER_FFMPEG_ADDON_CONFIG = { ...DEFAULT_DECODER_HWACCEL_CONFIG };
25850
26698
  var DecoderFfmpegAddon = class extends BaseAddon {
25851
26699
  sessions = /* @__PURE__ */ new Map();
25852
26700
  frameBuffers = /* @__PURE__ */ new Map();
25853
- handleBuffers = /* @__PURE__ */ new Map();
25854
26701
  unsubscribers = /* @__PURE__ */ new Map();
25855
26702
  sessionMeta = /* @__PURE__ */ new Map();
25856
- frameReaders = null;
25857
- getFrameHits = 0;
25858
- getFrameMisses = 0;
25859
26703
  /**
25860
26704
  * The ffmpeg binary every session spawns — resolved once at init via the
25861
26705
  * node-level `ctx.deps.ensureFfmpeg()`. The SAME binary the broker / recorder
@@ -25938,12 +26782,6 @@ var DecoderFfmpegAddon = class extends BaseAddon {
25938
26782
  return registrations;
25939
26783
  }
25940
26784
  this.ctx.logger.info("ffmpeg decoder addon initialized", { meta: { selectedBackend: backend } });
25941
- const purged = purgeOrphanSegments(SEGMENT_NAME_PREFIX);
25942
- if (purged.removed > 0) this.ctx.logger.warn("ffmpeg decoder: reclaimed orphaned shm segments at startup", { meta: {
25943
- removed: purged.removed,
25944
- scanned: purged.scanned
25945
- } });
25946
- this.frameReaders = new FrameRingReaderCache(this.ctx.logger);
25947
26785
  this.probedGpuScaleFilters = await probeGpuScaleFilters(this.ffmpegPath, this.ctx.logger);
25948
26786
  this.ctx.logger.info("decoder-ffmpeg: probed GPU scale filters", { meta: {
25949
26787
  filters: [...this.probedGpuScaleFilters],
@@ -26085,15 +26923,12 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26085
26923
  }
26086
26924
  async createSession(config) {
26087
26925
  const sessionId = randomUUID();
26088
- const { frameSink } = config;
26089
26926
  const nodeId = this.resolveLocalNodeId();
26090
26927
  const hwaccelChain = await this.resolveDecodeHwaccelChain();
26091
26928
  const availableGpuScaleFilters = new Set([...this.probedGpuScaleFilters].filter((f) => !this.unsupportedGpuScaleFilters.has(f)));
26092
26929
  const session = new FfmpegDecoderSession(config, this.ctx.logger, {
26093
26930
  hwaccelChain,
26094
26931
  ffmpegPath: this.ffmpegPath,
26095
- frameSink,
26096
- nodeId,
26097
26932
  availableGpuScaleFilters,
26098
26933
  onGpuScaleUnsupported: (filter) => {
26099
26934
  if (this.unsupportedGpuScaleFilters.has(filter)) return;
@@ -26101,7 +26936,7 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26101
26936
  this.ctx.logger.warn("decoder-ffmpeg: GPU scaler non-functional on this build — future sessions use CPU scale", { meta: { filter } });
26102
26937
  }
26103
26938
  });
26104
- const unsub = frameSink === "shm" ? this.wireShmSink(sessionId, session) : this.wireCallbackSink(sessionId, session);
26939
+ const unsub = this.wireCallbackSink(sessionId, session);
26105
26940
  this.sessions.set(sessionId, session);
26106
26941
  this.unsubscribers.set(sessionId, unsub);
26107
26942
  this.sessionMeta.set(sessionId, {
@@ -26116,7 +26951,6 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26116
26951
  sessionId,
26117
26952
  codec: config.codec,
26118
26953
  hwaccelChain,
26119
- frameSink,
26120
26954
  nodeId
26121
26955
  }
26122
26956
  });
@@ -26143,13 +26977,6 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26143
26977
  ringBuffer.push(capFrame);
26144
26978
  });
26145
26979
  }
26146
- wireShmSink(sessionId, session) {
26147
- const handleRing = new NotifyingRingBuffer(FRAME_BUFFER_CAPACITY);
26148
- this.handleBuffers.set(sessionId, handleRing);
26149
- return session.onFrameHandle((frame) => {
26150
- handleRing.push(frame.handle);
26151
- });
26152
- }
26153
26980
  async destroySession(input) {
26154
26981
  const { sessionId } = input;
26155
26982
  const session = this.sessions.get(sessionId);
@@ -26157,12 +26984,10 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26157
26984
  const unsub = this.unsubscribers.get(sessionId);
26158
26985
  if (unsub) unsub();
26159
26986
  this.frameBuffers.get(sessionId)?.cancel();
26160
- this.handleBuffers.get(sessionId)?.cancel();
26161
26987
  const deviceId = this.sessionMeta.get(sessionId)?.deviceId;
26162
26988
  await session.destroy();
26163
26989
  this.sessions.delete(sessionId);
26164
26990
  this.frameBuffers.delete(sessionId);
26165
- this.handleBuffers.delete(sessionId);
26166
26991
  this.unsubscribers.delete(sessionId);
26167
26992
  this.sessionMeta.delete(sessionId);
26168
26993
  this.ctx.logger.info("ffmpeg: destroyed session", {
@@ -26202,12 +27027,14 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26202
27027
  if (ringBuffer.size === 0 && input.waitMs) await ringBuffer.waitForItem(input.waitMs);
26203
27028
  return ringBuffer.drain(input.maxCount);
26204
27029
  }
26205
- async pullHandles(input) {
26206
- if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
26207
- const handleRing = this.handleBuffers.get(input.sessionId);
26208
- if (!handleRing) return [];
26209
- if (handleRing.size === 0 && input.waitMs) await handleRing.waitForItem(input.waitMs);
26210
- 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 [];
26211
27038
  }
26212
27039
  async updateConfig(input) {
26213
27040
  const session = this.sessions.get(input.sessionId);
@@ -26219,33 +27046,20 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26219
27046
  if (!session) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
26220
27047
  return session.getStats();
26221
27048
  }
26222
- async getFrame(input) {
26223
- const frame = this.frameReaders?.read(input.handle) ?? null;
26224
- if (!frame) {
26225
- this.getFrameMisses += 1;
26226
- return null;
26227
- }
26228
- this.getFrameHits += 1;
26229
- const arrayBuf = new ArrayBuffer(frame.data.byteLength);
26230
- new Uint8Array(arrayBuf).set(frame.data);
26231
- return {
26232
- data: new Uint8Array(arrayBuf),
26233
- width: frame.width,
26234
- height: frame.height,
26235
- format: frame.format,
26236
- timestamp: frame.timestamp
26237
- };
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;
26238
27056
  }
26239
- async getShmStats(input) {
26240
- const stats = this.sessions.get(input.sessionId)?.frameRingSinkOrNull?.getShmStats() ?? null;
26241
- if (!stats) return null;
26242
- return {
26243
- sessionId: input.sessionId,
26244
- ...stats,
26245
- budgetMb: RING_BUDGET_MB,
26246
- getFrameHits: this.getFrameHits,
26247
- getFrameMisses: this.getFrameMisses
26248
- };
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;
26249
27063
  }
26250
27064
  async onShutdown() {
26251
27065
  this.ctx.logger.info("ffmpeg decoder addon shutdown — destroying all sessions");
@@ -26260,11 +27074,8 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26260
27074
  await Promise.all(destroyPromises);
26261
27075
  this.sessions.clear();
26262
27076
  this.frameBuffers.clear();
26263
- this.handleBuffers.clear();
26264
27077
  this.unsubscribers.clear();
26265
27078
  this.sessionMeta.clear();
26266
- this.frameReaders?.close();
26267
- this.frameReaders = null;
26268
27079
  }
26269
27080
  };
26270
27081
  //#endregion