@camstack/addon-pipeline 1.2.107 → 1.2.115

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 (35) hide show
  1. package/dist/{addon-utils-Vv2dCqA2.js → addon-utils-C0ht2KPO.js} +51 -15
  2. package/dist/{addon-utils-A2S9D7pu.mjs → addon-utils-CgBCF-mE.mjs} +50 -14
  3. package/dist/audio-analyzer/index.js +3 -3
  4. package/dist/audio-analyzer/index.mjs +3 -3
  5. package/dist/detection-pipeline/index.js +170 -31
  6. package/dist/detection-pipeline/index.mjs +169 -30
  7. package/dist/{dist-L-1LMGQs.js → dist-BqIbYW1A.js} +130 -17
  8. package/dist/{dist-CkXAzTZ2.mjs → dist-DGQkkWc6.mjs} +130 -17
  9. package/dist/{event-loop-stall-monitor-fHkfgmlw.mjs → event-loop-stall-monitor-D5jK5v4Z.mjs} +223 -2
  10. package/dist/{event-loop-stall-monitor-DwAho7HD.js → event-loop-stall-monitor-ylMGpz48.js} +234 -1
  11. package/dist/{lazy-sharp-DfbyQxET.js → lazy-sharp-cOsy7l_P.js} +1 -1
  12. package/dist/motion-wasm/index.js +2 -2
  13. package/dist/motion-wasm/index.mjs +1 -1
  14. package/dist/pipeline-runner/index.js +915 -107
  15. package/dist/pipeline-runner/index.mjs +914 -106
  16. package/dist/{process-memory-CgrbLo3l.js → process-memory-45juuHpN.js} +1 -1
  17. package/dist/{process-memory-DlSOzuHP.mjs → process-memory-BtT7WfbJ.mjs} +1 -1
  18. package/dist/recorder/index.js +187 -47
  19. package/dist/recorder/index.mjs +187 -47
  20. package/dist/session-decode/decode-worker-child.js +30 -10
  21. package/dist/session-decode/decode-worker-child.mjs +29 -9
  22. package/dist/stream-broker/_stub.js +2 -2
  23. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BeL4_jgH.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_SFFNGT.mjs} +3 -3
  24. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B1LDFHY9.mjs +26 -0
  25. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CmHtm5CX.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAMyzCWw.mjs} +1 -1
  26. package/dist/stream-broker/{hostInit-BK9nzKAG.mjs → hostInit-CG28NvlV.mjs} +3 -3
  27. package/dist/stream-broker/index.js +279 -198
  28. package/dist/stream-broker/index.mjs +279 -198
  29. package/dist/stream-broker/remoteEntry.js +1 -1
  30. package/dist/{worker-protocol-GGnzfWu3.js → worker-protocol-CIrqGbpG.js} +6 -3
  31. package/dist/{worker-protocol-bEipQT9l.mjs → worker-protocol-DyocTIx_.mjs} +6 -3
  32. package/package.json +1 -1
  33. package/python/inference_pool.py +4 -1
  34. package/python/test_inference_pool_preprocess.py +87 -16
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DjndFc5V.mjs +0 -26
@@ -9058,6 +9058,15 @@ var LabelDefinitionSchema = object({
9058
9058
  description: string().optional(),
9059
9059
  icon: string().optional()
9060
9060
  });
