@camstack/addon-pipeline 1.2.73 → 1.2.74

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 (24) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/detection-pipeline/index.js +2 -2
  4. package/dist/detection-pipeline/index.mjs +2 -2
  5. package/dist/{dist-Dpr92hOt.mjs → dist-CKY0efl3.mjs} +1 -1
  6. package/dist/{dist-BAbvf81B.js → dist-CgJ8MRAy.js} +1 -1
  7. package/dist/{event-loop-stall-monitor-B7Wd2jo-.mjs → event-loop-stall-monitor-CIapFIez.mjs} +1 -1
  8. package/dist/{event-loop-stall-monitor-BhKoybot.js → event-loop-stall-monitor-Dx-zXcNg.js} +1 -1
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/pipeline-runner/index.js +106 -17
  12. package/dist/pipeline-runner/index.mjs +106 -17
  13. package/dist/recorder/index.js +1 -1
  14. package/dist/recorder/index.mjs +1 -1
  15. package/dist/session-decode/decode-worker-child.js +1 -1
  16. package/dist/session-decode/decode-worker-child.mjs +1 -1
  17. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Cvh5La_s.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVJ4gKkk.mjs} +1 -1
  18. package/dist/stream-broker/{hostInit-Ci7epN77.mjs → hostInit-5SFdfUyc.mjs} +1 -1
  19. package/dist/stream-broker/index.js +1 -1
  20. package/dist/stream-broker/index.mjs +1 -1
  21. package/dist/stream-broker/remoteEntry.js +1 -1
  22. package/dist/{worker-protocol-C1FtUWvH.mjs → worker-protocol-BzAQPUR-.mjs} +1 -1
  23. package/dist/{worker-protocol-BFq9AIDa.js → worker-protocol-D44DCkcs.js} +1 -1
  24. package/package.json +1 -1
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-BAbvf81B.js");
6
+ const require_dist = require("../dist-CgJ8MRAy.js");
7
7
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
8
8
  const require_addon_utils = require("../addon-utils-CQs-AjTJ.js");
9
9
  let node_fs = require("node:fs");
@@ -1,5 +1,5 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { B as mapAudioLabelToMacro, E as audioAnalyzerCapability, T as audioAnalysisCapability, ft as hydrateSchema, m as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, st as BaseAddon, tt as errMsg } from "../dist-Dpr92hOt.mjs";
2
+ import { B as mapAudioLabelToMacro, E as audioAnalyzerCapability, T as audioAnalysisCapability, ft as hydrateSchema, m as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, st as BaseAddon, tt as errMsg } from "../dist-CKY0efl3.mjs";
3
3
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
4
4
  import { i as downloadFile } from "../addon-utils-CZ2xo67g.mjs";
5
5
  import * as fs from "node:fs";
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-BAbvf81B.js");
7
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BhKoybot.js");
6
+ const require_dist = require("../dist-CgJ8MRAy.js");
7
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-Dx-zXcNg.js");
8
8
  const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
9
9
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
10
10
  const require_addon_utils = require("../addon-utils-CQs-AjTJ.js");
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { $ as supportedRuntimes$1, A as defaultDeviceFor$1, At as string, C as YAMNET_TO_MACRO, G as pipelineExecutorCapability, L as enumerateInferenceDevices, Mt as EventCategory, Ot as object, P as detectionPipelineCapability, R as evaluateZoneRules, St as array, X as runtimeDevices$1, _t as parseJsonUnknown, bt as sleep, dt as createEvent, ft as hydrateSchema, gt as nodePin, jt as union, st as BaseAddon, t as APPLE_SA_TO_MACRO, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT } from "../dist-Dpr92hOt.mjs";
3
- import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-B7Wd2jo-.mjs";
2
+ import { $ as supportedRuntimes$1, A as defaultDeviceFor$1, At as string, C as YAMNET_TO_MACRO, G as pipelineExecutorCapability, L as enumerateInferenceDevices, Mt as EventCategory, Ot as object, P as detectionPipelineCapability, R as evaluateZoneRules, St as array, X as runtimeDevices$1, _t as parseJsonUnknown, bt as sleep, dt as createEvent, ft as hydrateSchema, gt as nodePin, jt as union, st as BaseAddon, t as APPLE_SA_TO_MACRO, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT } from "../dist-CKY0efl3.mjs";
3
+ import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-CIapFIez.mjs";
4
4
  import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
