@camstack/addon-pipeline 1.2.293 → 1.2.295

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 (39) hide show
  1. package/THIRD_PARTY_MODELS.md +233 -0
  2. package/dist/audio-analyzer/index.js +2 -2
  3. package/dist/audio-analyzer/index.mjs +2 -2
  4. package/dist/{default-detection-model-DCQlW9fx.mjs → default-detection-model-CAUBVbgK.mjs} +53 -19
  5. package/dist/{default-detection-model-DTKHLLcC.js → default-detection-model-wNEISVA9.js} +58 -18
  6. package/dist/detection-pipeline/index.js +286 -458
  7. package/dist/detection-pipeline/index.mjs +286 -458
  8. package/dist/{dist-Ce8s7XqV.js → dist-BsVcf5wO.js} +1321 -833
  9. package/dist/{dist-C0Fr4hM5.mjs → dist-CuxSNLKW.mjs} +1316 -834
  10. package/dist/motion-wasm/index.js +1 -1
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-Xm7amG2e.mjs → node-Cqb1QiXk.mjs} +1 -1
  13. package/dist/{node-DpOrymbW.js → node-UFk6I2f6.js} +1 -1
  14. package/dist/pipeline-runner/index.js +266 -72
  15. package/dist/pipeline-runner/index.mjs +266 -72
  16. package/dist/{process-memory-CBRjuhyg.js → process-memory-CJV29sPf.js} +1 -1
  17. package/dist/{process-memory-BN5qYGsD.mjs → process-memory-D0Nvs9rr.mjs} +1 -1
  18. package/dist/recorder/index.js +12 -2
  19. package/dist/recorder/index.mjs +12 -2
  20. package/dist/{segment-demux-js-BhMlS_Ow.js → segment-demux-js-DGwmf5hu.js} +1 -1
  21. package/dist/{segment-demux-js-Bhwpj5Wl.mjs → segment-demux-js-DIDWw1iE.mjs} +1 -1
  22. package/dist/stream-broker/_stub.js +2 -2
  23. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DKd7oYba.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B7nBFqva.mjs} +2 -2
  24. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCIyBvRa.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-DoyA71_q.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DeD_UFdb.mjs} +1 -1
  26. package/dist/stream-broker/demux-worker-child.js +1 -1
  27. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  28. package/dist/stream-broker/{hostInit-CCMIHjff.mjs → hostInit-BKlb3qac.mjs} +2 -2
  29. package/dist/stream-broker/index.js +3 -3
  30. package/dist/stream-broker/index.mjs +3 -3
  31. package/dist/stream-broker/remoteEntry.js +1 -1
  32. package/package.json +3 -2
  33. package/python/postprocessors/__init__.py +2 -0
  34. package/python/postprocessors/ssd.py +73 -17
  35. package/python/postprocessors/test_ssd.py +205 -0
  36. package/python/postprocessors/test_yunet.py +292 -0
  37. package/python/postprocessors/testdata/ssdlite_mobiledet_outputs.json +1 -0
  38. package/python/postprocessors/yunet.py +275 -0
  39. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-6IHzlLJ_.mjs +0 -26
@@ -3,11 +3,11 @@ 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-Ce8s7XqV.js");
7
- const require_node = require("../node-DpOrymbW.js");
8
- const require_default_detection_model = require("../default-detection-model-DTKHLLcC.js");
6
+ const require_dist = require("../dist-BsVcf5wO.js");
7
+ const require_node = require("../node-UFk6I2f6.js");
8
+ const require_default_detection_model = require("../default-detection-model-wNEISVA9.js");
9
9
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
10
- const require_process_memory = require("../process-memory-CBRjuhyg.js");
10
+ const require_process_memory = require("../process-memory-CJV29sPf.js");
11
11
  let node_os = require("node:os");
12
12
  node_os = require_chunk.__toESM(node_os);
13
13
  let node_fs = require("node:fs");
@@ -1703,12 +1703,13 @@ function resolveEffectivePostprocessorForEntry(definition, entry) {
1703
1703
  //#region src/detection-pipeline/registry/model-knob-applicability.ts
1704
1704
  /** Decodes that run NMS in the pipeline (and hence consult `nmsIouThreshold`).
1705
1705
  * Mirrors the Python postprocessors that read the knob — yolo.py,
1706
- * yolo_seg.py, rfdetr.py, scrfd.py. */
1706
+ * yolo_seg.py, rfdetr.py, scrfd.py, yunet.py. */
1707
1707
  var NMS_CONSUMING_POSTPROCESSORS = new Set([
1708
1708
  "yolo",
1709
1709
  "yolo-seg",
1710
1710
  "rfdetr",
1711
- "scrfd"
1711
+ "scrfd",
1712
+ "yunet"
1712
1713
  ]);
1713
1714
  /**
1714
1715
  * Does this decode consult `nmsIouThreshold`?
@@ -3165,6 +3166,52 @@ function createInferenceTimeoutGuard(deps) {
3165
3166
  };
3166
3167
  }
3167
3168
  //#endregion
3169
+ //#region src/detection-pipeline/model-pin.ts
3170
+ function requestedModels(steps) {
3171
+ const out = /* @__PURE__ */ new Map();
3172
+ const walk = (nodes) => {
3173
+ for (const step of nodes) {
3174
+ if (!step.enabled) continue;
3175
+ out.set(step.addonId, step.modelId);
3176
+ if (step.children !== void 0) walk(step.children);
3177
+ }
3178
+ };
3179
+ walk(steps);
3180
+ return out;
3181
+ }
3182
+ function checkReplayPin(input) {
3183
+ if (input.requestedDeviceKey !== void 0 && input.dispatchDeviceKey !== input.requestedDeviceKey) return {
3184
+ kind: "refused",
3185
+ reason: "device-not-servable",
3186
+ detail: `device pool ${input.requestedDeviceKey} cannot run this tree (the capability gate would fall back to ${input.dispatchDeviceKey ?? "the node default"})`
3187
+ };
3188
+ const asked = requestedModels(input.requested);
3189
+ for (const step of input.executing) {
3190
+ const want = asked.get(step.addonId);
3191
+ if (want === void 0 || want.length === 0) return {
3192
+ kind: "refused",
3193
+ reason: "model-not-servable",
3194
+ detail: `${step.addonId}: no model pinned by the caller (would run ${step.modelId}, ${input.format})`
3195
+ };
3196
+ if (want !== step.modelId) return {
3197
+ kind: "refused",
3198
+ reason: "model-not-servable",
3199
+ detail: `${step.addonId}: asked for ${want}, this pool would run ${step.modelId} (${input.format})`
3200
+ };
3201
+ }
3202
+ return { kind: "pinned" };
3203
+ }
3204
+ var PRIMARY_ROOT_STEP = "object-detection";
3205
+ /**
3206
+ * The root model a frame result reports: `object-detection` when it runs (it
3207
+ * is the detector the tracks come from), else the first enabled video root.
3208
+ * `undefined` when no root runs — never a fabricated id.
3209
+ */
3210
+ function resolveRootModelId(roots) {
3211
+ const enabled = roots.filter((s) => s.enabled && s.slot !== "audio-classifier");
3212
+ return (enabled.find((s) => s.addonId === PRIMARY_ROOT_STEP) ?? enabled[0])?.modelId;
3213
+ }
3214
+ //#endregion
3168
3215
  //#region src/detection-pipeline/engine-provisioner.ts