9061
+ var ClassMapDefinitionSchema = object({
9062
+ mapping: record(string(), _enum([
9063
+ "person",
9064
+ "vehicle",
9065
+ "animal",
9066
+ "package"
9067
+ ])),
9068
+ preserveOriginal: boolean()
9069
+ });
9061
9070
  var MODEL_FORMATS = [
9062
9071
  "onnx",
9063
9072
  "coreml",
@@ -9236,7 +9245,13 @@ var ModelCatalogEntrySchema = object({
9236
9245
  * `id` stays the source of truth for resolution/download/persistence; grouping
9237
9246
  * is a presentation overlay resolved back to an `id`.
9238
9247
  */
9239
- group: ModelVariantGroupSchema.optional()
9248
+ group: ModelVariantGroupSchema.optional(),
9249
+ /**
9250
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
9251
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
9252
+ * labels already ARE the CamStack macros (Scrypted identity map).
9253
+ */
9254
+ classMap: ClassMapDefinitionSchema.optional()
9240
9255
  });
9241
9256
  var ConvertTargetSchema = discriminatedUnion("format", [object({
9242
9257
  format: literal("openvino"),
@@ -9265,7 +9280,8 @@ var ModelConvertMetadataSchema = object({
9265
9280
  "ocr",
9266
9281
  "segmentation"
9267
9282
  ]),
9268
- faceAlignment: boolean().optional()
9283
+ faceAlignment: boolean().optional(),
9284
+ classMap: ClassMapDefinitionSchema.optional()
9269
9285
  });
9270
9286
  var ConvertResultSchema = object({
9271
9287
  entry: ModelCatalogEntrySchema,
@@ -19049,6 +19065,33 @@ var NativeCropRefSchema = object({
19049
19065
  h: number()
19050
19066
  })
19051
19067
  });
19068
+ object({
19069
+ crop: object({
19070
+ left: number(),
19071
+ top: number(),
19072
+ width: number().positive(),
19073
+ height: number().positive()
19074
+ }).optional(),
19075
+ content: object({
19076
+ width: number().int().positive(),
19077
+ height: number().int().positive()
19078
+ }),
19079
+ fit: _enum(["stretch", "contain"]),
19080
+ format: _enum([
19081
+ "rgb",
19082
+ "gray",
19083
+ "jpeg"
19084
+ ])
19085
+ });
19086
+ var FrameRefSchema = object({
19087
+ registryId: string().min(1),
19088
+ id: string().min(1),
19089
+ width: number().int().positive(),
19090
+ height: number().int().positive(),
19091
+ format: _enum(["rgb", "gray"]),
19092
+ timestamp: number(),
19093
+ capturedAt: number().optional()
19094
+ });
19052
19095
  var ModelFormatSchema$1 = _enum([
19053
19096
  "onnx",
19054
19097
  "coreml",
@@ -19369,7 +19412,7 @@ var pipelineExecutorCapability = {
19369
19412
  * legacy call shape used by existing benchmark code; once all
19370
19413
  * callers pass it explicitly we make it required.
19371
19414
  *
19372
- * Exactly one of `frame`, `frameHandle`, `imageBase64`,
19415
+ * Exactly one of `frame`, `frameRef`, `frameHandle`, `imageBase64`,
19373
19416
  * `referenceImage` must be provided:
19374
19417
  * - `frame`: runtime dispatch path (runner → decoded broker frame).
19375
19418
  * Carries the raw buffer, dimensions, and format; the executor
@@ -19391,6 +19434,12 @@ var pipelineExecutorCapability = {
19391
19434
  steps: array(PipelineStepInputSchema).min(1),
19392
19435
  frame: FrameInputSchema.optional(),
19393
19436
  /**
19437
+ * Process-local lazy frame. Valid only when caller and provider resolve
19438
+ * in the same execution-group process; split/cross-node callers use
19439
+ * `frame`/`image` inline compatibility instead.
19440
+ */
19441
+ frameRef: FrameRefSchema.optional(),
19442
+ /**
19394
19443
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
19395
19444
  * the decoded pixels live in. One more member of the one-of
19396
19445
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -19700,7 +19749,10 @@ var NativeCropResultSchema = object({
19700
19749
  * Which source served this crop, so a quality-sensitive consumer (the native
19701
19750
  * `keyFrame`) can reject a degraded fallback:
19702
19751
  * - `native` — cut from the decode worker's retained NATIVE surface (the
19703
- * quality path).
19752
+ * quality path). A subject-tile serve is also native-resolution and stays
19753
+ * `native` here: the public enum cannot name `tile` without a breaking cap
19754
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
19755
+ * internal crop result (`nativeHits` vs `tileHits`).
19704
19756
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
19705
19757
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
19706
19758
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -20191,12 +20243,41 @@ var RunnerLocalLoadSchema = object({
20191
20243
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
20192
20244
  * working unchanged when they switch to reading from the runner cap.
20193
20245
  */
20246
+ var FrameLazyCountersSchema = object({
20247
+ framesDecoded: number(),
20248
+ framesAdmitted: number(),
20249
+ framesDroppedPixelFree: number(),
20250
+ viewsMaterialized: number(),
20251
+ viewsSkipped: number(),
20252
+ workerToRunnerBytes: number(),
20253
+ runnerToPoolRawBytes: number(),
20254
+ runnerToPoolJpegBytes: number(),
20255
+ onDemandFullFrameRequests: number(),
20256
+ onDemandCropRequests: number(),
20257
+ nativeHits: number(),
20258
+ nativeMisses: number(),
20259
+ tileHits: number(),
20260
+ tileMisses: number(),
20261
+ fallbackHits: number(),
20262
+ fallbackMisses: number(),
20263
+ retainedWritesAvoided: number(),
20264
+ residentRefs: number(),
20265
+ residentBytes: number(),
20266
+ releases: number(),
20267
+ evictions: number(),
20268
+ staleMisses: number()
20269
+ });
20270
+ var FrameLazyMetricsSchema = object({
20271
+ node: FrameLazyCountersSchema,
20272
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
20273
+ });
20194
20274
  var RunnerLocalMetricsSchema = object({
20195
20275
  nodeId: string(),
20196
20276
  activeCameras: number(),
20197
20277
  throttledCameras: number(),
20198
20278
  avgInferenceTimeMs: number(),
20199
- queueDepth: number()
20279
+ queueDepth: number(),
20280
+ frameLazy: FrameLazyMetricsSchema.optional()
20200
20281
  });
20201
20282
  /**
20202
20283
  * Pipeline Runner capability — runtime detection workhorse.
@@ -21653,6 +21734,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
21653
21734
  location: StorageLocationSchema,
21654
21735
  relativePath: string()
21655
21736
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
21737
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
21738
+ var ProfileSettingsSchemaBridge = unknown().nullable();
21739
+ var ProfileSettingsBagSchema = record(string(), unknown());
21656
21740
  /**
21657
21741
  * A live terminal session hosted by the provider addon. Output and input do
21658
21742
  * NOT flow through the capability — they use the addon data plane
@@ -21687,7 +21771,9 @@ var TerminalProfileInfoSchema = object({
21687
21771
  executable: string().optional(),
21688
21772
  args: array(string()).readonly().optional(),
21689
21773
  cwd: string().optional(),
21690
- environment: array(string()).readonly().optional()
21774
+ environment: array(string()).readonly().optional(),
21775
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
21776
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
21691
21777
  });
21692
21778
  /**
21693
21779
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -21704,7 +21790,8 @@ var TerminalInstanceInfoSchema = object({
21704
21790
  executable: string(),
21705
21791
  args: array(string()).readonly(),
21706
21792
  cwd: string(),
21707
- environment: array(string()).readonly()
21793
+ environment: array(string()).readonly(),
21794
+ profileSettings: ProfileSettingsBagSchema
21708
21795
  });
21709
21796
  var TerminalLegacyCameraSchema = object({
21710
21797
  stableId: string(),
@@ -21738,7 +21825,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
21738
21825
  executable: string().max(1024).optional(),
21739
21826
  args: array(string().max(2048)).max(64).optional(),
21740
21827
  cwd: string().max(1024).optional(),
21741
- environment: array(string().max(4096)).max(64).optional()
21828
+ environment: array(string().max(4096)).max(64).optional(),
21829
+ profileSettings: ProfileSettingsBagSchema.optional()
21742
21830
  }), TerminalInstanceInfoSchema, {
21743
21831
  kind: "mutation",
21744
21832
  auth: "admin"
@@ -21748,7 +21836,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
21748
21836
  executable: string().max(1024).optional(),
21749
21837
  args: array(string().max(2048)).max(64).optional(),
21750
21838
  cwd: string().max(1024).optional(),
21751
- environment: array(string().max(4096)).max(64).optional()
21839
+ environment: array(string().max(4096)).max(64).optional(),
21840
+ profileSettings: ProfileSettingsBagSchema.optional()
21752
21841
  }), TerminalInstanceInfoSchema, {
21753
21842
  kind: "mutation",
21754
21843
  auth: "admin"
@@ -24703,10 +24792,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
24703
24792
  *
24704
24793
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
24705
24794
  * to receive an ordered list of candidate base URLs it should race
24706
- * on connect — LAN IPv4 first (lowest latency when on same network),
24707
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
24708
- * race them with short timeouts and stick with the winner for the
24709
- * session.
24795
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24796
+ * when on the same network), then public hostname (if a tunnel is
24797
+ * up). The SDK can race them with short timeouts and stick with the
24798
+ * winner for the session.
24710
24799
  *
24711
24800
  * Why hub-only: agents are not directly addressable by the operator's
24712
24801
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24861,6 +24950,17 @@ var NotificationEndpointSchema = object({
24861
24950
  /** What the ranking currently resolves to (null when nothing is reachable). */
24862
24951
  resolved: string().nullable()
24863
24952
  });
24953
+ /**
24954
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24955
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24956
+ * currently expands to, so the UI can show the effective set either way.
24957
+ */
24958
+ var ViewerEndpointsSchema = object({
24959
+ /** The operator's explicit race set, or empty for AUTO. */
24960
+ baseUrls: array(string()).readonly(),
24961
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24962
+ resolved: array(string()).readonly()
24963
+ });
24864
24964
  var AllowedAddressesSchema = object({
24865
24965
  /**
24866
24966
  * Allowlist of interface addresses operators have explicitly opted
@@ -24892,17 +24992,18 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24892
24992
  */
24893
24993
  port: number().int().min(1).max(65535).optional(),
24894
24994
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24895
- * candidate. Default `true`. */
24995
+ * candidate. Default `false` — loopback is not a client route. */
24896
24996
  includeLoopback: boolean().optional(),
24897
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24898
- * Default `false`. */
24997
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24998
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24999
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24899
25000
  ipv4Only: boolean().optional(),
24900
25001
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24901
25002
  * Pass `'https'` when the caller is itself loaded over HTTPS
24902
25003
  * to avoid mixed-content blocks in the browser. The public
24903
25004
  * tunnel always emits `https://` regardless. */
24904
25005
  scheme: _enum(["http", "https"]).optional()
24905
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
25006
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24906
25007
  kind: "mutation",
24907
25008
  auth: "admin"
24908
25009
  }), method(object({
@@ -30886,6 +30987,12 @@ Object.freeze({
30886
30987
  addonId: null,
30887
30988
  access: "view"
30888
30989
  },
30990
+ "localNetwork.getViewerEndpoints": {
30991
+ capName: "local-network",
30992
+ capScope: "system",
30993
+ addonId: null,
30994
+ access: "view"
30995
+ },
30889
30996
  "localNetwork.list": {
30890
30997
  capName: "local-network",
30891
30998
  capScope: "system",
@@ -30922,6 +31029,12 @@ Object.freeze({
30922
31029
  addonId: null,
30923
31030
  access: "create"
30924
31031
  },
31032
+ "localNetwork.setViewerEndpoints": {
31033
+ capName: "local-network",
31034
+ capScope: "system",
31035
+ addonId: null,
31036
+ access: "create"
31037
+ },
30925
31038
  "localNetwork.uploadCertificate": {
30926
31039
  capName: "local-network",
30927
31040
  capScope: "system",
@@ -1,4 +1,5 @@
1
- import { K as hfModelUrl, a as COCO_80_LABELS, j as audioKindId, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-CkXAzTZ2.mjs";
1
+ import { K as hfModelUrl, a as COCO_80_LABELS, j as audioKindId, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-DGQkkWc6.mjs";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { PerformanceObserver } from "node:perf_hooks";
3
4
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
5
  var HF_REPO = "camstack/camstack-models";
@@ -1890,6 +1891,226 @@ function resolveModelForFormat(stepId, chosenModelId, format) {
1890
1891
  return getDefaultModelForFormat(stepId, format);
1891
1892
  }
1892
1893
  //#endregion
1894
+ //#region src/session-decode/frame-view-geometry.ts
1895
+ function positiveInteger(value, label) {
1896
+ if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be positive`);
1897
+ return Math.max(1, Math.round(value));
1898
+ }
1899
+ function clampCrop(sourceWidth, sourceHeight, crop) {
1900
+ if (!crop) return {
1901
+ left: 0,
1902
+ top: 0,
1903
+ width: sourceWidth,
1904
+ height: sourceHeight
1905
+ };
1906
+ const left = Math.max(0, Math.min(sourceWidth - 1, Math.floor(crop.left)));
1907
+ const top = Math.max(0, Math.min(sourceHeight - 1, Math.floor(crop.top)));
1908
+ const right = Math.max(left + 1, Math.min(sourceWidth, Math.ceil(crop.left + crop.width)));
1909
+ const bottom = Math.max(top + 1, Math.min(sourceHeight, Math.ceil(crop.top + crop.height)));
1910
+ return {
1911
+ left,
1912
+ top,
1913
+ width: right - left,
1914
+ height: bottom - top
1915
+ };
1916
+ }
1917
+ /** Pure geometry for worker-side crop/resize without model padding. */
1918
+ function resolveFrameViewGeometry(sourceWidthInput, sourceHeightInput, spec) {
1919
+ const sourceWidth = positiveInteger(sourceWidthInput, "source width");
1920
+ const sourceHeight = positiveInteger(sourceHeightInput, "source height");
1921
+ const targetWidth = positiveInteger(spec.content.width, "content width");
1922
+ const targetHeight = positiveInteger(spec.content.height, "content height");
1923
+ const sourceCrop = clampCrop(sourceWidth, sourceHeight, spec.crop);
1924
+ const output = spec.fit === "stretch" ? {
1925
+ width: targetWidth,
1926
+ height: targetHeight
1927
+ } : (() => {
1928
+ const scale = Math.min(targetWidth / sourceCrop.width, targetHeight / sourceCrop.height);
1929
+ return {
1930
+ width: Math.max(1, Math.round(sourceCrop.width * scale)),
1931
+ height: Math.max(1, Math.round(sourceCrop.height * scale))
1932
+ };
1933
+ })();
1934
+ return {
1935
+ source: {
1936
+ width: sourceWidth,
1937
+ height: sourceHeight
1938
+ },
1939
+ sourceCrop,
1940
+ output,
1941
+ scale: {
1942
+ x: output.width / sourceCrop.width,
1943
+ y: output.height / sourceCrop.height
1944
+ }
1945
+ };
1946
+ }
1947
+ //#endregion
1948
+ //#region src/session-decode/local-frame-registry.ts
1949
+ var DEFAULT_MAX_FRAMES = 24;
1950
+ var DEFAULT_MAX_BYTES = 512 * 1024 * 1024;
1951
+ function estimateFrameBytes(image) {
1952
+ return image.width * image.height * (image.format === "gray" ? 1 : 3);
1953
+ }
1954
+ function releasesNative(reason) {
1955
+ return reason !== "success";
1956
+ }
1957
+ /**
1958
+ * Bounded process-local registry. A serialized ref from another process misses
1959
+ * by registry id; an evicted id never aliases a newer frame. View resolution
1960
+ * is lease-only ({@link acquire} → {@link LocalFrameLease.resolve}) so an
1961
+ * in-flight `toBuffer()` is covered by `refs > 1` and cannot be LRU-evicted.
1962
+ */
1963
+ var LocalFrameRegistry = class {
1964
+ registryId;
1965
+ maxFrames;
1966
+ maxBytes;
1967
+ entries = /* @__PURE__ */ new Map();
1968
+ bytes = 0;
1969
+ releaseCount = 0;
1970
+ evictionCount = 0;
1971
+ staleMissCount = 0;
1972
+ constructor(options = {}) {
1973
+ this.registryId = options.registryId ?? `${process.pid}-${randomUUID()}`;
1974
+ this.maxFrames = options.maxFrames ?? DEFAULT_MAX_FRAMES;
1975
+ this.maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
1976
+ }
1977
+ get size() {
1978
+ return this.entries.size;
1979
+ }
1980
+ get estimatedBytes() {
1981
+ return this.bytes;
1982
+ }
1983
+ getStats() {
1984
+ let residentRefs = 0;
1985
+ for (const entry of this.entries.values()) residentRefs += entry.refs;
1986
+ return {
1987
+ residentFrames: this.entries.size,
1988
+ residentRefs,
1989
+ residentBytes: this.bytes,
1990
+ releases: this.releaseCount,
1991
+ evictions: this.evictionCount,
1992
+ staleMisses: this.staleMissCount
1993
+ };
1994
+ }
1995
+ register(image, metadata) {
1996
+ const estimatedBytes = estimateFrameBytes(image);
1997
+ if (this.maxFrames < 1 || estimatedBytes > this.maxBytes) {
1998
+ image.close();
1999
+ image.releaseNativeLease?.();
2000
+ return null;
2001
+ }
2002
+ const ref = {
2003
+ registryId: this.registryId,
2004
+ id: randomUUID(),
2005
+ width: image.width,
2006
+ height: image.height,
2007
+ format: image.format,
2008
+ timestamp: metadata.timestamp,
2009
+ ...metadata.capturedAt !== void 0 ? { capturedAt: metadata.capturedAt } : {}
2010
+ };
2011
+ this.entries.set(ref.id, {
2012
+ ref,
2013
+ image,
2014
+ estimatedBytes,
2015
+ refs: 1,
2016
+ releaseNativeOnClose: false
2017
+ });
2018
+ this.bytes += estimatedBytes;
2019
+ this.evictToBounds();
2020
+ return this.entries.has(ref.id) ? ref : null;
2021
+ }
2022
+ acquire(ref) {
2023
+ if (ref.registryId === this.registryId && !this.entries.has(ref.id)) this.staleMissCount += 1;
2024
+ const entry = this.getEntry(ref);
2025
+ if (!entry) return null;
2026
+ entry.refs += 1;
2027
+ this.touch(entry);
2028
+ let released = false;
2029
+ return {
2030
+ resolve: (spec) => this.resolveFromLease(entry, spec),
2031
+ release: (reason) => {
2032
+ if (released) return;
2033
+ released = true;
2034
+ this.release(ref, reason);
2035
+ }
2036
+ };
2037
+ }
2038
+ release(ref, reason) {
2039
+ const entry = this.getEntry(ref);
2040
+ if (!entry) return;
2041
+ this.releaseCount += 1;
2042
+ if (releasesNative(reason)) entry.releaseNativeOnClose = true;
2043
+ entry.refs = Math.max(0, entry.refs - 1);
2044
+ if (entry.refs === 0) this.drop(entry);
2045
+ }
2046
+ clear(reason = "detach") {
2047
+ for (const entry of [...this.entries.values()]) {
2048
+ if (releasesNative(reason)) entry.releaseNativeOnClose = true;
2049
+ this.drop(entry);
2050
+ }
2051
+ }
2052
+ getEntry(ref) {
2053
+ if (ref.registryId !== this.registryId) return null;
2054
+ return this.entries.get(ref.id) ?? null;
2055
+ }
2056
+ touch(entry) {
2057
+ this.entries.delete(entry.ref.id);
2058
+ this.entries.set(entry.ref.id, entry);
2059
+ }
2060
+ /**
2061
+ * Materialize a view for a live lease only. Public callers must go through
2062
+ * {@link acquire} so `refs > 1` covers the whole `toBuffer()` window.
2063
+ */
2064
+ async resolveFromLease(entry, spec) {
2065
+ if (!this.entries.has(entry.ref.id)) return {
2066
+ kind: "miss",
2067
+ reason: "stale-ref"
2068
+ };
2069
+ this.touch(entry);
2070
+ const geometry = resolveFrameViewGeometry(entry.image.width, entry.image.height, spec);
2071
+ return {
2072
+ kind: "view",
2073
+ data: await entry.image.toBuffer({
2074
+ crop: geometry.sourceCrop,
2075
+ resize: geometry.output,
2076
+ format: spec.format
2077
+ }),
2078
+ width: geometry.output.width,
2079
+ height: geometry.output.height,
2080
+ format: spec.format,
2081
+ geometry
2082
+ };
2083
+ }
2084
+ /** Oldest owner-only / queued entry (`refs === 1`). Consumer leases (`refs > 1`) stay. */
2085
+ findOwnerOnlyVictim() {
2086
+ for (const entry of this.entries.values()) if (entry.refs <= 1) return entry;
2087
+ }
2088
+ evictToBounds() {
2089
+ while (this.entries.size > this.maxFrames || this.bytes > this.maxBytes) {
2090
+ const victim = this.findOwnerOnlyVictim();
2091
+ if (!victim) break;
2092
+ victim.releaseNativeOnClose = true;
2093
+ this.evictionCount += 1;
2094
+ this.drop(victim);
2095
+ }
2096
+ }
2097
+ drop(entry) {
2098
+ if (!this.entries.delete(entry.ref.id)) return;
2099
+ this.bytes = Math.max(0, this.bytes - entry.estimatedBytes);
2100
+ entry.image.close();
2101
+ if (entry.releaseNativeOnClose) entry.image.releaseNativeLease?.();
2102
+ }
2103
+ };
2104
+ function envPositiveInt(name, fallback) {
2105
+ const parsed = Number(process.env[name]);
2106
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
2107
+ }
2108
+ /** Shared only by addons in the same grouped runner process. */
2109
+ var localFrameRegistry = new LocalFrameRegistry({
2110
+ maxFrames: envPositiveInt("CAMSTACK_LOCAL_FRAME_MAX_FRAMES", DEFAULT_MAX_FRAMES),
2111
+ maxBytes: envPositiveInt("CAMSTACK_LOCAL_FRAME_BUDGET_MB", DEFAULT_MAX_BYTES / 1048576) * 1048576
2112
+ });
2113
+ //#endregion
1893
2114
  //#region src/detection-pipeline/diagnostics/event-loop-stall-monitor.ts
1894
2115
  /**
1895
2116
  * event-loop-stall-monitor — names the thing that freezes the detection process.
@@ -2026,4 +2247,4 @@ function startEventLoopStallMonitor(logger, reportThresholdMs = STALL_REPORT_MS)
2026
2247
  };
2027
2248
  }
2028
2249
  //#endregion
2029
- export { getDefaultModelForFormat as a, getStepDefinition as c, ALL_STEPS as i, resolveModelForFormat as l, startEventLoopStallMonitor as n, getDefaultModelForFormatFromDef as o, ALL_PIPELINE_STEPS as r, getStep as s, attributeStall as t, landmarkPrecisionVerdict as u };
2250
+ export { ALL_PIPELINE_STEPS as a, getDefaultModelForFormatFromDef as c, resolveModelForFormat as d, landmarkPrecisionVerdict as f, resolveFrameViewGeometry as i, getStep as l, startEventLoopStallMonitor as n, ALL_STEPS as o, localFrameRegistry as r, getDefaultModelForFormat as s, attributeStall as t, getStepDefinition as u };