@camstack/addon-pipeline 1.2.621 → 1.2.622

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 (43) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-Il8UY4D4.js → custom-model-catalog-CKCK-QcT.js} +7 -7
  4. package/dist/{custom-model-catalog-0_SnSXzN.mjs → custom-model-catalog-wpMi2U7e.mjs} +7 -7
  5. package/dist/detection-pipeline/index.js +55 -414
  6. package/dist/detection-pipeline/index.mjs +79 -438
  7. package/dist/{dist-DHqkQhTQ.mjs → dist-BF60SjmI.mjs} +76 -226
  8. package/dist/{dist-ClkumZgv.js → dist-DUFGDd_2.js} +75 -231
  9. package/dist/{model-catalogs-BagalaYp.js → model-catalogs-CjTm_0O-.js} +1 -1
  10. package/dist/{model-catalogs-Z1GZAhSb.mjs → model-catalogs-CqFDhb0B.mjs} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-CkJLxlWI.mjs → node-DkphOOM2.mjs} +1 -1
  14. package/dist/{node-0O_61WVW.js → node-DzGVn7bD.js} +1 -1
  15. package/dist/pipeline-runner/index.js +18 -4
  16. package/dist/pipeline-runner/index.mjs +18 -4
  17. package/dist/{process-memory-DdIOhjYB.js → process-memory-8kKexHYR.js} +1 -1
  18. package/dist/{process-memory-Dj3UgL1e.mjs → process-memory-CBzfdSvC.mjs} +1 -1
  19. package/dist/recorder/index.js +2 -2
  20. package/dist/recorder/index.mjs +2 -2
  21. package/dist/{segment-demux-js-Bv0t0-tQ.mjs → segment-demux-js-DQNoNSJt.mjs} +1 -1
  22. package/dist/{segment-demux-js-BePzacki.js → segment-demux-js-DRLQb1pc.js} +1 -1
  23. package/dist/session-decode/decode-worker-main.js +12 -21
  24. package/dist/session-decode/decode-worker-main.mjs +12 -21
  25. package/dist/stream-broker/_stub.js +2 -2
  26. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-dNk74ahz.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BmANAxAe.mjs} +2 -2
  27. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-H6RKsaiZ.mjs +26 -0
  28. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CNoWeZHH.mjs +26 -0
  29. package/dist/stream-broker/demux-worker-child.js +1 -1
  30. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  31. package/dist/stream-broker/{hostInit-CH5g78CJ.mjs → hostInit-B3hXAfr1.mjs} +2 -2
  32. package/dist/stream-broker/index.js +3 -3
  33. package/dist/stream-broker/index.mjs +3 -3
  34. package/dist/stream-broker/remoteEntry.js +1 -1
  35. package/dist/{worker-protocol-D_NzPcnh.mjs → worker-protocol-DngGEAT2.js} +102 -2
  36. package/dist/{worker-protocol-C-G8qmye.js → worker-protocol-_avq7y8O.mjs} +67 -25
  37. package/package.json +1 -1
  38. package/python/coreml_load_probe.py +105 -15
  39. package/python/openvino_compile_probe.py +72 -18
  40. package/python/test_coreml_load_probe.py +83 -0
  41. package/python/test_openvino_compile_probe.py +114 -0
  42. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-2KkGoQ55.mjs +0 -26
  43. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dx-WrMx0.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-ClkumZgv.js");
7
- const require_node = require("../node-0O_61WVW.js");
8
- const require_custom_model_catalog = require("../custom-model-catalog-Il8UY4D4.js");
6
+ const require_dist = require("../dist-DUFGDd_2.js");
7
+ const require_node = require("../node-DzGVn7bD.js");
8
+ const require_custom_model_catalog = require("../custom-model-catalog-CKCK-QcT.js");
9
9
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
10
- const require_process_memory = require("../process-memory-DdIOhjYB.js");
10
+ const require_process_memory = require("../process-memory-8kKexHYR.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");
@@ -357,84 +357,6 @@ function normalizeEngineNodeId(rawNodeId) {
357
357
  return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
358
358
  }
359
359
  //#endregion