5
5
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
6
6
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-CZ2xo67g.mjs";
@@ -19194,7 +19194,7 @@ var detectionFpsField = {
19194
19194
  var occupancyRecheckSecField = {
19195
19195
  min: 0,
19196
19196
  max: 300,
19197
- default: 30,
19197
+ default: 300,
19198
19198
  step: 5
19199
19199
  };
19200
19200
  var occupancyRecheckFramesField = {
@@ -19194,7 +19194,7 @@ var detectionFpsField = {
19194
19194
  var occupancyRecheckSecField = {
19195
19195
  min: 0,
19196
19196
  max: 300,
19197
- default: 30,
19197
+ default: 300,
19198
19198
  step: 5
19199
19199
  };
19200
19200
  var occupancyRecheckFramesField = {
@@ -1,4 +1,4 @@
1
- import { a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS, z as hfModelUrl } from "./dist-Dpr92hOt.mjs";
1
+ import { a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS, z as hfModelUrl } from "./dist-CKY0efl3.mjs";
2
2
  import { PerformanceObserver } from "node:perf_hooks";
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-BAbvf81B.js");
1
+ const require_dist = require("./dist-CgJ8MRAy.js");
2
2
  let node_perf_hooks = require("node:perf_hooks");
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-BAbvf81B.js");
5
+ const require_dist = require("../dist-CgJ8MRAy.js");
6
6
  const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { H as motionDetectionCapability, R as evaluateZoneRules, ft as hydrateSchema, st as BaseAddon, ut as DeviceType } from "../dist-Dpr92hOt.mjs";
1
+ import { H as motionDetectionCapability, R as evaluateZoneRules, ft as hydrateSchema, st as BaseAddon, ut as DeviceType } from "../dist-CKY0efl3.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-BAbvf81B.js");
7
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BhKoybot.js");
8
- const require_worker_protocol = require("../worker-protocol-BFq9AIDa.js");
6
+ const require_dist = require("../dist-CgJ8MRAy.js");
7
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-Dx-zXcNg.js");
8
+ const require_worker_protocol = require("../worker-protocol-D44DCkcs.js");
9
9
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
10
10
  const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
11
11
  const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
@@ -3836,6 +3836,18 @@ function isUsable(bbox) {
3836
3836
  }
3837
3837
  //#endregion
3838
3838
  //#region src/pipeline-runner/index.ts
3839
+ /**
3840
+ * Bound on the frames whose cutter is bound but whose result has not come back.
3841
+ * A few per camera in flight; the ceiling only catches results that never
3842
+ * arrive (shed, inference timeout, detach mid-flight).
3843
+ */
3844
+ var MAX_BOUND_FRAME_CUTTERS = 256;
3845
+ /** How often a camera reports its undispatched cuts. */
3846
+ var TILE_CUT_DROP_LOG_INTERVAL_MS = 6e4;
3847
+ /** The per-frame binding key — the same identity the retention handle carries. */
3848
+ function frameCutterKey(handle) {
3849
+ return `${handle.shmId}#${handle.slot}#${handle.seq}`;
3850
+ }
3839
3851
  var DEFAULT_CONFIG = {
3840
3852
  maxQueueDepth: 30,
3841
3853
  maxConcurrentInferences: 16,
@@ -4151,6 +4163,20 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4151
4163
  */
4152
4164
  nativeCropRegistry = new NativeCropRegistry();
4153
4165
  /**
4166
+ * Per-frame cutter bindings — handle key → the requester that can cut this
4167
+ * frame's subject tiles and end its hold.
4168
+ *
4169
+ * Deliberately NOT `nativeCropRegistry`. That one is a node-wide LRU capped
4170
+ * at 256 and shared by every camera to serve LATE cross-process crops; on the
4171
+ * live hub it sits pinned at its cap, so an entry's lifetime depends on how
4172
+ * busy the OTHER cameras are. Ending a frame's hold is a per-frame lifecycle
4173
+ * event and cannot be allowed to depend on that.
4174
+ */
4175
+ frameCutters = /* @__PURE__ */ new Map();
4176
+ /** Per-camera tally of cuts that could not be dispatched, and why. */
4177
+ tileCutDrops = /* @__PURE__ */ new Map();
4178
+ tileCutDropLoggedAt = /* @__PURE__ */ new Map();
4179
+ /**
4154
4180
  * Node-level RAM store of retained rgb detection frames, keyed by the
4155
4181
  * `FrameHandle` that rides the inference-result event (shm-ring removal
4156
4182
  * 2026-07-16 — replaces the `csr.` shm retention ring). Backs the
@@ -5158,23 +5184,80 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
5158
5184
  * by an encode.
5159
5185
  */
5160
5186
  cutSubjectTiles(deviceId, result, handle) {
5161
- if (!handle) return;
5162
- const found = this.nativeCropRegistry.get(handle);
5163
- if (!found) return;
5187
+ const outcome = this.dispatchSubjectTiles(result, handle);
5188
+ if (outcome === "sent") return;
5189
+ const tally = this.tileCutDrops.get(deviceId) ?? {
5190
+ handleless: 0,
5191
+ unbound: 0,
5192
+ threw: 0
5193
+ };
5194
+ tally[outcome] += 1;
5195
+ this.tileCutDrops.set(deviceId, tally);
5196
+ const now = Date.now();
5197
+ if (now - (this.tileCutDropLoggedAt.get(deviceId) ?? 0) < TILE_CUT_DROP_LOG_INTERVAL_MS) return;
5198
+ this.tileCutDropLoggedAt.set(deviceId, now);
5199
+ this.ctx.logger.warn("subject tile cut NOT dispatched — the frame hold will not be released", {
5200
+ tags: { deviceId },
5201
+ meta: {
5202
+ ...tally,
5203
+ lastReason: outcome,
5204
+ boundFrames: this.frameCutters.size
5205
+ }
5206
+ });
5207
+ }
5208
+ /**
5209
+ * Send the cut for one finished frame. Returns what happened, so the caller
5210
+ * can account for it.
5211
+ *
5212
+ * The cutter is looked up in {@link frameCutters} — the addon's OWN per-frame
5213
+ * binding — and NOT in `nativeCropRegistry`. That distinction is the bug this
5214
+ * method exists to not have again: the registry is a node-wide LRU capped at
5215
+ * 256 entries, shared by every camera, and it exists to serve LATE
5216
+ * cross-process crops. Measured on the live hub during the incident it sits
5217
+ * PINNED at `registrySize=256 registryCap=256`, so an entry's lifetime is a
5218
+ * function of how many other cameras are busy. Ending a frame's hold is a
5219
+ * per-frame lifecycle event and must never be subject to another camera's
5220
+ * eviction pressure.
5221
+ */
5222
+ dispatchSubjectTiles(result, handle) {
5223
+ if (!handle) return "handleless";
5224
+ const key = frameCutterKey(handle);
5225
+ const cutter = this.frameCutters.get(key);
5226
+ this.frameCutters.delete(key);
5227
+ if (!cutter) return "unbound";
5164
5228
  const boxes = planTileCuts(result.detections ?? [], {
5165
5229
  width: result.width,
5166
5230
  height: result.height
5167
5231
  });
5168
5232
  try {
5169
- found.request.cutTiles(boxes);
5170
- } catch (err) {
5171
- this.ctx.logger.debug("subject tile cut could not be dispatched", {
5172
- tags: { deviceId },
5173
- meta: {
5174
- subjects: boxes.length,
5175
- error: err instanceof Error ? err.message : String(err)
5176
- }
5177
- });
5233
+ cutter.cutTiles(boxes);
5234
+ return "sent";
5235
+ } catch {
5236
+ return "threw";
5237
+ }
5238
+ }
5239
+ /**
5240
+ * Bind a frame's cutter at DELIVERY, so its result can end its hold.
5241
+ *
5242
+ * Bounded by {@link MAX_BOUND_FRAME_CUTTERS}: a frame whose result never
5243
+ * comes back (a shed, a timed-out inference, a camera detaching mid-flight)
5244
+ * would otherwise leave its binding forever. The oldest is dropped and its
5245
+ * hold released directly — the worker's own frame bound would have reclaimed
5246
+ * it anyway, but releasing here is what keeps `holdOverflow` meaning "the
5247
+ * runner stopped answering" rather than "the runner forgot".
5248
+ */
5249
+ bindFrameCutter(handle, cutter) {
5250
+ const key = frameCutterKey(handle);
5251
+ this.frameCutters.delete(key);
5252
+ this.frameCutters.set(key, cutter);
5253
+ while (this.frameCutters.size > MAX_BOUND_FRAME_CUTTERS) {
5254
+ const oldest = this.frameCutters.keys().next().value;
5255
+ if (oldest === void 0) break;
5256
+ const stale = this.frameCutters.get(oldest);
5257
+ this.frameCutters.delete(oldest);
5258
+ try {
5259
+ stale?.release();
5260
+ } catch {}
5178
5261
  }
5179
5262
  }
5180
5263
  /**
@@ -5973,7 +6056,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
5973
6056
  reservation = null;
5974
6057
  if (!current) return;
5975
6058
  const handle = current.detection ? retainedFrames?.write(config.deviceId, frame) ?? void 0 : void 0;
5976
- if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6059
+ if (handle && nativeCrop) {
6060
+ this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6061
+ this.bindFrameCutter(handle, nativeCrop);
6062
+ }
5977
6063
  if (this.enqueueSharedDetectionFrame(config.deviceId, frame, handle, current)) nativeCrop?.markInferred();
5978
6064
  }
5979
6065
  });
@@ -6497,7 +6583,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6497
6583
  onDecodedFrame: (frame, nativeCrop) => {
6498
6584
  if (settled) return;
6499
6585
  const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
6500
- if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6586
+ if (handle && nativeCrop) {
6587
+ this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6588
+ this.bindFrameCutter(handle, nativeCrop);
6589
+ }
6501
6590
  if (runner.enqueueOccupancyFrame(deviceId, frame, handle)) nativeCrop?.markInferred();
6502
6591
  count++;
6503
6592
  if (count >= frames) {
@@ -1,6 +1,6 @@
1
- import { At as string, Ct as boolean, Dt as number, Et as literal, K as pipelineRunnerCapability, M as deriveDetailCropRect, Mt as EventCategory, Ot as object, St as array, Tt as lazy, U as pickDetailCropConvention, W as pickNativeLeaseOverride, dt as createEvent, gt as nodePin, j as defineCustomActions, k as customAction, s as DEFAULT_DETAIL_CROP_CONVENTION, st as BaseAddon, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT, xt as _enum } from "../dist-Dpr92hOt.mjs";
2
- import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-B7Wd2jo-.mjs";
3
- import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-C1FtUWvH.mjs";
1
+ import { At as string, Ct as boolean, Dt as number, Et as literal, K as pipelineRunnerCapability, M as deriveDetailCropRect, Mt as EventCategory, Ot as object, St as array, Tt as lazy, U as pickDetailCropConvention, W as pickNativeLeaseOverride, dt as createEvent, gt as nodePin, j as defineCustomActions, k as customAction, s as DEFAULT_DETAIL_CROP_CONVENTION, st as BaseAddon, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT, xt as _enum } from "../dist-CKY0efl3.mjs";
2
+ import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-CIapFIez.mjs";
3
+ import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-BzAQPUR-.mjs";
4
4
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
6
6
  import { i as withDetectionIntent, n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-Ci7RXNGb.mjs";
@@ -3828,6 +3828,18 @@ function isUsable(bbox) {
3828
3828
  }
3829
3829
  //#endregion
3830
3830
  //#region src/pipeline-runner/index.ts
3831
+ /**
3832
+ * Bound on the frames whose cutter is bound but whose result has not come back.
3833
+ * A few per camera in flight; the ceiling only catches results that never
3834
+ * arrive (shed, inference timeout, detach mid-flight).
3835
+ */
3836
+ var MAX_BOUND_FRAME_CUTTERS = 256;
3837
+ /** How often a camera reports its undispatched cuts. */
3838
+ var TILE_CUT_DROP_LOG_INTERVAL_MS = 6e4;
3839
+ /** The per-frame binding key — the same identity the retention handle carries. */
3840
+ function frameCutterKey(handle) {
3841
+ return `${handle.shmId}#${handle.slot}#${handle.seq}`;
3842
+ }
3831
3843
  var DEFAULT_CONFIG = {
3832
3844
  maxQueueDepth: 30,
3833
3845
  maxConcurrentInferences: 16,
@@ -4143,6 +4155,20 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4143
4155
  */
4144
4156
  nativeCropRegistry = new NativeCropRegistry();
4145
4157
  /**
4158
+ * Per-frame cutter bindings — handle key → the requester that can cut this
4159
+ * frame's subject tiles and end its hold.
4160
+ *
4161
+ * Deliberately NOT `nativeCropRegistry`. That one is a node-wide LRU capped
4162
+ * at 256 and shared by every camera to serve LATE cross-process crops; on the
4163
+ * live hub it sits pinned at its cap, so an entry's lifetime depends on how
4164
+ * busy the OTHER cameras are. Ending a frame's hold is a per-frame lifecycle
4165
+ * event and cannot be allowed to depend on that.
4166
+ */
4167
+ frameCutters = /* @__PURE__ */ new Map();
4168
+ /** Per-camera tally of cuts that could not be dispatched, and why. */
4169
+ tileCutDrops = /* @__PURE__ */ new Map();
4170
+ tileCutDropLoggedAt = /* @__PURE__ */ new Map();
4171
+ /**
4146
4172
  * Node-level RAM store of retained rgb detection frames, keyed by the
4147
4173
  * `FrameHandle` that rides the inference-result event (shm-ring removal
4148
4174
  * 2026-07-16 — replaces the `csr.` shm retention ring). Backs the
@@ -5150,23 +5176,80 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
5150
5176
  * by an encode.
5151
5177
  */
5152
5178
  cutSubjectTiles(deviceId, result, handle) {
5153
- if (!handle) return;
5154
- const found = this.nativeCropRegistry.get(handle);
5155
- if (!found) return;
5179
+ const outcome = this.dispatchSubjectTiles(result, handle);
5180
+ if (outcome === "sent") return;
5181
+ const tally = this.tileCutDrops.get(deviceId) ?? {
5182
+ handleless: 0,
5183
+ unbound: 0,
5184
+ threw: 0
5185
+ };
5186
+ tally[outcome] += 1;
5187
+ this.tileCutDrops.set(deviceId, tally);
5188
+ const now = Date.now();
5189
+ if (now - (this.tileCutDropLoggedAt.get(deviceId) ?? 0) < TILE_CUT_DROP_LOG_INTERVAL_MS) return;
5190
+ this.tileCutDropLoggedAt.set(deviceId, now);
5191
+ this.ctx.logger.warn("subject tile cut NOT dispatched — the frame hold will not be released", {
5192
+ tags: { deviceId },
5193
+ meta: {
5194
+ ...tally,
5195
+ lastReason: outcome,
5196
+ boundFrames: this.frameCutters.size
5197
+ }
5198
+ });
5199
+ }
5200
+ /**
5201
+ * Send the cut for one finished frame. Returns what happened, so the caller
5202
+ * can account for it.
5203
+ *
5204
+ * The cutter is looked up in {@link frameCutters} — the addon's OWN per-frame
5205
+ * binding — and NOT in `nativeCropRegistry`. That distinction is the bug this
5206
+ * method exists to not have again: the registry is a node-wide LRU capped at
5207
+ * 256 entries, shared by every camera, and it exists to serve LATE
5208
+ * cross-process crops. Measured on the live hub during the incident it sits
5209
+ * PINNED at `registrySize=256 registryCap=256`, so an entry's lifetime is a
5210
+ * function of how many other cameras are busy. Ending a frame's hold is a
5211
+ * per-frame lifecycle event and must never be subject to another camera's
5212
+ * eviction pressure.
5213
+ */
5214
+ dispatchSubjectTiles(result, handle) {
5215
+ if (!handle) return "handleless";
5216
+ const key = frameCutterKey(handle);
5217
+ const cutter = this.frameCutters.get(key);
5218
+ this.frameCutters.delete(key);
5219
+ if (!cutter) return "unbound";
5156
5220
  const boxes = planTileCuts(result.detections ?? [], {
5157
5221
  width: result.width,
5158
5222
  height: result.height
5159
5223
  });
5160
5224
  try {
5161
- found.request.cutTiles(boxes);
5162
- } catch (err) {
5163
- this.ctx.logger.debug("subject tile cut could not be dispatched", {
5164
- tags: { deviceId },
5165
- meta: {
5166
- subjects: boxes.length,
5167
- error: err instanceof Error ? err.message : String(err)
5168
- }
5169
- });
5225
+ cutter.cutTiles(boxes);
5226
+ return "sent";
5227
+ } catch {
5228
+ return "threw";
5229
+ }
5230
+ }
5231
+ /**
5232
+ * Bind a frame's cutter at DELIVERY, so its result can end its hold.
5233
+ *
5234
+ * Bounded by {@link MAX_BOUND_FRAME_CUTTERS}: a frame whose result never
5235
+ * comes back (a shed, a timed-out inference, a camera detaching mid-flight)
5236
+ * would otherwise leave its binding forever. The oldest is dropped and its
5237
+ * hold released directly — the worker's own frame bound would have reclaimed
5238
+ * it anyway, but releasing here is what keeps `holdOverflow` meaning "the
5239
+ * runner stopped answering" rather than "the runner forgot".
5240
+ */
5241
+ bindFrameCutter(handle, cutter) {
5242
+ const key = frameCutterKey(handle);
5243
+ this.frameCutters.delete(key);
5244
+ this.frameCutters.set(key, cutter);
5245
+ while (this.frameCutters.size > MAX_BOUND_FRAME_CUTTERS) {
5246
+ const oldest = this.frameCutters.keys().next().value;
5247
+ if (oldest === void 0) break;
5248
+ const stale = this.frameCutters.get(oldest);
5249
+ this.frameCutters.delete(oldest);
5250
+ try {
5251
+ stale?.release();
5252
+ } catch {}
5170
5253
  }
5171
5254
  }
5172
5255
  /**
@@ -5965,7 +6048,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
5965
6048
  reservation = null;
5966
6049
  if (!current) return;
5967
6050
  const handle = current.detection ? retainedFrames?.write(config.deviceId, frame) ?? void 0 : void 0;
5968
- if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6051
+ if (handle && nativeCrop) {
6052
+ this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6053
+ this.bindFrameCutter(handle, nativeCrop);
6054
+ }
5969
6055
  if (this.enqueueSharedDetectionFrame(config.deviceId, frame, handle, current)) nativeCrop?.markInferred();
5970
6056
  }
5971
6057
  });
@@ -6489,7 +6575,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6489
6575
  onDecodedFrame: (frame, nativeCrop) => {
6490
6576
  if (settled) return;
6491
6577
  const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
6492
- if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6578
+ if (handle && nativeCrop) {
6579
+ this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
6580
+ this.bindFrameCutter(handle, nativeCrop);
6581
+ }
6493
6582
  if (runner.enqueueOccupancyFrame(deviceId, frame, handle)) nativeCrop?.markInferred();
6494
6583
  count++;
6495
6584
  if (count >= frames) {
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("../chunk-emK7D4bc.js");
2
- const require_dist = require("../dist-BAbvf81B.js");
2
+ const require_dist = require("../dist-CgJ8MRAy.js");
3
3
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
4
4
  const require_addon_utils = require("../addon-utils-CQs-AjTJ.js");
5
5
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { At as string, Dt as number, J as recordingExportCapability, Mt as EventCategory, N as deriveRecordingMode, Ot as object, St as array, Z as storageEvictableCapability, _ as OpsLogEntrySchema, b as RECORDING_EXPORT_MAX_READ_BYTES, c as DEFAULT_EVENTS_BAND_BUFFER_SEC, d as EVENT_PAD_MS, ft as hydrateSchema, gt as nodePin, kt as record, p as ExportRecordSchema, q as recordingCapability, st as BaseAddon, tt as errMsg, ut as DeviceType, x as RecordingConfigSchema, yt as selectAssignedProfileSlots } from "../dist-Dpr92hOt.mjs";
1
+ import { At as string, Dt as number, J as recordingExportCapability, Mt as EventCategory, N as deriveRecordingMode, Ot as object, St as array, Z as storageEvictableCapability, _ as OpsLogEntrySchema, b as RECORDING_EXPORT_MAX_READ_BYTES, c as DEFAULT_EVENTS_BAND_BUFFER_SEC, d as EVENT_PAD_MS, ft as hydrateSchema, gt as nodePin, kt as record, p as ExportRecordSchema, q as recordingCapability, st as BaseAddon, tt as errMsg, ut as DeviceType, x as RecordingConfigSchema, yt as selectAssignedProfileSlots } from "../dist-CKY0efl3.mjs";
2
2
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
3
3
  import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../addon-utils-CZ2xo67g.mjs";
4
4
  import { randomUUID } from "node:crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../chunk-emK7D4bc.js");
3
- const require_worker_protocol = require("../worker-protocol-BFq9AIDa.js");
3
+ const require_worker_protocol = require("../worker-protocol-D44DCkcs.js");
4
4
  const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
5
5
  let node_url = require("node:url");
6
6
  let node_fs = require("node:fs");
@@ -1,4 +1,4 @@
1
- import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-C1FtUWvH.mjs";
1
+ import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-BzAQPUR-.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import * as fs from "node:fs";
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.67",
21
+ version: "1.2.68",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.67",
39
+ version: "1.2.68",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-BAbvf81B.js");
6
+ const require_dist = require("../dist-CgJ8MRAy.js");
7
7
  const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  node_crypto = require_chunk.__toESM(node_crypto, 1);
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "../chunk-DnnnRqeS.mjs";
2
- import { At as string, Ct as boolean, D as cameraStreamsCapability, Dt as number, Et as literal, F as egressTranscodeSharingKey, I as egressTransportFromRequest, Mt as EventCategory, O as createHwAccelCache, Ot as object, Q as streamBrokerCapability, S as RingBuffer, St as array, V as maskUrlCredentials, Y as resolveEgressDecodeHwAccel, at as invocationFromEncodeProfile, ct as CAM_PROFILE_ORDER, dt as createEvent, et as webrtcSessionCapability, f as EncodeProfileSchema, gt as nodePin, ht as makeSourceBrokerId, it as buildFfmpegArgs$1, jt as union, kt as record, lt as DeviceFeature, mt as makeProfileBrokerId, nt as AUDIO_PRESETS, ot as isSoftwareDecode, pt as isEvent, rt as Fmp4BoxSplitter, st as BaseAddon, tt as errMsg, ut as DeviceType, v as RATE_CONTROL_RELAXED, vt as parseProfileBrokerId, w as addonWidgetsSourceCapability, wt as discriminatedUnion, xt as _enum, y as RATE_CONTROL_TIGHT } from "../dist-Dpr92hOt.mjs";
2
+ import { At as string, Ct as boolean, D as cameraStreamsCapability, Dt as number, Et as literal, F as egressTranscodeSharingKey, I as egressTransportFromRequest, Mt as EventCategory, O as createHwAccelCache, Ot as object, Q as streamBrokerCapability, S as RingBuffer, St as array, V as maskUrlCredentials, Y as resolveEgressDecodeHwAccel, at as invocationFromEncodeProfile, ct as CAM_PROFILE_ORDER, dt as createEvent, et as webrtcSessionCapability, f as EncodeProfileSchema, gt as nodePin, ht as makeSourceBrokerId, it as buildFfmpegArgs$1, jt as union, kt as record, lt as DeviceFeature, mt as makeProfileBrokerId, nt as AUDIO_PRESETS, ot as isSoftwareDecode, pt as isEvent, rt as Fmp4BoxSplitter, st as BaseAddon, tt as errMsg, ut as DeviceType, v as RATE_CONTROL_RELAXED, vt as parseProfileBrokerId, w as addonWidgetsSourceCapability, wt as discriminatedUnion, xt as _enum, y as RATE_CONTROL_TIGHT } from "../dist-CKY0efl3.mjs";
3
3
  import { n as profileForStreamId, r as parseRestreamPath } from "../remote-restream-Ci7RXNGb.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import * as crypto$1 from "node:crypto";
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Cvh5La_s.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVJ4gKkk.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
@@ -1,4 +1,4 @@
1
- import { g as NativeLeaseSettingsSchema, h as NativeLeaseAdmissionSchema, l as DEFAULT_NATIVE_LEASE_SETTINGS } from "./dist-Dpr92hOt.mjs";
1
+ import { g as NativeLeaseSettingsSchema, h as NativeLeaseAdmissionSchema, l as DEFAULT_NATIVE_LEASE_SETTINGS } from "./dist-CKY0efl3.mjs";
2
2
  //#region src/session-decode/native-lease-config.ts
3
3
  /**
4
4
  * Resolution of the native-frame hold / tile knobs for one decode worker.
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-BAbvf81B.js");
1
+ const require_dist = require("./dist-CgJ8MRAy.js");
2
2
  //#region src/session-decode/native-lease-config.ts
3
3
  /**
4
4
  * Resolution of the native-frame hold / tile knobs for one decode worker.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.73",
3
+ "version": "1.2.74",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",