@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
@@ -1,9 +1,9 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { $ as defaultDeviceFor$1, $t as errMsg, A as PoolMemoryWatchdog, En as array, Et as overlayClusterStepSettings, Fn as string, Gt as runtimeDevices$1, In as union, Ln as EventCategory, Mn as object, Mt as pipelineExecutorCapability, Ot as pickClusterStepModels, Ut as resolvePoolMemoryPolicy, V as YAMNET_TO_MACRO, Xt as supportedRuntimes$1, _t as loadContributionCapability, bn as nodePin, ct as evaluateZoneRules, f as DEFAULT_CLUSTER_STEP_MODELS, fn as createEvent, gn as hydrateSchema, ht as inferModelProvider, kt as pickClusterStepSettings, on as BaseAddon, ot as enumerateInferenceDevices, p as DEFAULT_CLUSTER_STEP_SETTINGS, rt as detectionPipelineCapability, t as APPLE_SA_TO_MACRO, v as DEVICE_BACKEND_TO_FORMAT, wn as sleep, xn as parseJsonUnknown, zt as resolveClusterStepModelId } from "../dist-C0Fr4hM5.mjs";
3
- import { s as readProcessCost } from "../node-Xm7amG2e.mjs";
4
- import { a as localFrameRegistry, c as ALL_STEPS, d as getStepDefinition, f as resolveModelForFormat, l as getDefaultModelForFormat, n as resolveDefaultDetectionModel, p as landmarkPrecisionVerdict, r as macroGateVerdict, s as ALL_PIPELINE_STEPS, u as getStep } from "../default-detection-model-DCQlW9fx.mjs";
2
+ import { At as pickClusterStepSettings, Bt as resolveClusterStepModelId, Dn as array, Dt as overlayClusterStepSettings, H as YAMNET_TO_MACRO, In as string, Kt as runtimeDevices$1, Ln as union, Nn as object, Nt as pipelineExecutorCapability, Rn as EventCategory, Sn as parseJsonUnknown, Tn as sleep, Wt as resolvePoolMemoryPolicy, Zt as supportedRuntimes$1, _n as hydrateSchema, en as errMsg, et as defaultDeviceFor$1, gt as inferModelProvider, it as detectionPipelineCapability, j as PoolMemoryWatchdog, kt as pickClusterStepModels, lt as evaluateZoneRules, m as DEFAULT_CLUSTER_STEP_SETTINGS, p as DEFAULT_CLUSTER_STEP_MODELS, pn as createEvent, sn as BaseAddon, st as enumerateInferenceDevices, t as APPLE_SA_TO_MACRO, vt as loadContributionCapability, xn as nodePin, y as DEVICE_BACKEND_TO_FORMAT } from "../dist-CuxSNLKW.mjs";
3
+ import { s as readProcessCost } from "../node-Cqb1QiXk.mjs";
4
+ import { a as localFrameRegistry, c as ALL_STEPS, d as getStepDefinition, f as resolveModelForFormat, l as getDefaultModelForFormat, m as landmarkPrecisionVerdict, n as resolveDefaultDetectionModel, r as macroGateVerdict, s as ALL_PIPELINE_STEPS, u as getStep } from "../default-detection-model-CAUBVbgK.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
6
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BN5qYGsD.mjs";
6
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-D0Nvs9rr.mjs";
7
7
  import * as os from "node:os";
8
8
  import * as fs from "node:fs";
9
9
  import * as path$1 from "node:path";
@@ -1696,12 +1696,13 @@ function resolveEffectivePostprocessorForEntry(definition, entry) {
1696
1696
  //#region src/detection-pipeline/registry/model-knob-applicability.ts
1697
1697
  /** Decodes that run NMS in the pipeline (and hence consult `nmsIouThreshold`).
1698
1698
  * Mirrors the Python postprocessors that read the knob — yolo.py,
1699
- * yolo_seg.py, rfdetr.py, scrfd.py. */
1699
+ * yolo_seg.py, rfdetr.py, scrfd.py, yunet.py. */
1700
1700
  var NMS_CONSUMING_POSTPROCESSORS = new Set([
1701
1701
  "yolo",
1702
1702
  "yolo-seg",
1703
1703
  "rfdetr",
1704
- "scrfd"
1704
+ "scrfd",
1705
+ "yunet"
1705
1706
  ]);
1706
1707
  /**
1707
1708
  * Does this decode consult `nmsIouThreshold`?
@@ -3158,6 +3159,52 @@ function createInferenceTimeoutGuard(deps) {
3158
3159
  };
3159
3160
  }
3160
3161
  //#endregion
3162
+ //#region src/detection-pipeline/model-pin.ts
3163
+ function requestedModels(steps) {
3164
+ const out = /* @__PURE__ */ new Map();
3165
+ const walk = (nodes) => {
3166
+ for (const step of nodes) {
3167
+ if (!step.enabled) continue;
3168
+ out.set(step.addonId, step.modelId);
3169
+ if (step.children !== void 0) walk(step.children);
3170
+ }
3171
+ };
3172
+ walk(steps);
3173
+ return out;
3174
+ }
3175
+ function checkReplayPin(input) {
3176
+ if (input.requestedDeviceKey !== void 0 && input.dispatchDeviceKey !== input.requestedDeviceKey) return {
3177
+ kind: "refused",
3178
+ reason: "device-not-servable",
3179
+ detail: `device pool ${input.requestedDeviceKey} cannot run this tree (the capability gate would fall back to ${input.dispatchDeviceKey ?? "the node default"})`
3180
+ };
3181
+ const asked = requestedModels(input.requested);
3182
+ for (const step of input.executing) {
3183
+ const want = asked.get(step.addonId);
3184
+ if (want === void 0 || want.length === 0) return {
3185
+ kind: "refused",
3186
+ reason: "model-not-servable",
3187
+ detail: `${step.addonId}: no model pinned by the caller (would run ${step.modelId}, ${input.format})`
3188
+ };
3189
+ if (want !== step.modelId) return {
3190
+ kind: "refused",
3191
+ reason: "model-not-servable",
3192
+ detail: `${step.addonId}: asked for ${want}, this pool would run ${step.modelId} (${input.format})`
3193
+ };
3194
+ }
3195
+ return { kind: "pinned" };
3196
+ }
3197
+ var PRIMARY_ROOT_STEP = "object-detection";
3198
+ /**
3199
+ * The root model a frame result reports: `object-detection` when it runs (it
3200
+ * is the detector the tracks come from), else the first enabled video root.
3201
+ * `undefined` when no root runs — never a fabricated id.
3202
+ */
3203
+ function resolveRootModelId(roots) {
3204
+ const enabled = roots.filter((s) => s.enabled && s.slot !== "audio-classifier");
3205
+ return (enabled.find((s) => s.addonId === PRIMARY_ROOT_STEP) ?? enabled[0])?.modelId;
3206
+ }
3207
+ //#endregion
3161
3208
  //#region src/detection-pipeline/engine-provisioner.ts