360
- //#region src/detection-pipeline/model-bound-device-overrides.ts
361
- /**
362
- * Auto tune (D745), the executor leg: list — and optionally remove — the
363
- * MODEL-BOUND thresholds stored in this addon's per-camera settings (the
364
- * object-detection root's `bindsToModel` keys and their binding keys, D738).
365
- * Every other key of a camera's store is kept.
366
- *
367
- * The device store only MERGES on write, so a removal is clear + write of the
368
- * kept keys — the idiom `migrate-hardware-state.ts` uses. Both run in this
369
- * process, serialized per camera; a write that fails after the clear restores
370
- * the original row, and the camera is reported `refused` rather than clean. A
371
- * store that cannot be read is `refused` too: a failed read is never "nothing
372
- * to clear" (D393).
373
- */
374
- function errMsg(err) {
375
- return err instanceof Error ? err.message : String(err);
376
- }
377
- /** The stored keys among `boundKeys` (values and bindings), sorted. */
378
- function presentBoundKeys(row, boundKeys) {
379
- return boundKeys.flatMap((k) => [k, require_dist.modelBindingKey(k)]).filter((k) => k in row).toSorted();
380
- }
381
- async function clearModelBoundDeviceOverrides(deps, input) {
382
- const ids = input.deviceIds ?? await deps.listDeviceIds();
383
- const entries = [];
384
- const refused = [];
385
- for (const deviceId of [...ids].toSorted((a, b) => a - b)) {
386
- let row;
387
- try {
388
- row = await deps.readDeviceStore(deviceId);
389
- } catch (err) {
390
- refused.push({
391
- deviceId,
392
- reason: `read failed: ${errMsg(err)}`
393
- });
394
- continue;
395
- }
396
- const keys = presentBoundKeys(row, deps.boundKeys);
397
- if (keys.length === 0) continue;
398
- if (input.dryRun) {
399
- entries.push({
400
- deviceId,
401
- keys
402
- });
403
- continue;
404
- }
405
- const kept = { ...row };
406
- for (const k of keys) delete kept[k];
407
- try {
408
- await deps.clearDeviceStore(deviceId);
409
- } catch (err) {
410
- refused.push({
411
- deviceId,
412
- reason: `clear failed: ${errMsg(err)}`
413
- });
414
- continue;
415
- }
416
- try {
417
- if (Object.keys(kept).length > 0) await deps.writeDeviceStore(deviceId, kept);
418
- entries.push({
419
- deviceId,
420
- keys
421
- });
422
- } catch (err) {
423
- await deps.writeDeviceStore(deviceId, row).catch(() => void 0);
424
- refused.push({
425
- deviceId,
426
- reason: `write failed, row restored: ${errMsg(err)}`
427
- });
428
- } finally {
429
- deps.invalidate(deviceId);
430
- }
431
- }
432
- return {
433
- entries,
434
- refused
435
- };
436
- }
437
- //#endregion
438
360
  //#region src/detection-pipeline/model-format-facts.ts