3169
3216
  /** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
3170
3217
  var BACKOFF_SCHEDULE_MS = [
@@ -3435,6 +3482,7 @@ var KNOWN_POSTPROCESSORS = new Set([
3435
3482
  "rfdetr",
3436
3483
  "yolo-seg",
3437
3484
  "scrfd",
3485
+ "yunet",
3438
3486
  "arcface",
3439
3487
  "clip",
3440
3488
  "softmax",
@@ -3536,9 +3584,12 @@ var DropSampler = class {
3536
3584
  * accumulates and a summary is released once per interval — carrying the
3537
3585
  * number suppressed, so nothing about the RATE is lost even though the lines
3538
3586
  * are.
3587
+ *
3588
+ * `channel` separates occasions that share a device — a replay's drops are
3589
+ * sampled apart from the live ones.
3539
3590
  */
3540
- record(deviceId, macroClass) {
3541
- const key = `${deviceId}:${macroClass}`;
3591
+ record(deviceId, macroClass, channel = "live") {
3592
+ const key = `${channel}:${deviceId}:${macroClass}`;
3542
3593
  const at = this.now();
3543
3594
  const prev = this.seen.get(key);
3544
3595
  if (prev === void 0) {
@@ -5062,7 +5113,8 @@ function buildFrameResult(input) {
5062
5113
  ...embeddingModelId !== void 0 ? { embeddingModelId } : {}
5063
5114
  } : {},
5064
5115
  ...m.faceAlignedCrop !== void 0 ? { faceAlignedCrop: m.faceAlignedCrop } : {},
5065
- ...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {}
5116
+ ...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {},
5117
+ ...m.landmarks !== void 0 ? { landmarks: m.landmarks } : {}
5066
5118
  };
5067
5119
  if (input.debug) {
5068
5120
  const cleanDebug = pruneUndefined({
@@ -5323,6 +5375,11 @@ var PipelineExecutor = class {
5323
5375
  async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
5324
5376
  const startMs = Date.now();
5325
5377
  const verbosity = runOpts?.traceVerbosity ?? "off";
5378
+ const occasion = runOpts?.occasion ?? "live";
5379
+ const logTags = occasion === "replay" ? {
5380
+ deviceId,
5381
+ occasion
5382
+ } : { deviceId };
5326
5383
  const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
5327
5384
  const firstLevel = [];
5328
5385
  const details = [];
@@ -5338,9 +5395,9 @@ var PipelineExecutor = class {
5338
5395
  idGen,
5339
5396
  details,
5340
5397
  onClassifierClassDropped: (drop) => {
5341
- const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`);
5398
+ const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`, occasion);
5342
5399
  if (sample.logExample) this.opts.logger?.info("classifier winner excluded by class filter — label dropped", {
5343
- tags: { deviceId },
5400
+ tags: logTags,
5344
5401
  meta: {
5345
5402
  step: drop.stepId,
5346
5403
  model: drop.modelId,
@@ -5351,7 +5408,7 @@ var PipelineExecutor = class {
5351
5408
  }
5352
5409
  });
5353
5410
  if (sample.summaryCount !== null) this.opts.logger?.info("classifier class filter: still dropping", {
5354
- tags: { deviceId },
5411
+ tags: logTags,
5355
5412
  meta: {
5356
5413
  step: drop.stepId,
5357
5414
  droppedClass: drop.droppedClass,
@@ -5384,7 +5441,7 @@ var PipelineExecutor = class {
5384
5441
  await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
5385
5442
  } catch (err) {
5386
5443
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
5387
- tags: { deviceId },
5444
+ tags: logTags,
5388
5445
  meta: {
5389
5446
  rootStepId: rootStep.stepId,
5390
5447
  parentClass: mutable.macroClass,
@@ -5438,9 +5495,9 @@ var PipelineExecutor = class {
5438
5495
  const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
5439
5496
  const fullFrameHardArea = rootStep.settings?.["fullFrameGuardHardAreaRatio"];
5440
5497
  if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0, typeof fullFrameHardArea === "number" ? fullFrameHardArea : void 0)) {
5441
- const sample = this.dropSampler.record(deviceId, String(mutable.macroClass));
5498
+ const sample = this.dropSampler.record(deviceId, String(mutable.macroClass), occasion);
5442
5499
  if (sample.summaryCount !== null) this.opts.logger?.info("full-frame phantom guard: still dropping", {
5443
- tags: { deviceId },
5500
+ tags: logTags,
5444
5501
  meta: {
5445
5502
  macroClass: mutable.macroClass,
5446
5503
  suppressed: sample.summaryCount,
@@ -5449,7 +5506,7 @@ var PipelineExecutor = class {
5449
5506
  }
5450
5507
  });
5451
5508
  if (sample.logExample) this.opts.logger?.info("full-frame phantom guard: detection dropped", {
5452
- tags: { deviceId },
5509
+ tags: logTags,
5453
5510
  meta: {
5454
5511
  macroClass: mutable.macroClass,
5455
5512
  score: mutable.score,
@@ -5475,7 +5532,7 @@ var PipelineExecutor = class {
5475
5532
  continue;
5476
5533
  }
5477
5534
  if (isFullFrameSurvivor(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0)) this.opts.logger?.info("full-frame box SURVIVED the guard on score", {
5478
- tags: { deviceId },
5535
+ tags: logTags,
5479
5536
  meta: {
5480
5537
  macroClass: mutable.macroClass,
5481
5538
  score: mutable.score,
@@ -5489,7 +5546,7 @@ var PipelineExecutor = class {
5489
5546
  await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
5490
5547
  } catch (err) {
5491
5548
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
5492
- tags: { deviceId },
5549
+ tags: logTags,
5493
5550
  meta: {
5494
5551
  rootStepId: rootStep.stepId,
5495
5552
  parentClass: mutable.macroClass,
@@ -6547,25 +6604,6 @@ var PROBE_READY_TIMEOUT_MS = 12e4;
6547
6604
  * race bound keeps a wedged transport from wedging `setApi`.
6548
6605
  */
6549
6606
  var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
6550
- /**
6551
- * Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
6552
- * env. Used wherever the old `detectBestEngine()` sync probe fell back — the
6553
- * result is identical (onnx / cpu) but is now derived through the shared rules
6554
- * instead of duplicated inline.
6555
- */
6556
- /**
6557
- * Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
6558
- * coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
6559
- * reject a persisted/global engine choice that the node's PLATFORM fundamentally
6560
- * cannot run (e.g. the cluster's OpenVINO default landing on a Mac) — distinct
6561
- * from the gpu-dependent support (linux without a probed Intel iGPU still keeps
6562
- * openvino as a valid platform choice; the device falls back to cpu).
6563
- */
6564
- function backendPossibleOnPlatform(backend) {
6565
- if (backend === "coreml") return process.platform === "darwin";
6566
- if (backend === "openvino") return process.arch === "x64" && process.platform !== "darwin";
6567
- return true;
6568
- }
6569
6607
  var DetectionPipelineProvider = class DetectionPipelineProvider {
6570
6608
  modelsDir;
6571
6609
  eventBus;
@@ -6705,22 +6743,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6705
6743
  */
6706
6744
  needsAutoPick = false;
6707
6745
  /**
6708
- * Warm cache for benchmark engine-override runs.
6709
- *
6710
- * Each override rebuild costs a full Python pool spin-up (~300-500ms)
6711
- * plus the per-model load. Benchmark tabs iterate up to 800× against
6712
- * the same override (e.g. yolov9s / coreml / all) — paying that spin-up
6713
- * each iteration dwarfs actual inference time.
6714
- *
6715
- * When the same override engine is requested within `OVERRIDE_CACHE_TTL_MS`,
6716
- * we reuse the prior transient factory. Hitting a different override
6717
- * or exceeding the TTL disposes the cache and spins a new factory.
6718
- * The prior "main" factory (`priorFactory`) is still restored so
6719
- * runtime dispatch (camera frames) keeps its own resident engine.
6720
- */
6721
- overrideCache = null;
6722
- overrideCacheTimer = null;
6723
- /**
6724
6746
  * Idle-TTL for a per-device inference pool (multi-device C3). A device pool
6725
6747
  * with no dispatch for this long is disposed and recreated on next use — the
6726
6748
  * memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
@@ -6753,7 +6775,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6753
6775
  error: err instanceof Error ? err.message : String(err)
6754
6776
  } })
6755
6777
  });
6756
- static OVERRIDE_CACHE_TTL_MS = 6e4;
6757
6778
  /**
6758
6779
  * RSS bound + periodic memory telemetry for the Python inference pools
6759
6780
  * (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
@@ -6890,15 +6911,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6890
6911
  /**
6891
6912
  * Lazy-install the pip requirements file matching `engine.backend` into
6892
6913
  * the embedded Python before the inference pool spawns. No-op for
6893
- * `runtime: 'node'`, for backends with no requirements file on disk,
6894
- * or before the addon context has been wired (warm-pool race window).
6914
+ * backends with no requirements file on disk, or before the addon
6915
+ * context has been wired (warm-pool race window).
6895
6916
  *
6896
6917
  * Idempotent — `installPythonRequirements` short-circuits on a hash
6897
6918
  * marker, so back-to-back EngineFactory rebuilds (benchmark overrides,
6898
6919
  * tuning respawns) skip the pip subprocess after the first install.
6899
6920
  */
6900
6921
  async ensureBackendDeps(engine) {
6901
- if (engine.runtime !== "python") return;
6902
6922
  if (!this.addonCtx) return;
6903
6923
  const pythonAddonDir = this.executorOptions.pythonAddonDir;
6904
6924
  if (!pythonAddonDir) return;
@@ -7463,8 +7483,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7463
7483
  * result with a warning logged, same posture as `recomputeConfigIssues`.
7464
7484
  */
7465
7485
  async validatePipeline(input) {
7466
- const format = this.currentEngine.format;
7486
+ let format = this.currentEngine.format;
7467
7487
  try {
7488
+ if (input.deviceKey !== void 0) try {
7489
+ format = resolveDeviceEngine(input.deviceKey).format;
7490
+ } catch (err) {
7491
+ return {
7492
+ ok: false,
7493
+ issues: [{
7494
+ addonId: "*",
7495
+ kind: "unknown-device",
7496
+ detail: require_dist.errMsg(err)
7497
+ }],
7498
+ substitutions: [],
7499
+ format
7500
+ };
7501
+ }
7468
7502
  const steps = input.steps;
7469
7503
  const enabledSteps = this.pruneToEnabledSteps(steps);
7470
7504
  const substitutions = collectSubstitutions(enabledSteps, format);
@@ -7789,7 +7823,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7789
7823
  const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
7790
7824
  const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
7791
7825
  const isRuntime = Boolean(input.frame || input.frameRef);
7792
- const progressToBus = !isRuntime || input.sessionId !== void 0;
7826
+ const progressToBus = !isRuntime && input.replay !== true || input.sessionId !== void 0;
7793
7827
  const emit = (message, extra) => {
7794
7828
  onProgress?.(message);
7795
7829
  if (!progressToBus) return;
@@ -7931,7 +7965,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7931
7965
  const dispatchResolution = this.resolveStepsForDispatch({
7932
7966
  steps: input.steps,
7933
7967
  deviceKey: input.deviceKey,
7934
- engineOverride: input.engine,
7935
7968
  deviceId: input.deviceId,
7936
7969
  plane: input.plane
7937
7970
  });
@@ -7951,322 +7984,150 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7951
7984
  }
7952
7985
  const enabledSteps = flattenSteps(benchmarkSteps);
7953
7986
  emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
7954
- await this.ensureEngineFactory();
7955
- const priorEngine = this.currentEngine;
7956
- const priorFactory = this.engineFactory;
7957
- const priorExecutor = this.executor;
7958
- const runEngine = input.engine ? {
7959
- runtime: input.engine.runtime,
7960
- backend: input.engine.backend,
7961
- format: input.engine.format,
7962
- ...input.engine.device || priorEngine.device ? { device: input.engine.device ?? priorEngine.device } : {}
7963
- } : priorEngine;
7964
- const differs = input.engine && (priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""));
7965
- if (differs) if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
7966
- const cached = this.overrideCache;
7967
- if (cached.initPromise) await cached.initPromise;
7968
- this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
7969
- this.currentEngine = runEngine;
7970
- this.engineFactory = cached.factory;
7971
- this.executor = cached.executor;
7972
- } else {
7973
- if (this.overrideCache) {
7974
- try {
7975
- await this.overrideCache.factory.dispose();
7976
- } catch {}
7977
- this.overrideCache = null;
7978
- }
7979
- this.log.info("Benchmark: engine override", { meta: {
7980
- from: `${priorEngine.runtime}/${priorEngine.backend}`,
7981
- to: `${runEngine.runtime}/${runEngine.backend}`
7982
- } });
7983
- await this.ensureBackendDeps(runEngine);
7984
- const newFactory = new EngineFactory({
7985
- engine: runEngine,
7986
- modelsDir: this.modelsDir,
7987
- logger: this.log.child("engine-override"),
7988
- pythonPath: this.executorOptions.pythonPath ?? "",
7989
- provisioning: this.executorOptions.provisioning,
7990
- resolveCustomModel: this.customModelResolver
7991
- });
7992
- const initPromise = newFactory.initialize([]);
7993
- this.overrideCache = {
7994
- engine: runEngine,
7995
- factory: newFactory,
7996
- executor: null,
7997
- lastUsedMs: Date.now(),
7998
- initPromise
7999
- };
8000
- this.scheduleOverrideCacheEviction();
8001
- try {
8002
- await initPromise;
8003
- this.overrideCache.initPromise = null;
8004
- } catch (err) {
8005
- this.overrideCache = null;
8006
- if (this.overrideCacheTimer) {
8007
- clearTimeout(this.overrideCacheTimer);
8008
- this.overrideCacheTimer = null;
8009
- }
8010
- throw err;
8011
- }
8012
- this.currentEngine = runEngine;
8013
- this.engineFactory = newFactory;
8014
- this.executor = null;
8015
- }
8016
- try {
8017
- const deviceLabel = runEngine.device ?? "default";
8018
- emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}${input.engine ? " (override)" : ""}`, {
8019
- step: "engine",
8020
- engine: {
8021
- runtime: runEngine.runtime,
8022
- backend: runEngine.backend,
8023
- device: deviceLabel
8024
- }
8025
- });
8026
- const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
8027
- const executor = this.executor ?? new PipelineExecutor({
8028
- engineRuntime: runtimeStr,
8029
- logger: this.log
7987
+ const rootModelId = resolveRootModelId(benchmarkSteps);
7988
+ const stampRoot = (r) => rootModelId === void 0 ? r : {
7989
+ ...r,
7990
+ rootModelId
7991
+ };
7992
+ if (input.replay === true) {
7993
+ const verdict = checkReplayPin({
7994
+ requested: input.steps,
7995
+ executing: input.plane === "frame" ? collectFramePlaneSteps(benchmarkSteps, isDetailPlaneStep) : enabledSteps,
7996
+ requestedDeviceKey: input.deviceKey,
7997
+ dispatchDeviceKey,
7998
+ format: dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format
8030
7999
  });
8031
- this.executor = executor;
8032
- const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
8033
- const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8034
- const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
8035
- if (needed.length > 0) {
8036
- this.log.info("Benchmark: models to load", { meta: {
8037
- count: needed.length,
8038
- models: needed.map((s) => `${s.addonId}/${s.modelId}`)
8039
- } });
8040
- for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
8041
- step: s.addonId,
8042
- addonId: s.addonId,
8043
- modelId: s.modelId
8000
+ if (verdict.kind === "refused") {
8001
+ this.log.warn("replay refused — the node cannot run exactly what was pinned", {
8002
+ ...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
8003
+ meta: {
8004
+ reason: verdict.reason,
8005
+ detail: verdict.detail,
8006
+ deviceKey: input.deviceKey
8007
+ }
8044
8008
  });
8045
- await this.ensureModelsForSteps(benchmarkSteps, dispatchFactory, dispatchEngine.format);
8046
- emit(`All models loaded`);
8047
- }
8048
- emit("Running inference...");
8049
- const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
8050
- setupMs = performance.now() - wallT0 - decodeMs;
8051
- const effectiveDeviceId = input.deviceId ?? 0;
8052
- const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
8053
- const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
8054
- const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
8055
- let cropZoneBbox;
8056
- if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
8057
- await this.ensureDeviceProxy(effectiveDeviceId);
8058
- const proxy = this.deviceProxies.get(effectiveDeviceId);
8059
- if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
8060
- }
8061
- let frameViewResolver;
8062
- let rootInputViewProvider;
8063
- if (runtimeFrameRef) {
8064
- frameViewResolver = createRootFrameViewResolver(require_default_detection_model.localFrameRegistry, runtimeFrameRef);
8065
- rootInputViewProvider = async (root, crop) => {
8066
- const view = await frameViewResolver(root, crop ? {
8067
- left: crop[0],
8068
- top: crop[1],
8069
- width: crop[2] - crop[0],
8070
- height: crop[3] - crop[1]
8071
- } : void 0);
8072
- const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
8073
- return {
8074
- input: {
8075
- kind: "jpeg",
8076
- data
8077
- },
8078
- jpegProvider: async () => data,
8079
- width: view.width,
8080
- height: view.height,
8081
- geometry: view.geometry
8082
- };
8083
- };
8084
- }
8085
- let executionSucceeded = false;
8086
- let execution;
8087
- try {
8088
- execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
8089
- traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
8090
- plane: input.plane
8091
- }, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
8092
- executionSucceeded = true;
8093
- } finally {
8094
- frameViewResolver?.release(executionSucceeded ? "success" : "error");
8095
- }
8096
- const { result, trace } = execution;
8097
- if (isRuntime) {
8098
- if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
8099
- type: "device",
8100
- id: trace.deviceId,
8101
- nodeId: "hub"
8102
- }, trace));
8103
- if (effectiveDeviceId > 0) {
8104
- await this.ensureDeviceProxy(effectiveDeviceId);
8105
- return this.gateDetectionsByZoneRules(effectiveDeviceId, result);
8106
- }
8107
- return result;
8108
- }
8109
- for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
8110
- step: t.source,
8111
- addonId: t.source,
8112
- modelId: t.modelId ?? void 0,
8113
- ms: t.ms
8114
- });
8115
- emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
8116
- const wallMs = performance.now() - wallT0;
8117
- const inferMs = result.debug?.totalInferenceMs ?? 0;
8118
- const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
8119
- return {
8120
- ...result,
8121
- debug: {
8122
- ...result.debug,
8123
- decodeMs: Math.round(decodeMs * 100) / 100,
8124
- setupMs: Math.round(setupMs * 100) / 100,
8125
- wallMs: Math.round(wallMs * 100) / 100,
8126
- overheadMs: Math.round(overheadMs * 100) / 100
8127
- }
8128
- };
8129
- } finally {
8130
- if (differs && this.overrideCache) {
8131
- this.overrideCache.executor = this.executor;
8132
- this.overrideCache.lastUsedMs = Date.now();
8133
- this.scheduleOverrideCacheEviction();
8134
- }
8135
- if (differs) {
8136
- this.currentEngine = priorEngine;
8137
- this.engineFactory = priorFactory;
8138
- this.executor = priorExecutor;
8009
+ throw new Error(`${verdict.reason}: ${verdict.detail}`);
8139
8010
  }
8140
8011
  }
8141
- }
8142
- /**
8143
- * Apply a benchmark engine override (mirroring the body of `runPipeline`
8144
- * lines 863-953). Swaps `currentEngine` / `engineFactory` / `executor`
8145
- * to the override's warm factory and returns a restore function the
8146
- * caller MUST invoke in `finally`. When `override` is undefined or
8147
- * matches the current engine, this is a no-op and the restore function
8148
- * does nothing — same shape so callers don't need a conditional path.
8149
- *
8150
- * Used by both `runPipeline` (single-frame benchmark) and
8151
- * `runPipelineBatch` (batched fast path). Without this, the batch
8152
- * path silently ignored `input.engine` and ran the user's override
8153
- * against the addon's saved engine — making the override matrix in
8154
- * the bench UI a no-op for batchSize > 1.
8155
- */
8156
- async applyEngineOverride(override) {
8157
- const priorEngine = this.currentEngine;
8158
- const priorFactory = this.engineFactory;
8159
- const priorExecutor = this.executor;
8160
- if (!override) return () => {};
8161
- if (!backendPossibleOnPlatform(override.backend)) {
8162
- this.log.warn("Engine override impossible on this platform — running on own engine", { meta: {
8163
- override: `${override.runtime}/${override.backend}/${override.device ?? "default"}`,
8164
- platform: process.platform,
8165
- arch: process.arch,
8166
- engine: `${priorEngine.runtime}/${priorEngine.backend}`
8167
- } });
8168
- return () => {};
8169
- }
8170
- const runEngine = {
8171
- runtime: override.runtime,
8172
- backend: override.backend,
8173
- format: override.format,
8174
- ...override.device || priorEngine.device ? { device: override.device ?? priorEngine.device } : {}
8175
- };
8176
- if (!(priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""))) return () => {};
8177
- if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
8178
- const cached = this.overrideCache;
8179
- if (cached.initPromise) await cached.initPromise;
8180
- this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
8181
- this.currentEngine = runEngine;
8182
- this.engineFactory = cached.factory;
8183
- this.executor = cached.executor;
8184
- } else {
8185
- if (this.overrideCache) {
8186
- try {
8187
- await this.overrideCache.factory.dispose();
8188
- } catch {}
8189
- this.overrideCache = null;
8012
+ const appliesCameraGates = isRuntime || input.replay === true;
8013
+ await this.ensureEngineFactory();
8014
+ const runEngine = this.currentEngine;
8015
+ const deviceLabel = runEngine.device ?? "default";
8016
+ emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}`, {
8017
+ step: "engine",
8018
+ engine: {
8019
+ runtime: runEngine.runtime,
8020
+ backend: runEngine.backend,
8021
+ device: deviceLabel
8190
8022
  }
8191
- this.log.info("Benchmark: engine override", { meta: {
8192
- from: `${priorEngine.runtime}/${priorEngine.backend}`,
8193
- to: `${runEngine.runtime}/${runEngine.backend}`
8023
+ });
8024
+ const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
8025
+ const executor = this.executor ?? new PipelineExecutor({
8026
+ engineRuntime: runtimeStr,
8027
+ logger: this.log
8028
+ });
8029
+ this.executor = executor;
8030
+ const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
8031
+ const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8032
+ const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
8033
+ if (needed.length > 0) {
8034
+ this.log.info("Benchmark: models to load", { meta: {
8035
+ count: needed.length,
8036
+ models: needed.map((s) => `${s.addonId}/${s.modelId}`)
8194
8037
  } });
8195
- await this.ensureBackendDeps(runEngine);
8196
- const newFactory = new EngineFactory({
8197
- engine: runEngine,
8198
- modelsDir: this.modelsDir,
8199
- logger: this.log.child("engine-override"),
8200
- pythonPath: this.executorOptions.pythonPath ?? "",
8201
- provisioning: this.executorOptions.provisioning,
8202
- resolveCustomModel: this.customModelResolver
8038
+ for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
8039
+ step: s.addonId,
8040
+ addonId: s.addonId,
8041
+ modelId: s.modelId
8203
8042
  });
8204
- const initPromise = newFactory.initialize([]);
8205
- this.overrideCache = {
8206
- engine: runEngine,
8207
- factory: newFactory,
8208
- executor: null,
8209
- lastUsedMs: Date.now(),
8210
- initPromise
8043
+ await this.ensureModelsForSteps(benchmarkSteps, dispatchFactory, dispatchEngine.format);
8044
+ emit(`All models loaded`);
8045
+ }
8046
+ emit("Running inference...");
8047
+ const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
8048
+ setupMs = performance.now() - wallT0 - decodeMs;
8049
+ const effectiveDeviceId = input.deviceId ?? 0;
8050
+ const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
8051
+ const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
8052
+ const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
8053
+ let cropZoneBbox;
8054
+ if (appliesCameraGates && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
8055
+ await this.ensureDeviceProxy(effectiveDeviceId);
8056
+ const proxy = this.deviceProxies.get(effectiveDeviceId);
8057
+ if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
8058
+ }
8059
+ let frameViewResolver;
8060
+ let rootInputViewProvider;
8061
+ if (runtimeFrameRef) {
8062
+ frameViewResolver = createRootFrameViewResolver(require_default_detection_model.localFrameRegistry, runtimeFrameRef);
8063
+ rootInputViewProvider = async (root, crop) => {
8064
+ const view = await frameViewResolver(root, crop ? {
8065
+ left: crop[0],
8066
+ top: crop[1],
8067
+ width: crop[2] - crop[0],
8068
+ height: crop[3] - crop[1]
8069
+ } : void 0);
8070
+ const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
8071
+ return {
8072
+ input: {
8073
+ kind: "jpeg",
8074
+ data
8075
+ },
8076
+ jpegProvider: async () => data,
8077
+ width: view.width,
8078
+ height: view.height,
8079
+ geometry: view.geometry
8080
+ };
8211
8081
  };
8212
- this.scheduleOverrideCacheEviction();
8213
- try {
8214
- await initPromise;
8215
- this.overrideCache.initPromise = null;
8216
- } catch (err) {
8217
- this.overrideCache = null;
8218
- if (this.overrideCacheTimer) {
8219
- clearTimeout(this.overrideCacheTimer);
8220
- this.overrideCacheTimer = null;
8221
- }
8222
- throw err;
8082
+ }
8083
+ let executionSucceeded = false;
8084
+ let execution;
8085
+ try {
8086
+ execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
8087
+ traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
8088
+ plane: input.plane,
8089
+ occasion: input.replay === true ? "replay" : "live"
8090
+ }, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
8091
+ executionSucceeded = true;
8092
+ } finally {
8093
+ frameViewResolver?.release(executionSucceeded ? "success" : "error");
8094
+ }
8095
+ const { result, trace } = execution;
8096
+ if (isRuntime) {
8097
+ if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
8098
+ type: "device",
8099
+ id: trace.deviceId,
8100
+ nodeId: "hub"
8101
+ }, trace));
8102
+ if (effectiveDeviceId > 0) {
8103
+ await this.ensureDeviceProxy(effectiveDeviceId);
8104
+ return stampRoot(this.gateDetectionsByZoneRules(effectiveDeviceId, result));
8223
8105
  }
8224
- this.currentEngine = runEngine;
8225
- this.engineFactory = newFactory;
8226
- this.executor = null;
8106
+ return stampRoot(result);
8227
8107
  }
8228
- return () => {
8229
- if (this.overrideCache) {
8230
- this.overrideCache.executor = this.executor;
8231
- this.overrideCache.lastUsedMs = Date.now();
8232
- this.scheduleOverrideCacheEviction();
8108
+ for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
8109
+ step: t.source,
8110
+ addonId: t.source,
8111
+ modelId: t.modelId ?? void 0,
8112
+ ms: t.ms
8113
+ });
8114
+ emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
8115
+ const wallMs = performance.now() - wallT0;
8116
+ const inferMs = result.debug?.totalInferenceMs ?? 0;
8117
+ const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
8118
+ const gated = input.replay === true && effectiveDeviceId > 0 ? await this.ensureDeviceProxy(effectiveDeviceId).then(() => this.gateDetectionsByZoneRules(effectiveDeviceId, result)) : result;
8119
+ return {
8120
+ ...stampRoot(gated),
8121
+ debug: {
8122
+ ...gated.debug,
8123
+ decodeMs: Math.round(decodeMs * 100) / 100,
8124
+ setupMs: Math.round(setupMs * 100) / 100,
8125
+ wallMs: Math.round(wallMs * 100) / 100,
8126
+ overheadMs: Math.round(overheadMs * 100) / 100
8233
8127
  }
8234
- this.currentEngine = priorEngine;
8235
- this.engineFactory = priorFactory;
8236
- this.executor = priorExecutor;
8237
8128
  };
8238
8129
  }
8239
8130
  /**
8240
- * Schedule eviction of the override cache after `OVERRIDE_CACHE_TTL_MS`
8241
- * of idleness. Rescheduling resets the timer so an active benchmark
8242
- * keeps its warm factory alive until iterations stop arriving.
8243
- */
8244
- scheduleOverrideCacheEviction() {
8245
- if (this.overrideCacheTimer) clearTimeout(this.overrideCacheTimer);
8246
- this.overrideCacheTimer = setTimeout(() => {
8247
- this.evictOverrideCache("idle TTL");
8248
- }, DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS);
8249
- }
8250
- async evictOverrideCache(reason) {
8251
- if (!this.overrideCache) return;
8252
- const entry = this.overrideCache;
8253
- this.overrideCache = null;
8254
- if (this.overrideCacheTimer) {
8255
- clearTimeout(this.overrideCacheTimer);
8256
- this.overrideCacheTimer = null;
8257
- }
8258
- try {
8259
- await entry.factory.dispose();
8260
- this.log.info("Override cache evicted", { meta: {
8261
- reason,
8262
- engine: `${entry.engine.runtime}/${entry.engine.backend}/${entry.engine.device ?? "default"}`,
8263
- ageMs: Date.now() - entry.lastUsedMs
8264
- } });
8265
- } catch (err) {
8266
- this.log.warn("Override cache dispose failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
8267
- }
8268
- }
8269
- /**
8270
8131
  * Convert PipelineStepInput[] → PipelineDefaultStep[] by enriching from
8271
8132
  * StepDefinitions. This is the LIVE per-camera dispatch path — it runs
8272
8133
  * once per decoded frame (up to detectionFps × N cameras/sec) — so it
@@ -8282,13 +8143,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8282
8143
  * `resolveStepModels` (see its doc comment); a second writer here would
8283
8144
  * corrupt it.
8284
8145
  *
8285
- * `format` defaults to `this.currentEngine.format` (the LIVE per-camera
8286
- * dispatch path never passes it explicitly). Benchmark call sites with a
8287
- * per-run engine override (`input.engine`) MUST pass the override's format
8288
- * explicitly — `this.currentEngine` only gets swapped to the override
8289
- * LATER in `runPipeline`/`runPipelineBatch`, so relying on the default here
8290
- * would resolve models against the node's persisted format instead of the
8291
- * format actually being benchmarked.
8146
+ * `format` defaults to `this.currentEngine.format` (the node-default
8147
+ * pool); a `deviceKey` dispatch passes that device's format explicitly.
8292
8148
  */
8293
8149
  inputStepsToPipelineSteps(steps, format = this.currentEngine.format, engine = {
8294
8150
  backend: this.currentEngine.backend,
@@ -8370,8 +8226,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8370
8226
  * devices — so the whole-tree gate stays.
8371
8227
  *
8372
8228
  * Fallback is strictly NODE-LOCAL and ordered: the node's own selected
8373
- * engine (or the benchmark's explicit `engineOverride`) is the one and only
8374
- * fallback — frames never migrate to another node from here (that is the
8229
+ * engine is the one and only fallback — frames never migrate to another node from here (that is the
8375
8230
  * orchestrator's tier). The fallback is logged ONCE per distinct
8376
8231
  * (deviceKey, steps, format) signature, with `tags: { deviceId }` when the
8377
8232
  * dispatch is camera-scoped.
@@ -8382,12 +8237,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8382
8237
  * configuration must stay loud, not vanish into a silent fallback chain.
8383
8238
  */
8384
8239
  resolveStepsForDispatch(args) {
8385
- const { deviceKey, engineOverride, deviceId } = args;
8386
- const nodeFormat = engineOverride?.format ?? this.currentEngine.format;
8387
- const nodeEngine = engineOverride ? {
8388
- backend: engineOverride.backend,
8389
- device: engineOverride.device ?? null
8390
- } : {
8240
+ const { deviceKey, deviceId } = args;
8241
+ const nodeFormat = this.currentEngine.format;
8242
+ const nodeEngine = {
8391
8243
  backend: this.currentEngine.backend,
8392
8244
  device: this.currentEngine.device ?? null
8393
8245
  };
@@ -8632,12 +8484,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8632
8484
  const dispatchResolution = this.resolveStepsForDispatch({
8633
8485
  steps: input.steps,
8634
8486
  deviceKey: input.deviceKey,
8635
- engineOverride: input.engine,
8636
8487
  deviceId: input.deviceId,
8637
8488
  plane: void 0
8638
8489
  });
8639
8490
  const dispatchDeviceKey = dispatchResolution.deviceKey;
8640
- const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : input.engine?.format ?? this.currentEngine.format;
8491
+ const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format;
8641
8492
  const benchmarkSteps = dispatchResolution.steps;
8642
8493
  const enabledSteps = flattenEnabledVideoSteps(benchmarkSteps);
8643
8494
  if (enabledSteps.length === 0) throw new Error("runPipelineBatch: no enabled steps");
@@ -8658,53 +8509,48 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8658
8509
  const uniformDims = input.frames.every((f) => f.width === firstFrame.width && f.height === firstFrame.height);
8659
8510
  const singleRoot = enabledSteps.length === 1;
8660
8511
  await this.ensureEngineFactory();
8661
- const restoreEngine = await this.applyEngineOverride(input.engine);
8662
- try {
8663
- const factory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8664
- if (!factory) throw new Error("runPipelineBatch: factory not initialised");
8665
- if (enabledSteps.filter((s) => factory.needsPoolUpdate(s)).length > 0) await this.ensureModelsForSteps(benchmarkSteps, factory, resolveFormat);
8666
- const canFastPath = singleRoot && allRaw && uniformDims && factory.supportsBatch();
8667
- this.log.info("runPipelineBatch path decision", { meta: {
8668
- phase: "batch",
8669
- canFastPath,
8670
- singleRoot,
8671
- allRaw,
8672
- uniformDims,
8673
- supportsBatch: factory.supportsBatch()
8674
- } });
8675
- if (!canFastPath) return { results: await Promise.all(input.frames.map((frame) => this.runPipeline({
8676
- steps: input.steps,
8677
- frame,
8678
- deviceId: input.deviceId,
8679
- sessionId: input.sessionId,
8680
- deviceKey: dispatchDeviceKey
8681
- }))) };
8682
- const items = input.frames.map((frame) => ({
8683
- raw: Buffer.from(frame.data),
8684
- width: frame.width,
8685
- height: frame.height,
8686
- format: frame.format
8687
- }));
8688
- this.log.info("runPipelineBatch fast path dispatch", { meta: {
8689
- phase: "batch",
8690
- stepId: rootStep.addonId,
8691
- modelId: rootStep.modelId,
8692
- items: items.length,
8693
- totalRawBytes: items.reduce((s, it) => s + it.raw.length, 0)
8694
- } });
8695
- const start = performance.now();
8696
- const rawResults = await factory.batchInferRaw(rootStep.addonId, items, rootStep.modelId, input.frameId ?? 0);
8697
- const totalMs = performance.now() - start;
8698
- this.log.info("runPipelineBatch fast path returned", { meta: {
8699
- phase: "batch",
8700
- resultCount: rawResults.length,
8701
- ms: Math.round(totalMs)
8702
- } });
8703
- const perFrameMs = totalMs / rawResults.length;
8704
- return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
8705
- } finally {
8706
- restoreEngine();
8707
- }
8512
+ const factory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8513
+ if (!factory) throw new Error("runPipelineBatch: factory not initialised");
8514
+ if (enabledSteps.filter((s) => factory.needsPoolUpdate(s)).length > 0) await this.ensureModelsForSteps(benchmarkSteps, factory, resolveFormat);
8515
+ const canFastPath = singleRoot && allRaw && uniformDims && factory.supportsBatch();
8516
+ this.log.info("runPipelineBatch path decision", { meta: {
8517
+ phase: "batch",
8518
+ canFastPath,
8519
+ singleRoot,
8520
+ allRaw,
8521
+ uniformDims,
8522
+ supportsBatch: factory.supportsBatch()
8523
+ } });
8524
+ if (!canFastPath) return { results: await Promise.all(input.frames.map((frame) => this.runPipeline({
8525
+ steps: input.steps,
8526
+ frame,
8527
+ deviceId: input.deviceId,
8528
+ sessionId: input.sessionId,
8529
+ deviceKey: dispatchDeviceKey
8530
+ }))) };
8531
+ const items = input.frames.map((frame) => ({
8532
+ raw: Buffer.from(frame.data),
8533
+ width: frame.width,
8534
+ height: frame.height,
8535
+ format: frame.format
8536
+ }));
8537
+ this.log.info("runPipelineBatch fast path dispatch", { meta: {
8538
+ phase: "batch",
8539
+ stepId: rootStep.addonId,
8540
+ modelId: rootStep.modelId,
8541
+ items: items.length,
8542
+ totalRawBytes: items.reduce((s, it) => s + it.raw.length, 0)
8543
+ } });
8544
+ const start = performance.now();
8545
+ const rawResults = await factory.batchInferRaw(rootStep.addonId, items, rootStep.modelId, input.frameId ?? 0);
8546
+ const totalMs = performance.now() - start;
8547
+ this.log.info("runPipelineBatch fast path returned", { meta: {
8548
+ phase: "batch",
8549
+ resultCount: rawResults.length,
8550
+ ms: Math.round(totalMs)
8551
+ } });
8552
+ const perFrameMs = totalMs / rawResults.length;
8553
+ return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
8708
8554
  }
8709
8555
  async listReferenceImages() {
8710
8556
  const dir = this.resolveRefImagesDir();
@@ -8809,7 +8655,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8809
8655
  this.poolMemoryGuard = null;
8810
8656
  this.inferenceTimeoutGuard?.stop();
8811
8657
  this.inferenceTimeoutGuard = null;
8812
- await this.evictOverrideCache("shutdown");
8813
8658
  if (this.engineFactory) {
8814
8659
  await this.engineFactory.dispose();
8815
8660
  this.engineFactory = null;
@@ -8891,13 +8736,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8891
8736
  * own runtime's row, so this is not "the node's tuning" — the per-pool truth
8892
8737
  * is on each pool's `Python inference pool` log line and in `listLoadedEngines`.
8893
8738
  */
8894
- async getEffectiveTuning() {
8895
- const p = resolvePoolProvisioning(this.currentEngine, this.executorOptions.provisioning);
8739
+ /**
8740
+ * One pool's provisioning — the device pool named by `deviceKey`, else the
8741
+ * node default. Resolved with the SAME override `resolveDeviceFactory` and
8742
+ * `ensureEngineFactory` build the pool with, so the answer describes the
8743
+ * pool that ran the work (D646).
8744
+ */
8745
+ async getEffectiveTuning(input = {}) {
8746
+ const p = resolvePoolProvisioning(input.deviceKey !== void 0 ? resolveDeviceEngine(input.deviceKey) : this.currentEngine, this.executorOptions.provisioning);
8896
8747
  return {
8748
+ deviceKey: input.deviceKey ?? null,
8749
+ runtime: p.runtime,
8897
8750
  batchMode: p.batchMode,
8898
8751
  windowMs: p.windowMs,
8899
8752
  maxBatchSize: p.maxBatchSize,
8900
- concurrency: p.concurrency
8753
+ concurrency: p.concurrency,
8754
+ numWorkers: p.numWorkers
8901
8755
  };
8902
8756
  }
8903
8757
  /**
@@ -9285,9 +9139,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9285
9139
  }
9286
9140
  async listLoadedEngines() {
9287
9141
  const out = [];
9288
- const overrideFactory = this.overrideCache?.factory ?? null;
9289
- const runtimeFactoryIsOverride = this.engineFactory !== null && this.engineFactory === overrideFactory;
9290
- if (this.engineFactory && !runtimeFactoryIsOverride) {
9142
+ if (this.engineFactory) {
9291
9143
  const eng = this.currentEngine;
9292
9144
  const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"}`;
9293
9145
  const loaded = this.engineFactory.listLoaded();
@@ -9302,22 +9154,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9302
9154
  idleTtlMs: null
9303
9155
  });
9304
9156
  }
9305
- if (this.overrideCache) {
9306
- const eng = this.overrideCache.engine;
9307
- const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"} (warm)`;
9308
- const loaded = this.overrideCache.factory.listLoaded();
9309
- const idleMs = Math.max(0, Date.now() - this.overrideCache.lastUsedMs);
9310
- if (idleMs <= DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS) out.push({
9311
- engineKey,
9312
- engine: eng,
9313
- modelsLoaded: loaded.map((l) => `${l.stepId}/${l.modelId}`),
9314
- inUseByCameras: [],
9315
- kind: "warm-override",
9316
- poolPid: this.overrideCache.factory.getPoolPid(),
9317
- idleMs,
9318
- idleTtlMs: DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS
9319
- });
9320
- }
9321
9157
  for (const [deviceKey, factory] of this.factoriesByDevice) {
9322
9158
  const eng = resolveDeviceEngine(deviceKey);
9323
9159
  const loaded = factory.listLoaded();
@@ -9346,14 +9182,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9346
9182
  return { success: true };
9347
9183
  }
9348
9184
  async killEngine(input) {
9349
- if (this.overrideCache && enginesEqual(this.overrideCache.engine, input.engine)) {
9350
- await this.evictOverrideCache("killEngine");
9351
- this.log.info("Override engine killed", { meta: {
9352
- ...input.engine,
9353
- force: input.force ?? false
9354
- } });
9355
- return { success: true };
9356
- }
9357
9185
  if (!this.engineFactory) return {
9358
9186
  success: false,
9359
9187
  reason: "not loaded"
@@ -9871,7 +9699,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
9871
9699
  /**
9872
9700
  * Embedded Python path resolved once at boot via
9873
9701
  * `ctx.deps.ensurePython()`. Empty string means the download failed
9874
- * or `runtime: 'node'` was selected — the provider's
9702
+ * — the provider's
9875
9703
  * `ensureBackendDeps` and EngineFactory's `initPythonPool` raise a
9876
9704
  * clear error in that case.
9877
9705
  */