@camstack/addon-pipeline 1.2.294 → 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-0dPKRKUD.mjs → default-detection-model-CAUBVbgK.mjs} +107 -54
  5. package/dist/{default-detection-model-D24AJOTn.js → default-detection-model-wNEISVA9.js} +107 -54
  6. package/dist/detection-pipeline/index.js +284 -457
  7. package/dist/detection-pipeline/index.mjs +284 -457
  8. package/dist/{dist-CJR259Xf.js → dist-BsVcf5wO.js} +1310 -832
  9. package/dist/{dist-RXbmRAwP.mjs → dist-CuxSNLKW.mjs} +1305 -833
  10. package/dist/motion-wasm/index.js +1 -1
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-atmRSHPk.mjs → node-Cqb1QiXk.mjs} +1 -1
  13. package/dist/{node-DWg9zbY1.js → node-UFk6I2f6.js} +1 -1
  14. package/dist/pipeline-runner/index.js +189 -70
  15. package/dist/pipeline-runner/index.mjs +189 -70
  16. package/dist/{process-memory-BJUXvTjd.js → process-memory-CJV29sPf.js} +1 -1
  17. package/dist/{process-memory-BgFOHFnx.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-C_fPJub3.js → segment-demux-js-DGwmf5hu.js} +1 -1
  21. package/dist/{segment-demux-js-G7wFpHzn.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-6IyM-BIn.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-BBYHWS3M.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-CJR259Xf.js");
7
- const require_node = require("../node-DWg9zbY1.js");
8
- const require_default_detection_model = require("../default-detection-model-D24AJOTn.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-BJUXvTjd.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) {
@@ -5324,6 +5375,11 @@ var PipelineExecutor = class {
5324
5375
  async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
5325
5376
  const startMs = Date.now();
5326
5377
  const verbosity = runOpts?.traceVerbosity ?? "off";
5378
+ const occasion = runOpts?.occasion ?? "live";
5379
+ const logTags = occasion === "replay" ? {
5380
+ deviceId,
5381
+ occasion
5382
+ } : { deviceId };
5327
5383
  const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
5328
5384
  const firstLevel = [];
5329
5385
  const details = [];
@@ -5339,9 +5395,9 @@ var PipelineExecutor = class {
5339
5395
  idGen,
5340
5396
  details,
5341
5397
  onClassifierClassDropped: (drop) => {
5342
- const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`);
5398
+ const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`, occasion);
5343
5399
  if (sample.logExample) this.opts.logger?.info("classifier winner excluded by class filter — label dropped", {
5344
- tags: { deviceId },
5400
+ tags: logTags,
5345
5401
  meta: {
5346
5402
  step: drop.stepId,
5347
5403
  model: drop.modelId,
@@ -5352,7 +5408,7 @@ var PipelineExecutor = class {
5352
5408
  }
5353
5409
  });
5354
5410
  if (sample.summaryCount !== null) this.opts.logger?.info("classifier class filter: still dropping", {
5355
- tags: { deviceId },
5411
+ tags: logTags,
5356
5412
  meta: {
5357
5413
  step: drop.stepId,
5358
5414
  droppedClass: drop.droppedClass,
@@ -5385,7 +5441,7 @@ var PipelineExecutor = class {
5385
5441
  await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
5386
5442
  } catch (err) {
5387
5443
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
5388
- tags: { deviceId },
5444
+ tags: logTags,
5389
5445
  meta: {
5390
5446
  rootStepId: rootStep.stepId,
5391
5447
  parentClass: mutable.macroClass,
@@ -5439,9 +5495,9 @@ var PipelineExecutor = class {
5439
5495
  const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
5440
5496
  const fullFrameHardArea = rootStep.settings?.["fullFrameGuardHardAreaRatio"];
5441
5497
  if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0, typeof fullFrameHardArea === "number" ? fullFrameHardArea : void 0)) {
5442
- const sample = this.dropSampler.record(deviceId, String(mutable.macroClass));
5498
+ const sample = this.dropSampler.record(deviceId, String(mutable.macroClass), occasion);
5443
5499
  if (sample.summaryCount !== null) this.opts.logger?.info("full-frame phantom guard: still dropping", {
5444
- tags: { deviceId },
5500
+ tags: logTags,
5445
5501
  meta: {
5446
5502
  macroClass: mutable.macroClass,
5447
5503
  suppressed: sample.summaryCount,
@@ -5450,7 +5506,7 @@ var PipelineExecutor = class {
5450
5506
  }
5451
5507
  });
5452
5508
  if (sample.logExample) this.opts.logger?.info("full-frame phantom guard: detection dropped", {
5453
- tags: { deviceId },
5509
+ tags: logTags,
5454
5510
  meta: {
5455
5511
  macroClass: mutable.macroClass,
5456
5512
  score: mutable.score,
@@ -5476,7 +5532,7 @@ var PipelineExecutor = class {
5476
5532
  continue;
5477
5533
  }
5478
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", {
5479
- tags: { deviceId },
5535
+ tags: logTags,
5480
5536
  meta: {
5481
5537
  macroClass: mutable.macroClass,
5482
5538
  score: mutable.score,
@@ -5490,7 +5546,7 @@ var PipelineExecutor = class {
5490
5546
  await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
5491
5547
  } catch (err) {
5492
5548
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
5493
- tags: { deviceId },
5549
+ tags: logTags,
5494
5550
  meta: {
5495
5551
  rootStepId: rootStep.stepId,
5496
5552
  parentClass: mutable.macroClass,
@@ -6548,25 +6604,6 @@ var PROBE_READY_TIMEOUT_MS = 12e4;
6548
6604
  * race bound keeps a wedged transport from wedging `setApi`.
6549
6605
  */
6550
6606
  var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
6551
- /**
6552
- * Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
6553
- * env. Used wherever the old `detectBestEngine()` sync probe fell back — the
6554
- * result is identical (onnx / cpu) but is now derived through the shared rules
6555
- * instead of duplicated inline.
6556
- */
6557
- /**
6558
- * Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
6559
- * coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
6560
- * reject a persisted/global engine choice that the node's PLATFORM fundamentally
6561
- * cannot run (e.g. the cluster's OpenVINO default landing on a Mac) — distinct
6562
- * from the gpu-dependent support (linux without a probed Intel iGPU still keeps
6563
- * openvino as a valid platform choice; the device falls back to cpu).
6564
- */
6565
- function backendPossibleOnPlatform(backend) {
6566
- if (backend === "coreml") return process.platform === "darwin";
6567
- if (backend === "openvino") return process.arch === "x64" && process.platform !== "darwin";
6568
- return true;
6569
- }
6570
6607
  var DetectionPipelineProvider = class DetectionPipelineProvider {
6571
6608
  modelsDir;
6572
6609
  eventBus;
@@ -6706,22 +6743,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6706
6743
  */
6707
6744
  needsAutoPick = false;
6708
6745
  /**
6709
- * Warm cache for benchmark engine-override runs.
6710
- *
6711
- * Each override rebuild costs a full Python pool spin-up (~300-500ms)
6712
- * plus the per-model load. Benchmark tabs iterate up to 800× against
6713
- * the same override (e.g. yolov9s / coreml / all) — paying that spin-up
6714
- * each iteration dwarfs actual inference time.
6715
- *
6716
- * When the same override engine is requested within `OVERRIDE_CACHE_TTL_MS`,
6717
- * we reuse the prior transient factory. Hitting a different override
6718
- * or exceeding the TTL disposes the cache and spins a new factory.
6719
- * The prior "main" factory (`priorFactory`) is still restored so
6720
- * runtime dispatch (camera frames) keeps its own resident engine.
6721
- */
6722
- overrideCache = null;
6723
- overrideCacheTimer = null;
6724
- /**
6725
6746
  * Idle-TTL for a per-device inference pool (multi-device C3). A device pool
6726
6747
  * with no dispatch for this long is disposed and recreated on next use — the
6727
6748
  * memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
@@ -6754,7 +6775,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6754
6775
  error: err instanceof Error ? err.message : String(err)
6755
6776
  } })
6756
6777
  });
6757
- static OVERRIDE_CACHE_TTL_MS = 6e4;
6758
6778
  /**
6759
6779
  * RSS bound + periodic memory telemetry for the Python inference pools
6760
6780
  * (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
@@ -6891,15 +6911,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6891
6911
  /**
6892
6912
  * Lazy-install the pip requirements file matching `engine.backend` into
6893
6913
  * the embedded Python before the inference pool spawns. No-op for
6894
- * `runtime: 'node'`, for backends with no requirements file on disk,
6895
- * 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).
6896
6916
  *
6897
6917
  * Idempotent — `installPythonRequirements` short-circuits on a hash
6898
6918
  * marker, so back-to-back EngineFactory rebuilds (benchmark overrides,
6899
6919
  * tuning respawns) skip the pip subprocess after the first install.
6900
6920
  */
6901
6921
  async ensureBackendDeps(engine) {
6902
- if (engine.runtime !== "python") return;
6903
6922
  if (!this.addonCtx) return;
6904
6923
  const pythonAddonDir = this.executorOptions.pythonAddonDir;
6905
6924
  if (!pythonAddonDir) return;
@@ -7464,8 +7483,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7464
7483
  * result with a warning logged, same posture as `recomputeConfigIssues`.
7465
7484
  */
7466
7485
  async validatePipeline(input) {
7467
- const format = this.currentEngine.format;
7486
+ let format = this.currentEngine.format;
7468
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
+ }
7469
7502
  const steps = input.steps;
7470
7503
  const enabledSteps = this.pruneToEnabledSteps(steps);
7471
7504
  const substitutions = collectSubstitutions(enabledSteps, format);
@@ -7790,7 +7823,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7790
7823
  const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
7791
7824
  const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
7792
7825
  const isRuntime = Boolean(input.frame || input.frameRef);
7793
- const progressToBus = !isRuntime || input.sessionId !== void 0;
7826
+ const progressToBus = !isRuntime && input.replay !== true || input.sessionId !== void 0;
7794
7827
  const emit = (message, extra) => {
7795
7828
  onProgress?.(message);
7796
7829
  if (!progressToBus) return;
@@ -7932,7 +7965,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7932
7965
  const dispatchResolution = this.resolveStepsForDispatch({
7933
7966
  steps: input.steps,
7934
7967
  deviceKey: input.deviceKey,
7935
- engineOverride: input.engine,
7936
7968
  deviceId: input.deviceId,
7937
7969
  plane: input.plane
7938
7970
  });
@@ -7952,322 +7984,150 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7952
7984
  }
7953
7985
  const enabledSteps = flattenSteps(benchmarkSteps);
7954
7986
  emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
7955
- await this.ensureEngineFactory();
7956
- const priorEngine = this.currentEngine;
7957
- const priorFactory = this.engineFactory;
7958
- const priorExecutor = this.executor;
7959
- const runEngine = input.engine ? {
7960
- runtime: input.engine.runtime,
7961
- backend: input.engine.backend,
7962
- format: input.engine.format,
7963
- ...input.engine.device || priorEngine.device ? { device: input.engine.device ?? priorEngine.device } : {}
7964
- } : priorEngine;
7965
- const differs = input.engine && (priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""));
7966
- if (differs) if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
7967
- const cached = this.overrideCache;
7968
- if (cached.initPromise) await cached.initPromise;
7969
- this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
7970
- this.currentEngine = runEngine;
7971
- this.engineFactory = cached.factory;
7972
- this.executor = cached.executor;
7973
- } else {
7974
- if (this.overrideCache) {
7975
- try {
7976
- await this.overrideCache.factory.dispose();
7977
- } catch {}
7978
- this.overrideCache = null;
7979
- }
7980
- this.log.info("Benchmark: engine override", { meta: {
7981
- from: `${priorEngine.runtime}/${priorEngine.backend}`,
7982
- to: `${runEngine.runtime}/${runEngine.backend}`
7983
- } });
7984
- await this.ensureBackendDeps(runEngine);
7985
- const newFactory = new EngineFactory({
7986
- engine: runEngine,
7987
- modelsDir: this.modelsDir,
7988
- logger: this.log.child("engine-override"),
7989
- pythonPath: this.executorOptions.pythonPath ?? "",
7990
- provisioning: this.executorOptions.provisioning,
7991
- resolveCustomModel: this.customModelResolver
7992
- });
7993
- const initPromise = newFactory.initialize([]);
7994
- this.overrideCache = {
7995
- engine: runEngine,
7996
- factory: newFactory,
7997
- executor: null,
7998
- lastUsedMs: Date.now(),
7999
- initPromise
8000
- };
8001
- this.scheduleOverrideCacheEviction();
8002
- try {
8003
- await initPromise;
8004
- this.overrideCache.initPromise = null;
8005
- } catch (err) {
8006
- this.overrideCache = null;
8007
- if (this.overrideCacheTimer) {
8008
- clearTimeout(this.overrideCacheTimer);
8009
- this.overrideCacheTimer = null;
8010
- }
8011
- throw err;
8012
- }
8013
- this.currentEngine = runEngine;
8014
- this.engineFactory = newFactory;
8015
- this.executor = null;
8016
- }
8017
- try {
8018
- const deviceLabel = runEngine.device ?? "default";
8019
- emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}${input.engine ? " (override)" : ""}`, {
8020
- step: "engine",
8021
- engine: {
8022
- runtime: runEngine.runtime,
8023
- backend: runEngine.backend,
8024
- device: deviceLabel
8025
- }
8026
- });
8027
- const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
8028
- const executor = this.executor ?? new PipelineExecutor({
8029
- engineRuntime: runtimeStr,
8030
- 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
8031
7999
  });
8032
- this.executor = executor;
8033
- const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
8034
- const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8035
- const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
8036
- if (needed.length > 0) {
8037
- this.log.info("Benchmark: models to load", { meta: {
8038
- count: needed.length,
8039
- models: needed.map((s) => `${s.addonId}/${s.modelId}`)
8040
- } });
8041
- for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
8042
- step: s.addonId,
8043
- addonId: s.addonId,
8044
- 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
+ }
8045
8008
  });
8046
- await this.ensureModelsForSteps(benchmarkSteps, dispatchFactory, dispatchEngine.format);
8047
- emit(`All models loaded`);
8048
- }
8049
- emit("Running inference...");
8050
- const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
8051
- setupMs = performance.now() - wallT0 - decodeMs;
8052
- const effectiveDeviceId = input.deviceId ?? 0;
8053
- const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
8054
- const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
8055
- const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
8056
- let cropZoneBbox;
8057
- if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
8058
- await this.ensureDeviceProxy(effectiveDeviceId);
8059
- const proxy = this.deviceProxies.get(effectiveDeviceId);
8060
- if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
8061
- }
8062
- let frameViewResolver;
8063
- let rootInputViewProvider;
8064
- if (runtimeFrameRef) {
8065
- frameViewResolver = createRootFrameViewResolver(require_default_detection_model.localFrameRegistry, runtimeFrameRef);
8066
- rootInputViewProvider = async (root, crop) => {
8067
- const view = await frameViewResolver(root, crop ? {
8068
- left: crop[0],
8069
- top: crop[1],
8070
- width: crop[2] - crop[0],
8071
- height: crop[3] - crop[1]
8072
- } : void 0);
8073
- const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
8074
- return {
8075
- input: {
8076
- kind: "jpeg",
8077
- data
8078
- },
8079
- jpegProvider: async () => data,
8080
- width: view.width,
8081
- height: view.height,
8082
- geometry: view.geometry
8083
- };
8084
- };
8085
- }
8086
- let executionSucceeded = false;
8087
- let execution;
8088
- try {
8089
- execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
8090
- traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
8091
- plane: input.plane
8092
- }, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
8093
- executionSucceeded = true;
8094
- } finally {
8095
- frameViewResolver?.release(executionSucceeded ? "success" : "error");
8096
- }
8097
- const { result, trace } = execution;
8098
- if (isRuntime) {
8099
- if (trace && this.eventBus) this.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.PipelineTrace, {
8100
- type: "device",
8101
- id: trace.deviceId,
8102
- nodeId: "hub"
8103
- }, trace));
8104
- if (effectiveDeviceId > 0) {
8105
- await this.ensureDeviceProxy(effectiveDeviceId);
8106
- return this.gateDetectionsByZoneRules(effectiveDeviceId, result);
8107
- }
8108
- return result;
8109
- }
8110
- for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
8111
- step: t.source,
8112
- addonId: t.source,
8113
- modelId: t.modelId ?? void 0,
8114
- ms: t.ms
8115
- });
8116
- emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
8117
- const wallMs = performance.now() - wallT0;
8118
- const inferMs = result.debug?.totalInferenceMs ?? 0;
8119
- const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
8120
- return {
8121
- ...result,
8122
- debug: {
8123
- ...result.debug,
8124
- decodeMs: Math.round(decodeMs * 100) / 100,
8125
- setupMs: Math.round(setupMs * 100) / 100,
8126
- wallMs: Math.round(wallMs * 100) / 100,
8127
- overheadMs: Math.round(overheadMs * 100) / 100
8128
- }
8129
- };
8130
- } finally {
8131
- if (differs && this.overrideCache) {
8132
- this.overrideCache.executor = this.executor;
8133
- this.overrideCache.lastUsedMs = Date.now();
8134
- this.scheduleOverrideCacheEviction();
8135
- }
8136
- if (differs) {
8137
- this.currentEngine = priorEngine;
8138
- this.engineFactory = priorFactory;
8139
- this.executor = priorExecutor;
8009
+ throw new Error(`${verdict.reason}: ${verdict.detail}`);
8140
8010
  }
8141
8011
  }
8142
- }
8143
- /**
8144
- * Apply a benchmark engine override (mirroring the body of `runPipeline`
8145
- * lines 863-953). Swaps `currentEngine` / `engineFactory` / `executor`
8146
- * to the override's warm factory and returns a restore function the
8147
- * caller MUST invoke in `finally`. When `override` is undefined or
8148
- * matches the current engine, this is a no-op and the restore function
8149
- * does nothing — same shape so callers don't need a conditional path.
8150
- *
8151
- * Used by both `runPipeline` (single-frame benchmark) and
8152
- * `runPipelineBatch` (batched fast path). Without this, the batch
8153
- * path silently ignored `input.engine` and ran the user's override
8154
- * against the addon's saved engine — making the override matrix in
8155
- * the bench UI a no-op for batchSize > 1.
8156
- */
8157
- async applyEngineOverride(override) {
8158
- const priorEngine = this.currentEngine;
8159
- const priorFactory = this.engineFactory;
8160
- const priorExecutor = this.executor;
8161
- if (!override) return () => {};
8162
- if (!backendPossibleOnPlatform(override.backend)) {
8163
- this.log.warn("Engine override impossible on this platform — running on own engine", { meta: {
8164
- override: `${override.runtime}/${override.backend}/${override.device ?? "default"}`,
8165
- platform: process.platform,
8166
- arch: process.arch,
8167
- engine: `${priorEngine.runtime}/${priorEngine.backend}`
8168
- } });
8169
- return () => {};
8170
- }
8171
- const runEngine = {
8172
- runtime: override.runtime,
8173
- backend: override.backend,
8174
- format: override.format,
8175
- ...override.device || priorEngine.device ? { device: override.device ?? priorEngine.device } : {}
8176
- };
8177
- if (!(priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""))) return () => {};
8178
- if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
8179
- const cached = this.overrideCache;
8180
- if (cached.initPromise) await cached.initPromise;
8181
- this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
8182
- this.currentEngine = runEngine;
8183
- this.engineFactory = cached.factory;
8184
- this.executor = cached.executor;
8185
- } else {
8186
- if (this.overrideCache) {
8187
- try {
8188
- await this.overrideCache.factory.dispose();
8189
- } catch {}
8190
- 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
8191
8022
  }
8192
- this.log.info("Benchmark: engine override", { meta: {
8193
- from: `${priorEngine.runtime}/${priorEngine.backend}`,
8194
- 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}`)
8195
8037
  } });
8196
- await this.ensureBackendDeps(runEngine);
8197
- const newFactory = new EngineFactory({
8198
- engine: runEngine,
8199
- modelsDir: this.modelsDir,
8200
- logger: this.log.child("engine-override"),
8201
- pythonPath: this.executorOptions.pythonPath ?? "",
8202
- provisioning: this.executorOptions.provisioning,
8203
- 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
8204
8042
  });
8205
- const initPromise = newFactory.initialize([]);
8206
- this.overrideCache = {
8207
- engine: runEngine,
8208
- factory: newFactory,
8209
- executor: null,
8210
- lastUsedMs: Date.now(),
8211
- 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
+ };
8212
8081
  };
8213
- this.scheduleOverrideCacheEviction();
8214
- try {
8215
- await initPromise;
8216
- this.overrideCache.initPromise = null;
8217
- } catch (err) {
8218
- this.overrideCache = null;
8219
- if (this.overrideCacheTimer) {
8220
- clearTimeout(this.overrideCacheTimer);
8221
- this.overrideCacheTimer = null;
8222
- }
8223
- 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));
8224
8105
  }
8225
- this.currentEngine = runEngine;
8226
- this.engineFactory = newFactory;
8227
- this.executor = null;
8106
+ return stampRoot(result);
8228
8107
  }
8229
- return () => {
8230
- if (this.overrideCache) {
8231
- this.overrideCache.executor = this.executor;
8232
- this.overrideCache.lastUsedMs = Date.now();
8233
- 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
8234
8127
  }
8235
- this.currentEngine = priorEngine;
8236
- this.engineFactory = priorFactory;
8237
- this.executor = priorExecutor;
8238
8128
  };
8239
8129
  }
8240
8130
  /**
8241
- * Schedule eviction of the override cache after `OVERRIDE_CACHE_TTL_MS`
8242
- * of idleness. Rescheduling resets the timer so an active benchmark
8243
- * keeps its warm factory alive until iterations stop arriving.
8244
- */
8245
- scheduleOverrideCacheEviction() {
8246
- if (this.overrideCacheTimer) clearTimeout(this.overrideCacheTimer);
8247
- this.overrideCacheTimer = setTimeout(() => {
8248
- this.evictOverrideCache("idle TTL");
8249
- }, DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS);
8250
- }
8251
- async evictOverrideCache(reason) {
8252
- if (!this.overrideCache) return;
8253
- const entry = this.overrideCache;
8254
- this.overrideCache = null;
8255
- if (this.overrideCacheTimer) {
8256
- clearTimeout(this.overrideCacheTimer);
8257
- this.overrideCacheTimer = null;
8258
- }
8259
- try {
8260
- await entry.factory.dispose();
8261
- this.log.info("Override cache evicted", { meta: {
8262
- reason,
8263
- engine: `${entry.engine.runtime}/${entry.engine.backend}/${entry.engine.device ?? "default"}`,
8264
- ageMs: Date.now() - entry.lastUsedMs
8265
- } });
8266
- } catch (err) {
8267
- this.log.warn("Override cache dispose failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
8268
- }
8269
- }
8270
- /**
8271
8131
  * Convert PipelineStepInput[] → PipelineDefaultStep[] by enriching from
8272
8132
  * StepDefinitions. This is the LIVE per-camera dispatch path — it runs
8273
8133
  * once per decoded frame (up to detectionFps × N cameras/sec) — so it
@@ -8283,13 +8143,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8283
8143
  * `resolveStepModels` (see its doc comment); a second writer here would
8284
8144
  * corrupt it.
8285
8145
  *
8286
- * `format` defaults to `this.currentEngine.format` (the LIVE per-camera
8287
- * dispatch path never passes it explicitly). Benchmark call sites with a
8288
- * per-run engine override (`input.engine`) MUST pass the override's format
8289
- * explicitly — `this.currentEngine` only gets swapped to the override
8290
- * LATER in `runPipeline`/`runPipelineBatch`, so relying on the default here
8291
- * would resolve models against the node's persisted format instead of the
8292
- * 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.
8293
8148
  */
8294
8149
  inputStepsToPipelineSteps(steps, format = this.currentEngine.format, engine = {
8295
8150
  backend: this.currentEngine.backend,
@@ -8371,8 +8226,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8371
8226
  * devices — so the whole-tree gate stays.
8372
8227
  *
8373
8228
  * Fallback is strictly NODE-LOCAL and ordered: the node's own selected
8374
- * engine (or the benchmark's explicit `engineOverride`) is the one and only
8375
- * 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
8376
8230
  * orchestrator's tier). The fallback is logged ONCE per distinct
8377
8231
  * (deviceKey, steps, format) signature, with `tags: { deviceId }` when the
8378
8232
  * dispatch is camera-scoped.
@@ -8383,12 +8237,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8383
8237
  * configuration must stay loud, not vanish into a silent fallback chain.
8384
8238
  */
8385
8239
  resolveStepsForDispatch(args) {
8386
- const { deviceKey, engineOverride, deviceId } = args;
8387
- const nodeFormat = engineOverride?.format ?? this.currentEngine.format;
8388
- const nodeEngine = engineOverride ? {
8389
- backend: engineOverride.backend,
8390
- device: engineOverride.device ?? null
8391
- } : {
8240
+ const { deviceKey, deviceId } = args;
8241
+ const nodeFormat = this.currentEngine.format;
8242
+ const nodeEngine = {
8392
8243
  backend: this.currentEngine.backend,
8393
8244
  device: this.currentEngine.device ?? null
8394
8245
  };
@@ -8633,12 +8484,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8633
8484
  const dispatchResolution = this.resolveStepsForDispatch({
8634
8485
  steps: input.steps,
8635
8486
  deviceKey: input.deviceKey,
8636
- engineOverride: input.engine,
8637
8487
  deviceId: input.deviceId,
8638
8488
  plane: void 0
8639
8489
  });
8640
8490
  const dispatchDeviceKey = dispatchResolution.deviceKey;
8641
- const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : input.engine?.format ?? this.currentEngine.format;
8491
+ const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format;
8642
8492
  const benchmarkSteps = dispatchResolution.steps;
8643
8493
  const enabledSteps = flattenEnabledVideoSteps(benchmarkSteps);
8644
8494
  if (enabledSteps.length === 0) throw new Error("runPipelineBatch: no enabled steps");
@@ -8659,53 +8509,48 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8659
8509
  const uniformDims = input.frames.every((f) => f.width === firstFrame.width && f.height === firstFrame.height);
8660
8510
  const singleRoot = enabledSteps.length === 1;
8661
8511
  await this.ensureEngineFactory();
8662
- const restoreEngine = await this.applyEngineOverride(input.engine);
8663
- try {
8664
- const factory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8665
- if (!factory) throw new Error("runPipelineBatch: factory not initialised");
8666
- if (enabledSteps.filter((s) => factory.needsPoolUpdate(s)).length > 0) await this.ensureModelsForSteps(benchmarkSteps, factory, resolveFormat);
8667
- const canFastPath = singleRoot && allRaw && uniformDims && factory.supportsBatch();
8668
- this.log.info("runPipelineBatch path decision", { meta: {
8669
- phase: "batch",
8670
- canFastPath,
8671
- singleRoot,
8672
- allRaw,
8673
- uniformDims,
8674
- supportsBatch: factory.supportsBatch()
8675
- } });
8676
- if (!canFastPath) return { results: await Promise.all(input.frames.map((frame) => this.runPipeline({
8677
- steps: input.steps,
8678
- frame,
8679
- deviceId: input.deviceId,
8680
- sessionId: input.sessionId,
8681
- deviceKey: dispatchDeviceKey
8682
- }))) };
8683
- const items = input.frames.map((frame) => ({
8684
- raw: Buffer.from(frame.data),
8685
- width: frame.width,
8686
- height: frame.height,
8687
- format: frame.format
8688
- }));
8689
- this.log.info("runPipelineBatch fast path dispatch", { meta: {
8690
- phase: "batch",
8691
- stepId: rootStep.addonId,
8692
- modelId: rootStep.modelId,
8693
- items: items.length,
8694
- totalRawBytes: items.reduce((s, it) => s + it.raw.length, 0)
8695
- } });
8696
- const start = performance.now();
8697
- const rawResults = await factory.batchInferRaw(rootStep.addonId, items, rootStep.modelId, input.frameId ?? 0);
8698
- const totalMs = performance.now() - start;
8699
- this.log.info("runPipelineBatch fast path returned", { meta: {
8700
- phase: "batch",
8701
- resultCount: rawResults.length,
8702
- ms: Math.round(totalMs)
8703
- } });
8704
- const perFrameMs = totalMs / rawResults.length;
8705
- return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
8706
- } finally {
8707
- restoreEngine();
8708
- }
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)) };
8709
8554
  }
8710
8555
  async listReferenceImages() {
8711
8556
  const dir = this.resolveRefImagesDir();
@@ -8810,7 +8655,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8810
8655
  this.poolMemoryGuard = null;
8811
8656
  this.inferenceTimeoutGuard?.stop();
8812
8657
  this.inferenceTimeoutGuard = null;
8813
- await this.evictOverrideCache("shutdown");
8814
8658
  if (this.engineFactory) {
8815
8659
  await this.engineFactory.dispose();
8816
8660
  this.engineFactory = null;
@@ -8892,13 +8736,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8892
8736
  * own runtime's row, so this is not "the node's tuning" — the per-pool truth
8893
8737
  * is on each pool's `Python inference pool` log line and in `listLoadedEngines`.
8894
8738
  */
8895
- async getEffectiveTuning() {
8896
- 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);
8897
8747
  return {
8748
+ deviceKey: input.deviceKey ?? null,
8749
+ runtime: p.runtime,
8898
8750
  batchMode: p.batchMode,
8899
8751
  windowMs: p.windowMs,
8900
8752
  maxBatchSize: p.maxBatchSize,
8901
- concurrency: p.concurrency
8753
+ concurrency: p.concurrency,
8754
+ numWorkers: p.numWorkers
8902
8755
  };
8903
8756
  }
8904
8757
  /**
@@ -9286,9 +9139,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9286
9139
  }
9287
9140
  async listLoadedEngines() {
9288
9141
  const out = [];
9289
- const overrideFactory = this.overrideCache?.factory ?? null;
9290
- const runtimeFactoryIsOverride = this.engineFactory !== null && this.engineFactory === overrideFactory;
9291
- if (this.engineFactory && !runtimeFactoryIsOverride) {
9142
+ if (this.engineFactory) {
9292
9143
  const eng = this.currentEngine;
9293
9144
  const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"}`;
9294
9145
  const loaded = this.engineFactory.listLoaded();
@@ -9303,22 +9154,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9303
9154
  idleTtlMs: null
9304
9155
  });
9305
9156
  }
9306
- if (this.overrideCache) {
9307
- const eng = this.overrideCache.engine;
9308
- const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"} (warm)`;
9309
- const loaded = this.overrideCache.factory.listLoaded();
9310
- const idleMs = Math.max(0, Date.now() - this.overrideCache.lastUsedMs);
9311
- if (idleMs <= DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS) out.push({
9312
- engineKey,
9313
- engine: eng,
9314
- modelsLoaded: loaded.map((l) => `${l.stepId}/${l.modelId}`),
9315
- inUseByCameras: [],
9316
- kind: "warm-override",
9317
- poolPid: this.overrideCache.factory.getPoolPid(),
9318
- idleMs,
9319
- idleTtlMs: DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS
9320
- });
9321
- }
9322
9157
  for (const [deviceKey, factory] of this.factoriesByDevice) {
9323
9158
  const eng = resolveDeviceEngine(deviceKey);
9324
9159
  const loaded = factory.listLoaded();
@@ -9347,14 +9182,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9347
9182
  return { success: true };
9348
9183
  }
9349
9184
  async killEngine(input) {
9350
- if (this.overrideCache && enginesEqual(this.overrideCache.engine, input.engine)) {
9351
- await this.evictOverrideCache("killEngine");
9352
- this.log.info("Override engine killed", { meta: {
9353
- ...input.engine,
9354
- force: input.force ?? false
9355
- } });
9356
- return { success: true };
9357
- }
9358
9185
  if (!this.engineFactory) return {
9359
9186
  success: false,
9360
9187
  reason: "not loaded"
@@ -9872,7 +9699,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
9872
9699
  /**
9873
9700
  * Embedded Python path resolved once at boot via
9874
9701
  * `ctx.deps.ensurePython()`. Empty string means the download failed
9875
- * or `runtime: 'node'` was selected — the provider's
9702
+ * — the provider's
9876
9703
  * `ensureBackendDeps` and EngineFactory's `initPythonPool` raise a
9877
9704
  * clear error in that case.
9878
9705
  */