439
361
  /**
440
362
  * Two per-format facts `getSchema` reports beside `downloaded`, for Model
@@ -769,122 +691,6 @@ var ClusterUnknownRefusalLog = class {
769
691
  }
770
692
  };
771
693
  //#endregion
772
- //#region src/detection-pipeline/device-override-mirror.ts
773
- /**
774
- * The per-device detection overrides, kept fresh OFF the frame path.
775
- *
776
- * ## The read this replaces
777
- *
778
- * `runPipeline` resolved a device's overrides by calling
779
- * `settings.readDeviceStore(deviceId)` on **every inference frame**. Measured
780
- * on the live hub 2026-08-25: `get detection-pipeline:addon-devices`, 1 143
781
- * calls a minute at 0.44 rows per call — the disk asked, nineteen times a
782
- * second, whether an operator has ever changed a threshold.
783
- *
784
- * ## Why this is not "a cache in the consumer" (D224)
785
- *
786
- * D224 forbids a SECOND authority over a fact that belongs to somebody else:
787
- * the failure it records is a consumer caching a value whose write path does
788
- * not run through that consumer, so an operator's own change is the one read
789
- * the cache gets wrong.
790
- *
791
- * This addon OWNS the row. `<addonId>:addon-devices` is detection-pipeline's
792
- * own store, and the only writers are this addon's `updateDeviceSettings` and
793
- * its `clearDeviceOverrides` — both of which call {@link invalidate}. A write
794
- * is therefore its own invalidation, exactly as D224 requires, and the window
795
- * below governs only the case nobody writes through this process.
796
- *
797
- * ## The window, declared
798
- *
799
- * {@link DEVICE_OVERRIDE_STALE_MS} is what an operator's change costs on a node
800
- * that did NOT serve the write — this addon runs per node, and a settings write
801
- * lands on one of them. Thirty seconds is the trade: it removes 99.9% of the
802
- * reads (one per device per window instead of one per frame) and it is short
803
- * enough that "I moved the threshold and nothing happened" is not a support
804
- * question. Ten minutes — {@link OPERATOR_WRITTEN_STALE_MS}, the constant for a
805
- * cap whose value only an operator changes — is right for a POLLED reader that
806
- * costs a camera round trip; this read costs one local SQLite `get`, and the
807
- * cheap read buys a short window.
808
- *
809
- * A stale entry is SERVED and revalidated behind the caller, never awaited: a
810
- * frame that waits for the settings store is a frame the pipeline dropped, and
811
- * the reader is by definition about to ask again.
812
- */
813
- /**
814
- * How long a mirrored entry is served before a refresh is started behind the
815
- * next reader. See the module doc for why it is 30 s and not ten minutes.
816
- */
817
- var DEVICE_OVERRIDE_STALE_MS = 3e4;
818
- /**
819
- * How long a FAILED refresh is not retried.
820
- *
821
- * A refresh that throws leaves `fetchedAt` where it was, so without a cooldown
822
- * every subsequent frame would start another one — a broken settings store
823
- * would be hammered at frame rate by the very mechanism built to stop that.
824
- */
825
- var DEVICE_OVERRIDE_MISS_COOLDOWN_MS = 5e3;
826
- var DeviceOverrideMirror = class {
827
- deps;
828
- staleMs;
829
- missCooldownMs;
830
- entries = /* @__PURE__ */ new Map();
831
- /** In-flight reads, so a burst of frames on a cold device costs ONE read and
832
- * not one per frame — the exact failure this class exists to remove. */
833
- inFlight = /* @__PURE__ */ new Map();
834
- now;
835
- constructor(deps, staleMs = DEVICE_OVERRIDE_STALE_MS, missCooldownMs = DEVICE_OVERRIDE_MISS_COOLDOWN_MS) {
836
- this.deps = deps;
837
- this.staleMs = staleMs;
838
- this.missCooldownMs = missCooldownMs;
839
- this.now = deps.now ?? (() => Date.now());
840
- }
841
- /**
842
- * The device's overrides.
843
- *
844
- * COLD awaits — the first frame on a camera has to get the real answer, and a
845
- * default is exactly what "the operator lowered this threshold" looks like.
846
- * WARM returns immediately, stale or not, and starts the refresh behind the
847
- * caller.
848
- */
849
- async resolve(deviceId) {
850
- const entry = this.entries.get(deviceId);
851
- if (entry === void 0) return this.load(deviceId);
852
- if (this.now() - entry.fetchedAt >= this.staleMs) this.load(deviceId);
853
- return entry.value;
854
- }
855
- /** Drop a device's entry. Called by every writer in this process, so an
856
- * operator's own change is never served from the mirror. */
857
- invalidate(deviceId) {
858
- this.entries.delete(deviceId);
859
- }
860
- invalidateAll() {
861
- this.entries.clear();
862
- }
863
- async load(deviceId) {
864
- const pending = this.inFlight.get(deviceId);
865
- if (pending !== void 0) return pending;
866
- const run = this.deps.read(deviceId).then((value) => {
867
- this.entries.set(deviceId, {
868
- value,
869
- fetchedAt: this.now()
870
- });
871
- return value;
872
- }).catch((err) => {
873
- this.deps.onRefreshFailed?.(deviceId, err);
874
- const existing = this.entries.get(deviceId);
875
- if (existing !== void 0) {
876
- existing.fetchedAt = this.now() - this.staleMs + this.missCooldownMs;
877
- return existing.value;
878
- }
879
- return {};
880
- }).finally(() => {
881
- this.inFlight.delete(deviceId);
882
- });
883
- this.inFlight.set(deviceId, run);
884
- return run;
885
- }
886
- };
887
- //#endregion
888
694
  //#region src/detection-pipeline/engine/pool-worker-health.ts