3162
3209
  /** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
3163
3210
  var BACKOFF_SCHEDULE_MS = [
@@ -3428,6 +3475,7 @@ var KNOWN_POSTPROCESSORS = new Set([
3428
3475
  "rfdetr",
3429
3476
  "yolo-seg",
3430
3477
  "scrfd",
3478
+ "yunet",
3431
3479
  "arcface",
3432
3480
  "clip",
3433
3481
  "softmax",
@@ -3529,9 +3577,12 @@ var DropSampler = class {
3529
3577
  * accumulates and a summary is released once per interval — carrying the
3530
3578
  * number suppressed, so nothing about the RATE is lost even though the lines
3531
3579
  * are.
3580
+ *
3581
+ * `channel` separates occasions that share a device — a replay's drops are
3582
+ * sampled apart from the live ones.
3532
3583
  */
3533
- record(deviceId, macroClass) {
3534
- const key = `${deviceId}:${macroClass}`;
3584
+ record(deviceId, macroClass, channel = "live") {
3585
+ const key = `${channel}:${deviceId}:${macroClass}`;
3535
3586
  const at = this.now();
3536
3587
  const prev = this.seen.get(key);
3537
3588
  if (prev === void 0) {
@@ -5055,7 +5106,8 @@ function buildFrameResult(input) {
5055
5106
  ...embeddingModelId !== void 0 ? { embeddingModelId } : {}
5056
5107
  } : {},
5057
5108
  ...m.faceAlignedCrop !== void 0 ? { faceAlignedCrop: m.faceAlignedCrop } : {},
5058
- ...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {}
5109
+ ...m.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: m.nativeFaceShortSidePx } : {},
5110
+ ...m.landmarks !== void 0 ? { landmarks: m.landmarks } : {}
5059
5111
  };
5060
5112
  if (input.debug) {
5061
5113
  const cleanDebug = pruneUndefined({
@@ -5316,6 +5368,11 @@ var PipelineExecutor = class {
5316
5368
  async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeCropProvider, cropZoneBbox, rootInputViewProvider) {
5317
5369
  const startMs = Date.now();
5318
5370
  const verbosity = runOpts?.traceVerbosity ?? "off";
5371
+ const occasion = runOpts?.occasion ?? "live";
5372
+ const logTags = occasion === "replay" ? {
5373
+ deviceId,
5374
+ occasion
5375
+ } : { deviceId };
5319
5376
  const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
5320
5377
  const firstLevel = [];
5321
5378
  const details = [];
@@ -5331,9 +5388,9 @@ var PipelineExecutor = class {
5331
5388
  idGen,
5332
5389
  details,
5333
5390
  onClassifierClassDropped: (drop) => {
5334
- const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`);
5391
+ const sample = this.classFilterDropSampler.record(deviceId, `${drop.stepId}:${drop.droppedClass}`, occasion);
5335
5392
  if (sample.logExample) this.opts.logger?.info("classifier winner excluded by class filter — label dropped", {
5336
- tags: { deviceId },
5393
+ tags: logTags,
5337
5394
  meta: {
5338
5395
  step: drop.stepId,
5339
5396
  model: drop.modelId,
@@ -5344,7 +5401,7 @@ var PipelineExecutor = class {
5344
5401
  }
5345
5402
  });
5346
5403
  if (sample.summaryCount !== null) this.opts.logger?.info("classifier class filter: still dropping", {
5347
- tags: { deviceId },
5404
+ tags: logTags,
5348
5405
  meta: {
5349
5406
  step: drop.stepId,
5350
5407
  droppedClass: drop.droppedClass,
@@ -5377,7 +5434,7 @@ var PipelineExecutor = class {
5377
5434
  await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
5378
5435
  } catch (err) {
5379
5436
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
5380
- tags: { deviceId },
5437
+ tags: logTags,
5381
5438
  meta: {
5382
5439
  rootStepId: rootStep.stepId,
5383
5440
  parentClass: mutable.macroClass,
@@ -5431,9 +5488,9 @@ var PipelineExecutor = class {
5431
5488
  const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
5432
5489
  const fullFrameHardArea = rootStep.settings?.["fullFrameGuardHardAreaRatio"];
5433
5490
  if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0, typeof fullFrameHardArea === "number" ? fullFrameHardArea : void 0)) {
5434
- const sample = this.dropSampler.record(deviceId, String(mutable.macroClass));
5491
+ const sample = this.dropSampler.record(deviceId, String(mutable.macroClass), occasion);
5435
5492
  if (sample.summaryCount !== null) this.opts.logger?.info("full-frame phantom guard: still dropping", {
5436
- tags: { deviceId },
5493
+ tags: logTags,
5437
5494
  meta: {
5438
5495
  macroClass: mutable.macroClass,
5439
5496
  suppressed: sample.summaryCount,
@@ -5442,7 +5499,7 @@ var PipelineExecutor = class {
5442
5499
  }
5443
5500
  });
5444
5501
  if (sample.logExample) this.opts.logger?.info("full-frame phantom guard: detection dropped", {
5445
- tags: { deviceId },
5502
+ tags: logTags,
5446
5503
  meta: {
5447
5504
  macroClass: mutable.macroClass,
5448
5505
  score: mutable.score,
@@ -5468,7 +5525,7 @@ var PipelineExecutor = class {
5468
5525
  continue;
5469
5526
  }
5470
5527
  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", {
5471
- tags: { deviceId },
5528
+ tags: logTags,
5472
5529
  meta: {
5473
5530
  macroClass: mutable.macroClass,
5474
5531
  score: mutable.score,
@@ -5482,7 +5539,7 @@ var PipelineExecutor = class {
5482
5539
  await this.executeChildren(rootStep.children, mutable, rootView?.jpegProvider ?? fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
5483
5540
  } catch (err) {
5484
5541
  this.opts.logger?.warn("Pipeline child execution failed — keeping parent detection", {
5485
- tags: { deviceId },
5542
+ tags: logTags,
5486
5543
  meta: {
5487
5544
  rootStepId: rootStep.stepId,
5488
5545
  parentClass: mutable.macroClass,
@@ -6540,25 +6597,6 @@ var PROBE_READY_TIMEOUT_MS = 12e4;
6540
6597
  * race bound keeps a wedged transport from wedging `setApi`.
6541
6598
  */
6542
6599
  var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
6543
- /**
6544
- * Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
6545
- * env. Used wherever the old `detectBestEngine()` sync probe fell back — the
6546
- * result is identical (onnx / cpu) but is now derived through the shared rules
6547
- * instead of duplicated inline.
6548
- */
6549
- /**
6550
- * Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
6551
- * coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
6552
- * reject a persisted/global engine choice that the node's PLATFORM fundamentally
6553
- * cannot run (e.g. the cluster's OpenVINO default landing on a Mac) — distinct
6554
- * from the gpu-dependent support (linux without a probed Intel iGPU still keeps
6555
- * openvino as a valid platform choice; the device falls back to cpu).
6556
- */
6557
- function backendPossibleOnPlatform(backend) {
6558
- if (backend === "coreml") return process.platform === "darwin";
6559
- if (backend === "openvino") return process.arch === "x64" && process.platform !== "darwin";
6560
- return true;
6561
- }
6562
6600
  var DetectionPipelineProvider = class DetectionPipelineProvider {
6563
6601
  modelsDir;
6564
6602
  eventBus;
@@ -6698,22 +6736,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6698
6736
  */
6699
6737
  needsAutoPick = false;
6700
6738
  /**
6701
- * Warm cache for benchmark engine-override runs.
6702
- *
6703
- * Each override rebuild costs a full Python pool spin-up (~300-500ms)
6704
- * plus the per-model load. Benchmark tabs iterate up to 800× against
6705
- * the same override (e.g. yolov9s / coreml / all) — paying that spin-up
6706
- * each iteration dwarfs actual inference time.
6707
- *
6708
- * When the same override engine is requested within `OVERRIDE_CACHE_TTL_MS`,
6709
- * we reuse the prior transient factory. Hitting a different override
6710
- * or exceeding the TTL disposes the cache and spins a new factory.
6711
- * The prior "main" factory (`priorFactory`) is still restored so
6712
- * runtime dispatch (camera frames) keeps its own resident engine.
6713
- */
6714
- overrideCache = null;
6715
- overrideCacheTimer = null;
6716
- /**
6717
6739
  * Idle-TTL for a per-device inference pool (multi-device C3). A device pool
6718
6740
  * with no dispatch for this long is disposed and recreated on next use — the
6719
6741
  * memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
@@ -6746,7 +6768,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6746
6768
  error: err instanceof Error ? err.message : String(err)
6747
6769
  } })
6748
6770
  });
6749
- static OVERRIDE_CACHE_TTL_MS = 6e4;
6750
6771
  /**
6751
6772
  * RSS bound + periodic memory telemetry for the Python inference pools
6752
6773
  * (2026-08-18 OOM audit: the pools, not the recorder, were the host's OOM
@@ -6883,15 +6904,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6883
6904
  /**
6884
6905
  * Lazy-install the pip requirements file matching `engine.backend` into
6885
6906
  * the embedded Python before the inference pool spawns. No-op for
6886
- * `runtime: 'node'`, for backends with no requirements file on disk,
6887
- * or before the addon context has been wired (warm-pool race window).
6907
+ * backends with no requirements file on disk, or before the addon
6908
+ * context has been wired (warm-pool race window).
6888
6909
  *
6889
6910
  * Idempotent — `installPythonRequirements` short-circuits on a hash
6890
6911
  * marker, so back-to-back EngineFactory rebuilds (benchmark overrides,
6891
6912
  * tuning respawns) skip the pip subprocess after the first install.
6892
6913
  */
6893
6914
  async ensureBackendDeps(engine) {
6894
- if (engine.runtime !== "python") return;
6895
6915
  if (!this.addonCtx) return;
6896
6916
  const pythonAddonDir = this.executorOptions.pythonAddonDir;
6897
6917
  if (!pythonAddonDir) return;
@@ -7456,8 +7476,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7456
7476
  * result with a warning logged, same posture as `recomputeConfigIssues`.
7457
7477
  */
7458
7478
  async validatePipeline(input) {
7459
- const format = this.currentEngine.format;
7479
+ let format = this.currentEngine.format;
7460
7480
  try {
7481
+ if (input.deviceKey !== void 0) try {
7482
+ format = resolveDeviceEngine(input.deviceKey).format;
7483
+ } catch (err) {
7484
+ return {
7485
+ ok: false,
7486
+ issues: [{
7487
+ addonId: "*",
7488
+ kind: "unknown-device",
7489
+ detail: errMsg(err)
7490
+ }],
7491
+ substitutions: [],
7492
+ format
7493
+ };
7494
+ }
7461
7495
  const steps = input.steps;
7462
7496
  const enabledSteps = this.pruneToEnabledSteps(steps);
7463
7497
  const substitutions = collectSubstitutions(enabledSteps, format);
@@ -7782,7 +7816,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7782
7816
  const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
7783
7817
  const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
7784
7818
  const isRuntime = Boolean(input.frame || input.frameRef);
7785
- const progressToBus = !isRuntime || input.sessionId !== void 0;
7819
+ const progressToBus = !isRuntime && input.replay !== true || input.sessionId !== void 0;
7786
7820
  const emit = (message, extra) => {
7787
7821
  onProgress?.(message);
7788
7822
  if (!progressToBus) return;
@@ -7924,7 +7958,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7924
7958
  const dispatchResolution = this.resolveStepsForDispatch({
7925
7959
  steps: input.steps,
7926
7960
  deviceKey: input.deviceKey,
7927
- engineOverride: input.engine,
7928
7961
  deviceId: input.deviceId,
7929
7962
  plane: input.plane
7930
7963
  });
@@ -7944,322 +7977,150 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
7944
7977
  }
7945
7978
  const enabledSteps = flattenSteps(benchmarkSteps);
7946
7979
  emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
7947
- await this.ensureEngineFactory();
7948
- const priorEngine = this.currentEngine;
7949
- const priorFactory = this.engineFactory;
7950
- const priorExecutor = this.executor;
7951
- const runEngine = input.engine ? {
7952
- runtime: input.engine.runtime,
7953
- backend: input.engine.backend,
7954
- format: input.engine.format,
7955
- ...input.engine.device || priorEngine.device ? { device: input.engine.device ?? priorEngine.device } : {}
7956
- } : priorEngine;
7957
- const differs = input.engine && (priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""));
7958
- if (differs) if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
7959
- const cached = this.overrideCache;
7960
- if (cached.initPromise) await cached.initPromise;
7961
- this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
7962
- this.currentEngine = runEngine;
7963
- this.engineFactory = cached.factory;
7964
- this.executor = cached.executor;
7965
- } else {
7966
- if (this.overrideCache) {
7967
- try {
7968
- await this.overrideCache.factory.dispose();
7969
- } catch {}
7970
- this.overrideCache = null;
7971
- }
7972
- this.log.info("Benchmark: engine override", { meta: {
7973
- from: `${priorEngine.runtime}/${priorEngine.backend}`,
7974
- to: `${runEngine.runtime}/${runEngine.backend}`
7975
- } });
7976
- await this.ensureBackendDeps(runEngine);
7977
- const newFactory = new EngineFactory({
7978
- engine: runEngine,
7979
- modelsDir: this.modelsDir,
7980
- logger: this.log.child("engine-override"),
7981
- pythonPath: this.executorOptions.pythonPath ?? "",
7982
- provisioning: this.executorOptions.provisioning,
7983
- resolveCustomModel: this.customModelResolver
7984
- });
7985
- const initPromise = newFactory.initialize([]);
7986
- this.overrideCache = {
7987
- engine: runEngine,
7988
- factory: newFactory,
7989
- executor: null,
7990
- lastUsedMs: Date.now(),
7991
- initPromise
7992
- };
7993
- this.scheduleOverrideCacheEviction();
7994
- try {
7995
- await initPromise;
7996
- this.overrideCache.initPromise = null;
7997
- } catch (err) {
7998
- this.overrideCache = null;
7999
- if (this.overrideCacheTimer) {
8000
- clearTimeout(this.overrideCacheTimer);
8001
- this.overrideCacheTimer = null;
8002
- }
8003
- throw err;
8004
- }
8005
- this.currentEngine = runEngine;
8006
- this.engineFactory = newFactory;
8007
- this.executor = null;
8008
- }
8009
- try {
8010
- const deviceLabel = runEngine.device ?? "default";
8011
- emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}${input.engine ? " (override)" : ""}`, {
8012
- step: "engine",
8013
- engine: {
8014
- runtime: runEngine.runtime,
8015
- backend: runEngine.backend,
8016
- device: deviceLabel
8017
- }
8018
- });
8019
- const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
8020
- const executor = this.executor ?? new PipelineExecutor({
8021
- engineRuntime: runtimeStr,
8022
- logger: this.log
7980
+ const rootModelId = resolveRootModelId(benchmarkSteps);
7981
+ const stampRoot = (r) => rootModelId === void 0 ? r : {
7982
+ ...r,
7983
+ rootModelId
7984
+ };
7985
+ if (input.replay === true) {
7986
+ const verdict = checkReplayPin({
7987
+ requested: input.steps,
7988
+ executing: input.plane === "frame" ? collectFramePlaneSteps(benchmarkSteps, isDetailPlaneStep) : enabledSteps,
7989
+ requestedDeviceKey: input.deviceKey,
7990
+ dispatchDeviceKey,
7991
+ format: dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format
8023
7992
  });
8024
- this.executor = executor;
8025
- const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
8026
- const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8027
- const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
8028
- if (needed.length > 0) {
8029
- this.log.info("Benchmark: models to load", { meta: {
8030
- count: needed.length,
8031
- models: needed.map((s) => `${s.addonId}/${s.modelId}`)
8032
- } });
8033
- for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
8034
- step: s.addonId,
8035
- addonId: s.addonId,
8036
- modelId: s.modelId
7993
+ if (verdict.kind === "refused") {
7994
+ this.log.warn("replay refused — the node cannot run exactly what was pinned", {
7995
+ ...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
7996
+ meta: {
7997
+ reason: verdict.reason,
7998
+ detail: verdict.detail,
7999
+ deviceKey: input.deviceKey
8000
+ }
8037
8001
  });
8038
- await this.ensureModelsForSteps(benchmarkSteps, dispatchFactory, dispatchEngine.format);
8039
- emit(`All models loaded`);
8040
- }
8041
- emit("Running inference...");
8042
- const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
8043
- setupMs = performance.now() - wallT0 - decodeMs;
8044
- const effectiveDeviceId = input.deviceId ?? 0;
8045
- const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
8046
- const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
8047
- const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
8048
- let cropZoneBbox;
8049
- if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
8050
- await this.ensureDeviceProxy(effectiveDeviceId);
8051
- const proxy = this.deviceProxies.get(effectiveDeviceId);
8052
- if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
8053
- }
8054
- let frameViewResolver;
8055
- let rootInputViewProvider;
8056
- if (runtimeFrameRef) {
8057
- frameViewResolver = createRootFrameViewResolver(localFrameRegistry, runtimeFrameRef);
8058
- rootInputViewProvider = async (root, crop) => {
8059
- const view = await frameViewResolver(root, crop ? {
8060
- left: crop[0],
8061
- top: crop[1],
8062
- width: crop[2] - crop[0],
8063
- height: crop[3] - crop[1]
8064
- } : void 0);
8065
- const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
8066
- return {
8067
- input: {
8068
- kind: "jpeg",
8069
- data
8070
- },
8071
- jpegProvider: async () => data,
8072
- width: view.width,
8073
- height: view.height,
8074
- geometry: view.geometry
8075
- };
8076
- };
8077
- }
8078
- let executionSucceeded = false;
8079
- let execution;
8080
- try {
8081
- execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
8082
- traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
8083
- plane: input.plane
8084
- }, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
8085
- executionSucceeded = true;
8086
- } finally {
8087
- frameViewResolver?.release(executionSucceeded ? "success" : "error");
8088
- }
8089
- const { result, trace } = execution;
8090
- if (isRuntime) {
8091
- if (trace && this.eventBus) this.eventBus.emit(createEvent(EventCategory.PipelineTrace, {
8092
- type: "device",
8093
- id: trace.deviceId,
8094
- nodeId: "hub"
8095
- }, trace));
8096
- if (effectiveDeviceId > 0) {
8097
- await this.ensureDeviceProxy(effectiveDeviceId);
8098
- return this.gateDetectionsByZoneRules(effectiveDeviceId, result);
8099
- }
8100
- return result;
8101
- }
8102
- for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
8103
- step: t.source,
8104
- addonId: t.source,
8105
- modelId: t.modelId ?? void 0,
8106
- ms: t.ms
8107
- });
8108
- emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
8109
- const wallMs = performance.now() - wallT0;
8110
- const inferMs = result.debug?.totalInferenceMs ?? 0;
8111
- const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
8112
- return {
8113
- ...result,
8114
- debug: {
8115
- ...result.debug,
8116
- decodeMs: Math.round(decodeMs * 100) / 100,
8117
- setupMs: Math.round(setupMs * 100) / 100,
8118
- wallMs: Math.round(wallMs * 100) / 100,
8119
- overheadMs: Math.round(overheadMs * 100) / 100
8120
- }
8121
- };
8122
- } finally {
8123
- if (differs && this.overrideCache) {
8124
- this.overrideCache.executor = this.executor;
8125
- this.overrideCache.lastUsedMs = Date.now();
8126
- this.scheduleOverrideCacheEviction();
8127
- }
8128
- if (differs) {
8129
- this.currentEngine = priorEngine;
8130
- this.engineFactory = priorFactory;
8131
- this.executor = priorExecutor;
8002
+ throw new Error(`${verdict.reason}: ${verdict.detail}`);
8132
8003
  }
8133
8004
  }
8134
- }
8135
- /**
8136
- * Apply a benchmark engine override (mirroring the body of `runPipeline`
8137
- * lines 863-953). Swaps `currentEngine` / `engineFactory` / `executor`
8138
- * to the override's warm factory and returns a restore function the
8139
- * caller MUST invoke in `finally`. When `override` is undefined or
8140
- * matches the current engine, this is a no-op and the restore function
8141
- * does nothing — same shape so callers don't need a conditional path.
8142
- *
8143
- * Used by both `runPipeline` (single-frame benchmark) and
8144
- * `runPipelineBatch` (batched fast path). Without this, the batch
8145
- * path silently ignored `input.engine` and ran the user's override
8146
- * against the addon's saved engine — making the override matrix in
8147
- * the bench UI a no-op for batchSize > 1.
8148
- */
8149
- async applyEngineOverride(override) {
8150
- const priorEngine = this.currentEngine;
8151
- const priorFactory = this.engineFactory;
8152
- const priorExecutor = this.executor;
8153
- if (!override) return () => {};
8154
- if (!backendPossibleOnPlatform(override.backend)) {
8155
- this.log.warn("Engine override impossible on this platform — running on own engine", { meta: {
8156
- override: `${override.runtime}/${override.backend}/${override.device ?? "default"}`,
8157
- platform: process.platform,
8158
- arch: process.arch,
8159
- engine: `${priorEngine.runtime}/${priorEngine.backend}`
8160
- } });
8161
- return () => {};
8162
- }
8163
- const runEngine = {
8164
- runtime: override.runtime,
8165
- backend: override.backend,
8166
- format: override.format,
8167
- ...override.device || priorEngine.device ? { device: override.device ?? priorEngine.device } : {}
8168
- };
8169
- if (!(priorEngine.runtime !== runEngine.runtime || priorEngine.backend !== runEngine.backend || priorEngine.format !== runEngine.format || (priorEngine.device ?? "") !== (runEngine.device ?? ""))) return () => {};
8170
- if (this.overrideCache && enginesEqual(this.overrideCache.engine, runEngine)) {
8171
- const cached = this.overrideCache;
8172
- if (cached.initPromise) await cached.initPromise;
8173
- this.log.debug("Benchmark: reusing warm override factory", { meta: { engine: `${runEngine.runtime}/${runEngine.backend}/${runEngine.device ?? "default"}` } });
8174
- this.currentEngine = runEngine;
8175
- this.engineFactory = cached.factory;
8176
- this.executor = cached.executor;
8177
- } else {
8178
- if (this.overrideCache) {
8179
- try {
8180
- await this.overrideCache.factory.dispose();
8181
- } catch {}
8182
- this.overrideCache = null;
8005
+ const appliesCameraGates = isRuntime || input.replay === true;
8006
+ await this.ensureEngineFactory();
8007
+ const runEngine = this.currentEngine;
8008
+ const deviceLabel = runEngine.device ?? "default";
8009
+ emit(`Engine: ${runEngine.runtime}/${runEngine.backend}/${deviceLabel}`, {
8010
+ step: "engine",
8011
+ engine: {
8012
+ runtime: runEngine.runtime,
8013
+ backend: runEngine.backend,
8014
+ device: deviceLabel
8183
8015
  }
8184
- this.log.info("Benchmark: engine override", { meta: {
8185
- from: `${priorEngine.runtime}/${priorEngine.backend}`,
8186
- to: `${runEngine.runtime}/${runEngine.backend}`
8016
+ });
8017
+ const runtimeStr = `${runEngine.runtime}+${runEngine.backend}`;
8018
+ const executor = this.executor ?? new PipelineExecutor({
8019
+ engineRuntime: runtimeStr,
8020
+ logger: this.log
8021
+ });
8022
+ this.executor = executor;
8023
+ const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
8024
+ const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8025
+ const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
8026
+ if (needed.length > 0) {
8027
+ this.log.info("Benchmark: models to load", { meta: {
8028
+ count: needed.length,
8029
+ models: needed.map((s) => `${s.addonId}/${s.modelId}`)
8187
8030
  } });
8188
- await this.ensureBackendDeps(runEngine);
8189
- const newFactory = new EngineFactory({
8190
- engine: runEngine,
8191
- modelsDir: this.modelsDir,
8192
- logger: this.log.child("engine-override"),
8193
- pythonPath: this.executorOptions.pythonPath ?? "",
8194
- provisioning: this.executorOptions.provisioning,
8195
- resolveCustomModel: this.customModelResolver
8031
+ for (const s of needed) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
8032
+ step: s.addonId,
8033
+ addonId: s.addonId,
8034
+ modelId: s.modelId
8196
8035
  });
8197
- const initPromise = newFactory.initialize([]);
8198
- this.overrideCache = {
8199
- engine: runEngine,
8200
- factory: newFactory,
8201
- executor: null,
8202
- lastUsedMs: Date.now(),
8203
- initPromise
8036
+ await this.ensureModelsForSteps(benchmarkSteps, dispatchFactory, dispatchEngine.format);
8037
+ emit(`All models loaded`);
8038
+ }
8039
+ emit("Running inference...");
8040
+ const tree = buildExecutableTree(benchmarkSteps, (stepId) => dispatchFactory.getEngine(stepId), this.customModelResolver);
8041
+ setupMs = performance.now() - wallT0 - decodeMs;
8042
+ const effectiveDeviceId = input.deviceId ?? 0;
8043
+ const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
8044
+ const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
8045
+ const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
8046
+ let cropZoneBbox;
8047
+ if (appliesCameraGates && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
8048
+ await this.ensureDeviceProxy(effectiveDeviceId);
8049
+ const proxy = this.deviceProxies.get(effectiveDeviceId);
8050
+ if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
8051
+ }
8052
+ let frameViewResolver;
8053
+ let rootInputViewProvider;
8054
+ if (runtimeFrameRef) {
8055
+ frameViewResolver = createRootFrameViewResolver(localFrameRegistry, runtimeFrameRef);
8056
+ rootInputViewProvider = async (root, crop) => {
8057
+ const view = await frameViewResolver(root, crop ? {
8058
+ left: crop[0],
8059
+ top: crop[1],
8060
+ width: crop[2] - crop[0],
8061
+ height: crop[3] - crop[1]
8062
+ } : void 0);
8063
+ const data = Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength);
8064
+ return {
8065
+ input: {
8066
+ kind: "jpeg",
8067
+ data
8068
+ },
8069
+ jpegProvider: async () => data,
8070
+ width: view.width,
8071
+ height: view.height,
8072
+ geometry: view.geometry
8073
+ };
8204
8074
  };
8205
- this.scheduleOverrideCacheEviction();
8206
- try {
8207
- await initPromise;
8208
- this.overrideCache.initPromise = null;
8209
- } catch (err) {
8210
- this.overrideCache = null;
8211
- if (this.overrideCacheTimer) {
8212
- clearTimeout(this.overrideCacheTimer);
8213
- this.overrideCacheTimer = null;
8214
- }
8215
- throw err;
8075
+ }
8076
+ let executionSucceeded = false;
8077
+ let execution;
8078
+ try {
8079
+ execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
8080
+ traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
8081
+ plane: input.plane,
8082
+ occasion: input.replay === true ? "replay" : "live"
8083
+ }, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
8084
+ executionSucceeded = true;
8085
+ } finally {
8086
+ frameViewResolver?.release(executionSucceeded ? "success" : "error");
8087
+ }
8088
+ const { result, trace } = execution;
8089
+ if (isRuntime) {
8090
+ if (trace && this.eventBus) this.eventBus.emit(createEvent(EventCategory.PipelineTrace, {
8091
+ type: "device",
8092
+ id: trace.deviceId,
8093
+ nodeId: "hub"
8094
+ }, trace));
8095
+ if (effectiveDeviceId > 0) {
8096
+ await this.ensureDeviceProxy(effectiveDeviceId);
8097
+ return stampRoot(this.gateDetectionsByZoneRules(effectiveDeviceId, result));
8216
8098
  }
8217
- this.currentEngine = runEngine;
8218
- this.engineFactory = newFactory;
8219
- this.executor = null;
8099
+ return stampRoot(result);
8220
8100
  }
8221
- return () => {
8222
- if (this.overrideCache) {
8223
- this.overrideCache.executor = this.executor;
8224
- this.overrideCache.lastUsedMs = Date.now();
8225
- this.scheduleOverrideCacheEviction();
8101
+ for (const t of result.debug?.stepTimings ?? []) emit(`${t.source}${t.modelId ? ` (${t.modelId})` : ""} → ${t.ms}ms`, {
8102
+ step: t.source,
8103
+ addonId: t.source,
8104
+ modelId: t.modelId ?? void 0,
8105
+ ms: t.ms
8106
+ });
8107
+ emit(`Done — ${result.detections.length} detection(s) in ${result.debug?.totalInferenceMs ?? 0}ms`, { ms: result.debug?.totalInferenceMs });
8108
+ const wallMs = performance.now() - wallT0;
8109
+ const inferMs = result.debug?.totalInferenceMs ?? 0;
8110
+ const overheadMs = Math.max(0, wallMs - decodeMs - setupMs - inferMs);
8111
+ const gated = input.replay === true && effectiveDeviceId > 0 ? await this.ensureDeviceProxy(effectiveDeviceId).then(() => this.gateDetectionsByZoneRules(effectiveDeviceId, result)) : result;
8112
+ return {
8113
+ ...stampRoot(gated),
8114
+ debug: {
8115
+ ...gated.debug,
8116
+ decodeMs: Math.round(decodeMs * 100) / 100,
8117
+ setupMs: Math.round(setupMs * 100) / 100,
8118
+ wallMs: Math.round(wallMs * 100) / 100,
8119
+ overheadMs: Math.round(overheadMs * 100) / 100
8226
8120
  }
8227
- this.currentEngine = priorEngine;
8228
- this.engineFactory = priorFactory;
8229
- this.executor = priorExecutor;
8230
8121
  };
8231
8122
  }
8232
8123
  /**
8233
- * Schedule eviction of the override cache after `OVERRIDE_CACHE_TTL_MS`
8234
- * of idleness. Rescheduling resets the timer so an active benchmark
8235
- * keeps its warm factory alive until iterations stop arriving.
8236
- */
8237
- scheduleOverrideCacheEviction() {
8238
- if (this.overrideCacheTimer) clearTimeout(this.overrideCacheTimer);
8239
- this.overrideCacheTimer = setTimeout(() => {
8240
- this.evictOverrideCache("idle TTL");
8241
- }, DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS);
8242
- }
8243
- async evictOverrideCache(reason) {
8244
- if (!this.overrideCache) return;
8245
- const entry = this.overrideCache;
8246
- this.overrideCache = null;
8247
- if (this.overrideCacheTimer) {
8248
- clearTimeout(this.overrideCacheTimer);
8249
- this.overrideCacheTimer = null;
8250
- }
8251
- try {
8252
- await entry.factory.dispose();
8253
- this.log.info("Override cache evicted", { meta: {
8254
- reason,
8255
- engine: `${entry.engine.runtime}/${entry.engine.backend}/${entry.engine.device ?? "default"}`,
8256
- ageMs: Date.now() - entry.lastUsedMs
8257
- } });
8258
- } catch (err) {
8259
- this.log.warn("Override cache dispose failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
8260
- }
8261
- }
8262
- /**
8263
8124
  * Convert PipelineStepInput[] → PipelineDefaultStep[] by enriching from
8264
8125
  * StepDefinitions. This is the LIVE per-camera dispatch path — it runs
8265
8126
  * once per decoded frame (up to detectionFps × N cameras/sec) — so it
@@ -8275,13 +8136,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8275
8136
  * `resolveStepModels` (see its doc comment); a second writer here would
8276
8137
  * corrupt it.
8277
8138
  *
8278
- * `format` defaults to `this.currentEngine.format` (the LIVE per-camera
8279
- * dispatch path never passes it explicitly). Benchmark call sites with a
8280
- * per-run engine override (`input.engine`) MUST pass the override's format
8281
- * explicitly — `this.currentEngine` only gets swapped to the override
8282
- * LATER in `runPipeline`/`runPipelineBatch`, so relying on the default here
8283
- * would resolve models against the node's persisted format instead of the
8284
- * format actually being benchmarked.
8139
+ * `format` defaults to `this.currentEngine.format` (the node-default
8140
+ * pool); a `deviceKey` dispatch passes that device's format explicitly.
8285
8141
  */
8286
8142
  inputStepsToPipelineSteps(steps, format = this.currentEngine.format, engine = {
8287
8143
  backend: this.currentEngine.backend,
@@ -8363,8 +8219,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8363
8219
  * devices — so the whole-tree gate stays.
8364
8220
  *
8365
8221
  * Fallback is strictly NODE-LOCAL and ordered: the node's own selected
8366
- * engine (or the benchmark's explicit `engineOverride`) is the one and only
8367
- * fallback — frames never migrate to another node from here (that is the
8222
+ * engine is the one and only fallback — frames never migrate to another node from here (that is the
8368
8223
  * orchestrator's tier). The fallback is logged ONCE per distinct
8369
8224
  * (deviceKey, steps, format) signature, with `tags: { deviceId }` when the
8370
8225
  * dispatch is camera-scoped.
@@ -8375,12 +8230,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8375
8230
  * configuration must stay loud, not vanish into a silent fallback chain.
8376
8231
  */
8377
8232
  resolveStepsForDispatch(args) {
8378
- const { deviceKey, engineOverride, deviceId } = args;
8379
- const nodeFormat = engineOverride?.format ?? this.currentEngine.format;
8380
- const nodeEngine = engineOverride ? {
8381
- backend: engineOverride.backend,
8382
- device: engineOverride.device ?? null
8383
- } : {
8233
+ const { deviceKey, deviceId } = args;
8234
+ const nodeFormat = this.currentEngine.format;
8235
+ const nodeEngine = {
8384
8236
  backend: this.currentEngine.backend,
8385
8237
  device: this.currentEngine.device ?? null
8386
8238
  };
@@ -8625,12 +8477,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8625
8477
  const dispatchResolution = this.resolveStepsForDispatch({
8626
8478
  steps: input.steps,
8627
8479
  deviceKey: input.deviceKey,
8628
- engineOverride: input.engine,
8629
8480
  deviceId: input.deviceId,
8630
8481
  plane: void 0
8631
8482
  });
8632
8483
  const dispatchDeviceKey = dispatchResolution.deviceKey;
8633
- const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : input.engine?.format ?? this.currentEngine.format;
8484
+ const resolveFormat = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey).format : this.currentEngine.format;
8634
8485
  const benchmarkSteps = dispatchResolution.steps;
8635
8486
  const enabledSteps = flattenEnabledVideoSteps(benchmarkSteps);
8636
8487
  if (enabledSteps.length === 0) throw new Error("runPipelineBatch: no enabled steps");
@@ -8651,53 +8502,48 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8651
8502
  const uniformDims = input.frames.every((f) => f.width === firstFrame.width && f.height === firstFrame.height);
8652
8503
  const singleRoot = enabledSteps.length === 1;
8653
8504
  await this.ensureEngineFactory();
8654
- const restoreEngine = await this.applyEngineOverride(input.engine);
8655
- try {
8656
- const factory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8657
- if (!factory) throw new Error("runPipelineBatch: factory not initialised");
8658
- if (enabledSteps.filter((s) => factory.needsPoolUpdate(s)).length > 0) await this.ensureModelsForSteps(benchmarkSteps, factory, resolveFormat);
8659
- const canFastPath = singleRoot && allRaw && uniformDims && factory.supportsBatch();
8660
- this.log.info("runPipelineBatch path decision", { meta: {
8661
- phase: "batch",
8662
- canFastPath,
8663
- singleRoot,
8664
- allRaw,
8665
- uniformDims,
8666
- supportsBatch: factory.supportsBatch()
8667
- } });
8668
- if (!canFastPath) return { results: await Promise.all(input.frames.map((frame) => this.runPipeline({
8669
- steps: input.steps,
8670
- frame,
8671
- deviceId: input.deviceId,
8672
- sessionId: input.sessionId,
8673
- deviceKey: dispatchDeviceKey
8674
- }))) };
8675
- const items = input.frames.map((frame) => ({
8676
- raw: Buffer.from(frame.data),
8677
- width: frame.width,
8678
- height: frame.height,
8679
- format: frame.format
8680
- }));
8681
- this.log.info("runPipelineBatch fast path dispatch", { meta: {
8682
- phase: "batch",
8683
- stepId: rootStep.addonId,
8684
- modelId: rootStep.modelId,
8685
- items: items.length,
8686
- totalRawBytes: items.reduce((s, it) => s + it.raw.length, 0)
8687
- } });
8688
- const start = performance.now();
8689
- const rawResults = await factory.batchInferRaw(rootStep.addonId, items, rootStep.modelId, input.frameId ?? 0);
8690
- const totalMs = performance.now() - start;
8691
- this.log.info("runPipelineBatch fast path returned", { meta: {
8692
- phase: "batch",
8693
- resultCount: rawResults.length,
8694
- ms: Math.round(totalMs)
8695
- } });
8696
- const perFrameMs = totalMs / rawResults.length;
8697
- return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
8698
- } finally {
8699
- restoreEngine();
8700
- }
8505
+ const factory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
8506
+ if (!factory) throw new Error("runPipelineBatch: factory not initialised");
8507
+ if (enabledSteps.filter((s) => factory.needsPoolUpdate(s)).length > 0) await this.ensureModelsForSteps(benchmarkSteps, factory, resolveFormat);
8508
+ const canFastPath = singleRoot && allRaw && uniformDims && factory.supportsBatch();
8509
+ this.log.info("runPipelineBatch path decision", { meta: {
8510
+ phase: "batch",
8511
+ canFastPath,
8512
+ singleRoot,
8513
+ allRaw,
8514
+ uniformDims,
8515
+ supportsBatch: factory.supportsBatch()
8516
+ } });
8517
+ if (!canFastPath) return { results: await Promise.all(input.frames.map((frame) => this.runPipeline({
8518
+ steps: input.steps,
8519
+ frame,
8520
+ deviceId: input.deviceId,
8521
+ sessionId: input.sessionId,
8522
+ deviceKey: dispatchDeviceKey
8523
+ }))) };
8524
+ const items = input.frames.map((frame) => ({
8525
+ raw: Buffer.from(frame.data),
8526
+ width: frame.width,
8527
+ height: frame.height,
8528
+ format: frame.format
8529
+ }));
8530
+ this.log.info("runPipelineBatch fast path dispatch", { meta: {
8531
+ phase: "batch",
8532
+ stepId: rootStep.addonId,
8533
+ modelId: rootStep.modelId,
8534
+ items: items.length,
8535
+ totalRawBytes: items.reduce((s, it) => s + it.raw.length, 0)
8536
+ } });
8537
+ const start = performance.now();
8538
+ const rawResults = await factory.batchInferRaw(rootStep.addonId, items, rootStep.modelId, input.frameId ?? 0);
8539
+ const totalMs = performance.now() - start;
8540
+ this.log.info("runPipelineBatch fast path returned", { meta: {
8541
+ phase: "batch",
8542
+ resultCount: rawResults.length,
8543
+ ms: Math.round(totalMs)
8544
+ } });
8545
+ const perFrameMs = totalMs / rawResults.length;
8546
+ return { results: rawResults.map((raw, i) => assembleBatchedFrameResult(raw, input.frames[i], rootStep.addonId, input.deviceId ?? 0, perFrameMs, perFrameMs)) };
8701
8547
  }
8702
8548
  async listReferenceImages() {
8703
8549
  const dir = this.resolveRefImagesDir();
@@ -8802,7 +8648,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8802
8648
  this.poolMemoryGuard = null;
8803
8649
  this.inferenceTimeoutGuard?.stop();
8804
8650
  this.inferenceTimeoutGuard = null;
8805
- await this.evictOverrideCache("shutdown");
8806
8651
  if (this.engineFactory) {
8807
8652
  await this.engineFactory.dispose();
8808
8653
  this.engineFactory = null;
@@ -8884,13 +8729,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8884
8729
  * own runtime's row, so this is not "the node's tuning" — the per-pool truth
8885
8730
  * is on each pool's `Python inference pool` log line and in `listLoadedEngines`.
8886
8731
  */
8887
- async getEffectiveTuning() {
8888
- const p = resolvePoolProvisioning(this.currentEngine, this.executorOptions.provisioning);
8732
+ /**
8733
+ * One pool's provisioning — the device pool named by `deviceKey`, else the
8734
+ * node default. Resolved with the SAME override `resolveDeviceFactory` and
8735
+ * `ensureEngineFactory` build the pool with, so the answer describes the
8736
+ * pool that ran the work (D646).
8737
+ */
8738
+ async getEffectiveTuning(input = {}) {
8739
+ const p = resolvePoolProvisioning(input.deviceKey !== void 0 ? resolveDeviceEngine(input.deviceKey) : this.currentEngine, this.executorOptions.provisioning);
8889
8740
  return {
8741
+ deviceKey: input.deviceKey ?? null,
8742
+ runtime: p.runtime,
8890
8743
  batchMode: p.batchMode,
8891
8744
  windowMs: p.windowMs,
8892
8745
  maxBatchSize: p.maxBatchSize,
8893
- concurrency: p.concurrency
8746
+ concurrency: p.concurrency,
8747
+ numWorkers: p.numWorkers
8894
8748
  };
8895
8749
  }
8896
8750
  /**
@@ -9278,9 +9132,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9278
9132
  }
9279
9133
  async listLoadedEngines() {
9280
9134
  const out = [];
9281
- const overrideFactory = this.overrideCache?.factory ?? null;
9282
- const runtimeFactoryIsOverride = this.engineFactory !== null && this.engineFactory === overrideFactory;
9283
- if (this.engineFactory && !runtimeFactoryIsOverride) {
9135
+ if (this.engineFactory) {
9284
9136
  const eng = this.currentEngine;
9285
9137
  const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"}`;
9286
9138
  const loaded = this.engineFactory.listLoaded();
@@ -9295,22 +9147,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9295
9147
  idleTtlMs: null
9296
9148
  });
9297
9149
  }
9298
- if (this.overrideCache) {
9299
- const eng = this.overrideCache.engine;
9300
- const engineKey = `${eng.runtime}/${eng.backend}/${eng.device ?? "default"} (warm)`;
9301
- const loaded = this.overrideCache.factory.listLoaded();
9302
- const idleMs = Math.max(0, Date.now() - this.overrideCache.lastUsedMs);
9303
- if (idleMs <= DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS) out.push({
9304
- engineKey,
9305
- engine: eng,
9306
- modelsLoaded: loaded.map((l) => `${l.stepId}/${l.modelId}`),
9307
- inUseByCameras: [],
9308
- kind: "warm-override",
9309
- poolPid: this.overrideCache.factory.getPoolPid(),
9310
- idleMs,
9311
- idleTtlMs: DetectionPipelineProvider.OVERRIDE_CACHE_TTL_MS
9312
- });
9313
- }
9314
9150
  for (const [deviceKey, factory] of this.factoriesByDevice) {
9315
9151
  const eng = resolveDeviceEngine(deviceKey);
9316
9152
  const loaded = factory.listLoaded();
@@ -9339,14 +9175,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9339
9175
  return { success: true };
9340
9176
  }
9341
9177
  async killEngine(input) {
9342
- if (this.overrideCache && enginesEqual(this.overrideCache.engine, input.engine)) {
9343
- await this.evictOverrideCache("killEngine");
9344
- this.log.info("Override engine killed", { meta: {
9345
- ...input.engine,
9346
- force: input.force ?? false
9347
- } });
9348
- return { success: true };
9349
- }
9350
9178
  if (!this.engineFactory) return {
9351
9179
  success: false,
9352
9180
  reason: "not loaded"
@@ -9864,7 +9692,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
9864
9692
  /**
9865
9693
  * Embedded Python path resolved once at boot via
9866
9694
  * `ctx.deps.ensurePython()`. Empty string means the download failed
9867
- * or `runtime: 'node'` was selected — the provider's
9695
+ * — the provider's
9868
9696
  * `ensureBackendDeps` and EngineFactory's `initPythonPool` raise a
9869
9697
  * clear error in that case.
9870
9698
  */