889
695
  /**
890
696
  * The one table of load bounds (D653, fix round 1).
@@ -4229,6 +4035,29 @@ function benchmarkKeyOf(deviceKey) {
4229
4035
  function isBenchmarkRun(input) {
4230
4036
  return input.frame === void 0 && input.frameRef === void 0 && input.deviceId === void 0 && input.replay !== true;
4231
4037
  }
4038
+ /**
4039
+ * Backends whose device opens in ONE process at a time. libedgetpu holds an
4040
+ * Edge TPU exclusively for the process that opened it, so a second pool on a
4041
+ * Coral the cameras' pool already holds cannot open it at all — every
4042
+ * benchmark and auto-benchmark (D913) of an in-use Coral failed that way on
4043
+ * the dev hub (1.2.619). Every other backend this node builds (OpenVINO,
4044
+ * CoreML, CUDA, ONNX, CPU) shares its device between processes.
4045
+ */
4046
+ var EXCLUSIVE_BACKENDS = new Set(["edgetpu"]);
4047
+ /** True when the device behind `deviceKey` (`<backend>:<device>`) cannot be opened by a second pool. */
4048
+ function isExclusiveDevice(deviceKey) {
4049
+ const colon = deviceKey.indexOf(":");
4050
+ const backend = colon >= 0 ? deviceKey.slice(0, colon) : deviceKey;
4051
+ return EXCLUSIVE_BACKENDS.has(backend);
4052
+ }
4053
+ /**
4054
+ * A benchmark on an EXCLUSIVE device is the one benchmark that goes to the
4055
+ * live pool: the separate pool could not open the device, so the choice is
4056
+ * between measuring through the cameras' pool and not measuring at all.
4057
+ */
4058
+ function benchmarkPlacementOf(benchmark, deviceKey) {
4059
+ return benchmark && !isExclusiveDevice(deviceKey) ? "benchmark-pool" : "live-pool";
4060
+ }
4232
4061
  var BenchmarkPools = class {
4233
4062
  options;
4234
4063
  pools = /* @__PURE__ */ new Map();
@@ -4809,14 +4638,15 @@ var PRIMARY_ROOT_STEP = "object-detection";
4809
4638
  /**
4810
4639
  * The same rule over an EXECUTABLE tree's roots (D658): their model ids are the
4811
4640
  * pool variants that will run — after resolution, per-step loads and drops —
4812
- * so this is what a live `runPipeline` stamps. The tree holds only enabled
4813
- * video roots already.
4814
- */
4815
- function resolveTreeRootModelId(roots) {
4816
- return primaryRootModelId(roots);
4817
- }
4818
- function primaryRootModelId(roots) {
4819
- return (roots.find((s) => s.stepId === PRIMARY_ROOT_STEP) ?? roots[0])?.modelId;
4641
+ * so this is what a live `runPipeline` stamps, with the root's step id beside
4642
+ * the model (D925). The tree holds only enabled video roots already.
4643
+ */
4644
+ function resolveTreeRoot(roots) {
4645
+ const root = roots.find((s) => s.stepId === PRIMARY_ROOT_STEP) ?? roots[0];
4646
+ return root === void 0 ? void 0 : {
4647
+ stepId: root.stepId,
4648
+ modelId: root.modelId
4649
+ };
4820
4650
  }
4821
4651
  //#endregion
4822
4652
  //#region src/detection-pipeline/engine-provisioner.ts
@@ -7872,16 +7702,6 @@ function collectSchemaDefaults(schema) {
7872
7702
  walkFieldsForDefaults(schema, out);
7873
7703
  return out;
7874
7704
  }
7875
- /**
7876
- * The operator settings of `stepId` as the model that RUNS sees them (D738):
7877
- * a model-bound value made for another model is dropped and binding keys are
7878
- * stripped. For a settings layer merged AFTER the tree is built (the
7879
- * executor's per-device store, `applyDeviceOverridesToTree`), so it obeys the
7880
- * same binding as the layers `buildNode` merges.
7881
- */
7882
- function resolveOperatorSettingsForModel(stepId, settings, runningModelId) {
7883
- return require_dist.applyModelBoundSettings(settings, require_dist.collectModelBoundKeys(require_custom_model_catalog.getStep(stepId).getConfigSchema()), runningModelId) ?? {};
7884
- }
7885
7705
  function walkFieldsForDefaults(fields, out) {
7886
7706
  for (const field of fields) {
7887
7707
  if ("type" in field && field.type === "group" && "fields" in field) {
@@ -8570,7 +8390,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8570
8390
  * node-default tree against `this.currentEngine.format`. Recomputed
8571
8391
  * EAGERLY by {@link recomputeConfigIssues} whenever the engine actually
8572
8392
  * changes (see `onEngineSelectionChanged`), rather than relying on a
8573
- * `getGlobalSteps()` call that may never happen on a camera-only node.
8393
+ * `nodeDefaultSteps()` call that may never happen on a camera-only node.
8574
8394
  * Merged into {@link getEngineProvisioning}'s snapshot; never consulted by
8575
8395
  * `assertEngineReady` — this gate SURFACES, it does not block inference.
8576
8396
  */
@@ -8672,20 +8492,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8672
8492
  readStore;
8673
8493
  /** Write a patch to the addon settings store. */
8674
8494
  writeStore;
8675
- /** Read per-device settings. */
8676
- readDeviceStore;
8677
- /** Delete all per-device settings for a device (used by `clearDeviceOverrides`). */
8678
- clearDeviceStore;
8679
- /** Merge a patch into one device's settings (Auto tune' keep-the-rest rewrite, D745). */
8680
- writeDeviceStore;
8681
- /**
8682
- * The per-device overrides the FRAME path reads, kept fresh off that path.
8683
- *
8684
- * Public so the addon class can drop a device on its own
8685
- * `updateDeviceSettings` — the write and the mirror have to be the same
8686
- * process's, or the mirror becomes the second authority D224 forbids.
8687
- */
8688
- deviceOverrides;
8689
8495
  constructor(settings, modelsDir, logger, eventBus = null, detectionConfigSchemaSource = null, executorOptions = {}) {
8690
8496
  this.modelsDir = modelsDir;
8691
8497
  this.eventBus = eventBus;
@@ -8694,18 +8500,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
8694
8500
  this.log = logger;
8695
8501
  this.readStore = () => settings.readAddonStore();
8696
8502
  this.writeStore = (patch) => settings.writeAddonStore(patch);
8697
- this.readDeviceStore = settings.readDeviceStore ?? (async () => ({}));
8698
- const clearDeviceStore = settings.clearDeviceStore;
8699
- this.clearDeviceStore = clearDeviceStore ? (deviceId) => clearDeviceStore.call(settings, deviceId) : async () => void 0;
8700
- const writeDeviceStore = settings.writeDeviceStore;
8701
- this.writeDeviceStore = writeDeviceStore ? (deviceId, patch) => writeDeviceStore.call(settings, deviceId, patch) : async () => void 0;
8702
- this.deviceOverrides = new DeviceOverrideMirror({
8703
- read: (deviceId) => this.readDeviceStore(deviceId),
8704
- onRefreshFailed: (deviceId, err) => this.log.warn("per-device detection overrides refresh failed — serving the last value", {
8705
- tags: { deviceId },
8706
- meta: { error: require_dist.errMsg(err) }
8707
- })
8708
- });
8709
8503
  this.currentEngine = ONNX_FLOOR;
8710
8504
  this.log.info("Engine pick pending (placeholder until probe / persisted selection)", { meta: {
8711
8505
  runtime: this.currentEngine.runtime,
@@ -9008,7 +8802,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9008
8802
  * the node-default step tree for `this.currentEngine.format` and compute
9009
8803
  * human-readable config issues from the result:
9010
8804
  * - model substitutions (`getActiveModelSubstitutions()`, populated as a
9011
- * side effect of the `getGlobalSteps()` re-resolve below) — a step's
8805
+ * side effect of the `nodeDefaultSteps()` re-resolve below) — a step's
9012
8806
  * chosen model lacks a build for this format, so the format default
9013
8807
  * runs instead (degraded but loadable).
9014
8808
  * - zero-build steps (`collectZeroBuildIssues`) — a step whose FULL
@@ -9023,7 +8817,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9023
8817
  async recomputeConfigIssues() {
9024
8818
  this.configIssues = [];
9025
8819
  try {
9026
- const steps = await this.getGlobalSteps();
8820
+ const steps = await this.nodeDefaultSteps();
9027
8821
  if (!steps) return;
9028
8822
  const format = this.currentEngine.format;
9029
8823
  const substitutionIssues = this.getActiveModelSubstitutions().map((s) => `${s.addonId}: chose ${s.chosen}, running ${s.running} (${s.format})`);
@@ -9071,7 +8865,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9071
8865
  * `format` for the download fan-out.
9072
8866
  */
9073
8867
  async ensureModelsForCurrentSteps(format) {
9074
- const steps = await this.getGlobalSteps();
8868
+ const steps = await this.nodeDefaultSteps();
9075
8869
  if (!steps || steps.length === 0) return;
9076
8870
  for (const step of flattenSteps(steps)) {
9077
8871
  if (!step.enabled) continue;
@@ -9244,10 +9038,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9244
9038
  defaultDevice: e.engine.device
9245
9039
  };
9246
9040
  }
9247
- async getDefaultSteps(engine) {
9248
- const detectionModelId = require_custom_model_catalog.resolveDefaultDetectionModel(engine.backend, engine.device ?? null, engine.format);
9249
- return buildDefaultStepTree(engine.format, detectionModelId);
9250
- }
9251
9041
  /**
9252
9042
  * Resolve every step's model for this node's engine format via the shared
9253
9043
  * `resolveModelForFormat`, and surface any substitution. `step.engine?.format`
@@ -9290,18 +9080,19 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9290
9080
  return [...this.modelSubstitutions.values()];
9291
9081
  }
9292
9082
  /**
9293
- * PURE node-default step tree — computed on demand, never persisted.
9083
+ * PURE node-default step tree — computed on demand, never persisted, and
9084
+ * PRIVATE (D927): it is not the model any camera ran, so no caller outside
9085
+ * this provider may read it. It serves Gate A's config-issue probe and the
9086
+ * engine provisioning's model download.
9294
9087
  *
9295
9088
  * The object-detection default is hardware-aware (this node's resolved
9296
9089
  * backend + device via `resolveDefaultDetectionModel`); every other step
9297
9090
  * keeps its flat per-format default. The result is cached in
9298
9091
  * `currentSteps` only as a memo — the cache is dropped whenever the
9299
9092
  * engine (hence model format) changes, so a re-read always reflects the
9300
- * live engine. Nothing in the live per-camera dispatch path reads this:
9301
- * it exists as the benchmark fallback (empty input steps), the Gate-A
9302
- * config-issue probe, and the "effective post-reset model" probe.
9093
+ * live engine.
9303
9094
  */
9304
- async getGlobalSteps() {
9095
+ async nodeDefaultSteps() {
9305
9096
  if (this.currentSteps) return this.currentSteps;
9306
9097
  const detectionModelId = require_custom_model_catalog.resolveDefaultDetectionModel(this.currentEngine.backend, this.currentEngine.device ?? null, this.currentEngine.format);
9307
9098
  const defaults = buildDefaultStepTree(this.currentEngine.format, detectionModelId);
@@ -9309,10 +9100,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9309
9100
  this.currentSteps = this.resolveStepModels(defaults);
9310
9101
  return this.currentSteps;
9311
9102
  }
9312
- async getGlobalPipelineConfig() {
9313
- if (!this.currentSteps) return null;
9314
- return stepsToPipelineConfig(this.currentSteps, this.currentEngine);
9315
- }
9316
9103
  /**
9317
9104
  * Task G5 (Gate B) — pre-init config-correctness gate. PURE COMPUTE
9318
9105
  * against `this.currentEngine.format`: resolves `steps` the same way the
@@ -9895,7 +9682,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9895
9682
  this.executor = executor;
9896
9683
  const dispatchEngine = dispatchDeviceKey ? require_custom_model_catalog.resolveDeviceEngine(dispatchDeviceKey) : runEngine;
9897
9684
  const benchmark = isBenchmarkRun(input);
9898
- const dispatchFactory = benchmark ? await this.benchmarkPools.acquire(require_custom_model_catalog.deviceKeyOf(dispatchEngine), dispatchEngine) : dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
9685
+ const placement = benchmarkPlacementOf(benchmark, require_custom_model_catalog.deviceKeyOf(dispatchEngine));
9686
+ if (benchmark && placement === "live-pool") this.log.info("Benchmark on an exclusive device — served by the live pool", { meta: { deviceKey: require_custom_model_catalog.deviceKeyOf(dispatchEngine) } });
9687
+ const dispatchFactory = placement === "benchmark-pool" ? await this.benchmarkPools.acquire(require_custom_model_catalog.deviceKeyOf(dispatchEngine), dispatchEngine) : dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
9899
9688
  const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
9900
9689
  let runSteps = benchmarkSteps;
9901
9690
  const loadPolicy = dispatchLoadPolicyOf(input);
@@ -9936,21 +9725,21 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
9936
9725
  }, dispatchDeviceKeyLabel, [model], model, "not-warm", require_dist.errMsg(failure.error));
9937
9726
  }
9938
9727
  }
9939
- const rootModelId = resolveTreeRootModelId(tree.roots);
9728
+ const treeRoot = resolveTreeRoot(tree.roots);
9940
9729
  const rootDeviceKey = require_custom_model_catalog.deviceKeyOf(dispatchEngine);
9941
- const stampRoot = (r) => rootModelId === void 0 ? {
9730
+ const stampRoot = (r) => treeRoot === void 0 ? {
9942
9731
  ...r,
9943
9732
  rootDeviceKey
9944
9733
  } : {
9945
9734
  ...r,
9946
- rootModelId,
9735
+ rootModelId: treeRoot.modelId,
9736
+ rootStepId: treeRoot.stepId,
9947
9737
  rootDeviceKey
9948
9738
  };
9949
9739
  if (input.replay !== true) this.clusterModelSwitchLog.note(input.deviceId ?? 0, tree);
9950
9740
  setupMs = performance.now() - wallT0 - decodeMs;
9951
9741
  const effectiveDeviceId = input.deviceId ?? 0;
9952
- const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
9953
- const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
9742
+ const effectiveTree = tree;
9954
9743
  const nativeCropProvider = this.buildNativeCropProviderFromRef(input.nativeCropRef) ?? this.buildNativeFaceCropProvider(input.frameHandle);
9955
9744
  let cropZoneBbox;
9956
9745
  if (appliesCameraGates && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
@@ -11285,102 +11074,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
11285
11074
  this.log.info("videoPipelineSteps updated", { meta: { addons: Object.keys(sanitized).length } });
11286
11075
  return { success: true };
11287
11076
  }
11288
- /**
11289
- * Clear THIS node's executor-side PER-DEVICE settings stores — the
11290
- * per-camera step overrides the object-detection root reads via
11291
- * `applyDeviceOverridesToTree`.
11292
- *
11293
- * Node-routed (the cap's default `nodeIdMode: 'routing'`): the generated
11294
- * cap-router strips `nodeId` and dispatches to the target node, so this
11295
- * method runs ON the node being cleared and takes no argument.
11296
- *
11297
- * This is the slimmed executor leg of the orchestrator's
11298
- * `resetNodePipelineDefaults` flow. The legacy `resetToDefault` — which
11299
- * also reset a persisted GLOBAL step-tree seed nothing in the live
11300
- * per-camera path read — was removed together with that seed
11301
- * (`getGlobalSteps` is pure now, so there is nothing node-global left to
11302
- * reset here).
11303
- *
11304
- * Idempotent: clearing already-empty per-device stores is a no-op.
11305
- */
11306
- async clearDeviceOverrides() {
11307
- const clearedDevices = await this.clearAllDeviceOverrides();
11308
- this.log.info("executor per-device overrides cleared", { meta: { clearedDevices } });
11309
- return {
11310
- success: true,
11311
- clearedDevices
11312
- };
11313
- }
11314
- /**
11315
- * Auto tune (D745): list — or, unless `dryRun`, remove — the model-bound
11316
- * thresholds of the per-camera detector store. See
11317
- * `model-bound-device-overrides.ts`.
11318
- */
11319
- async clearModelBoundDeviceOverrides(input) {
11320
- const result = await clearModelBoundDeviceOverrides({
11321
- listDeviceIds: async () => {
11322
- const api = this.addonCtx?.api;
11323
- if (!api) throw new Error("clearModelBoundDeviceOverrides: ctx.api unavailable");
11324
- return (await api.deviceManager.listAll.query({ projection: "slim" })).map((d) => d.id);
11325
- },
11326
- readDeviceStore: (deviceId) => this.readDeviceStore(deviceId),
11327
- clearDeviceStore: (deviceId) => this.clearDeviceStore(deviceId),
11328
- writeDeviceStore: (deviceId, patch) => this.writeDeviceStore(deviceId, patch),
11329
- invalidate: (deviceId) => this.deviceOverrides.invalidate(deviceId),
11330
- boundKeys: require_dist.collectModelBoundKeys(require_custom_model_catalog.getStep("object-detection").getConfigSchema())
11331
- }, input);
11332
- if (!input.dryRun) for (const entry of result.entries) this.log.info("auto tune: per-camera detector thresholds cleared", {
11333
- tags: { deviceId: entry.deviceId },
11334
- meta: {
11335
- deviceId: entry.deviceId,
11336
- keys: entry.keys
11337
- }
11338
- });
11339
- for (const r of result.refused) this.log.warn("auto tune: per-camera detector thresholds not cleared", {
11340
- tags: { deviceId: r.deviceId },
11341
- meta: {
11342
- deviceId: r.deviceId,
11343
- reason: r.reason,
11344
- dryRun: input.dryRun
11345
- }
11346
- });
11347
- return result;
11348
- }
11349
- /**
11350
- * Clear the per-device pipeline-step overrides for every known device.
11351
- * Returns the count of devices whose store was cleared. There is no
11352
- * bulk-list primitive over the per-device store, so devices are
11353
- * enumerated via the device-manager and cleared one by one
11354
- * (`clearDeviceStore` is idempotent — a device with no overrides is a
11355
- * no-op). Never throws: a device-manager outage degrades to "cleared
11356
- * nothing" so the reset flow still completes.
11357
- */
11358
- async clearAllDeviceOverrides() {
11359
- const api = this.addonCtx?.api;
11360
- if (!api) return 0;
11361
- let devices = [];
11362
- try {
11363
- devices = await api.deviceManager.listAll.query({ projection: "slim" });
11364
- } catch (err) {
11365
- this.log.warn("clearDeviceOverrides: device enumeration failed — skipping per-device clear", { meta: { error: require_dist.errMsg(err) } });
11366
- return 0;
11367
- }
11368
- let cleared = 0;
11369
- for (const device of devices) try {
11370
- await this.clearDeviceStore(device.id);
11371
- this.deviceOverrides.invalidate(device.id);
11372
- cleared++;
11373
- } catch (err) {
11374
- this.log.warn("clearDeviceOverrides: failed to clear per-device overrides for device", {
11375
- tags: { deviceId: device.id },
11376
- meta: {
11377
- deviceId: device.id,
11378
- error: require_dist.errMsg(err)
11379
- }
11380
- });
11381
- }
11382
- return cleared;
11383
- }
11384
11077
  async listLoadedEngines() {
11385
11078
  const out = [];
11386
11079
  if (this.engineFactory) {
@@ -11627,7 +11320,6 @@ function buildSchemaSlots(formats, modelsDir, customByStep) {
11627
11320
  defaultModelId: step.defaultModelId,
11628
11321
  ...step.defaultModelIdByFormat ? { defaultModelIdByFormat: { ...step.defaultModelIdByFormat } } : {},
11629
11322
  ...step.enabledByDefault !== void 0 ? { enabledByDefault: step.enabledByDefault } : {},
11630
- ...step.backfillIntoExistingOverrides !== void 0 ? { backfillIntoExistingOverrides: step.backfillIntoExistingOverrides } : {},
11631
11323
  defaultConfidence: step.defaultConfidence,
11632
11324
  ...step.group ? { group: step.group } : {},
11633
11325
  ...configSchemaWithRoots.length > 0 ? { configSchema: configSchemaWithRoots } : {}
@@ -11675,7 +11367,7 @@ function resolveChildSlots(slot) {
11675
11367
  }
11676
11368
  /**
11677
11369
  * V2 default: the node-default step tree (never persisted — see
11678
- * `getGlobalSteps`). The core detection + recognition steps are enabled; every
11370
+ * `nodeDefaultSteps`). The core detection + recognition steps are enabled; every
11679
11371
  * optional step is present with its catalog `enabledByDefault` (the animal and
11680
11372
  * vehicle classifiers on — their answer comes from the root model when it
11681
11373
  * declares them, D736 — the bird classifier and segmentation off).
@@ -11717,27 +11409,6 @@ function buildDefaultStepTree(format, objectDetectionModelId) {
11717
11409
  if (audioStep) steps.push(audioStep);
11718
11410
  return steps;
11719
11411
  }
11720
- function stepsToPipelineConfig(steps, engine) {
11721
- const toNode = (step) => ({
11722
- step: step.addonId,
11723
- addon: step.addonId,
11724
- configOverride: {
11725
- modelId: step.modelId,
11726
- ...step.settings
11727
- },
11728
- children: step.children.map(toNode)
11729
- });
11730
- const videoSteps = steps.filter((s) => s.slot !== "audio-classifier");
11731
- const audioStep = steps.find((s) => s.slot === "audio-classifier" && s.enabled);
11732
- return {
11733
- engine,
11734
- video: videoSteps.map(toNode),
11735
- ...audioStep ? {
11736
- audio: toNode(audioStep),
11737
- audioEngine: engine
11738
- } : {}
11739
- };
11740
- }
11741
11412
  /** Flatten a step tree into a flat list (DFS) — delegates to shared utility. */
11742
11413
  function flattenSteps(steps) {
11743
11414
  return flattenEnabledVideoSteps(steps);
@@ -11798,22 +11469,6 @@ function availableFormatsForStep(addonId) {
11798
11469
  return [];
11799
11470
  }
11800
11471
  }
11801
- /**
11802
- * Return a shallow-cloned executable tree with `rootStepId`'s `settings`
11803
- * merged with `overrides` (per-device values win). Non-matching root steps
11804
- * pass through unchanged. Used by `runFrame` to apply per-device detection
11805
- * settings without rebuilding the tree per frame. A model-bound value (D738)
11806
- * applies only while the model it was made for is the one the root RUNS.
11807
- */
11808
- function applyDeviceOverridesToTree(tree, rootStepId, overrides) {
11809
- return { roots: tree.roots.map((root) => root.stepId === rootStepId ? {
11810
- ...root,
11811
- settings: {
11812
- ...root.settings,
11813
- ...resolveOperatorSettingsForModel(rootStepId, overrides, root.modelId)
11814
- }
11815
- } : root) };
11816
- }
11817
11472
  //#endregion
11818
11473
  //#region src/detection-pipeline/python-readiness.ts
11819
11474
  /**
@@ -11928,20 +11583,6 @@ function isProfileSliderKey(key) {
11928
11583
  }
11929
11584
  var DetectionPipelineAddon = class extends require_dist.BaseAddon {
11930
11585
  provider = null;
11931
- /**
11932
- * An operator's own write drops the mirror the frame path reads.
11933
- *
11934
- * This override is the half of `device-override-mirror.ts` that keeps it from
11935
- * being the second authority D224 forbids: `BaseAddon.updateDeviceSettings`
11936
- * writes THIS addon's `<addonId>:addon-devices` row, so the write and the
11937
- * mirror are in one process and the write invalidates. Without it the
11938
- * operator's own change would be the one read the mirror got wrong, which is
11939
- * the exact failure D224 records.
11940
- */
11941
- async updateDeviceSettings(deviceId, patch) {
11942
- await super.updateDeviceSettings(deviceId, patch);
11943
- this.provider?.deviceOverrides.invalidate(deviceId);
11944
- }
11945
11586
  /** Last non-null probed hardware PER NODE — reused when the probe transiently
11946
11587
  * returns null so offered backends / device lists don't collapse. Keyed by
11947
11588
  * node because the hub addon probes other nodes when serving their config.