@camstack/addon-pipeline 1.1.0 → 1.1.1

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 (34) hide show
  1. package/dist/audio-analyzer/index.js +104 -29
  2. package/dist/audio-analyzer/index.mjs +100 -25
  3. package/dist/audio-codec-nodeav/index.js +1 -1
  4. package/dist/audio-codec-nodeav/index.mjs +1 -1
  5. package/dist/decoder-nodeav/index.js +1 -1
  6. package/dist/decoder-nodeav/index.mjs +1 -1
  7. package/dist/detection-pipeline/index.js +187 -56
  8. package/dist/detection-pipeline/index.mjs +175 -44
  9. package/dist/{dist-BA6DR_jV.mjs → dist-BWc-HYQz.mjs} +194 -1
  10. package/dist/{dist-BLcTVvol.js → dist-DnD2tm7T.js} +194 -1
  11. package/dist/{model-download-service-RxAOiYvX-CMAvhgO7.mjs → model-download-service-C-IHWnXx-3Mmeob3l.mjs} +1 -1
  12. package/dist/{model-download-service-RxAOiYvX-C8rTRJy_.js → model-download-service-C-IHWnXx-BnQ_awK4.js} +1 -1
  13. package/dist/motion-wasm/index.js +1 -1
  14. package/dist/motion-wasm/index.mjs +1 -1
  15. package/dist/pipeline-runner/index.js +14 -10
  16. package/dist/pipeline-runner/index.mjs +14 -10
  17. package/dist/recorder/index.js +4 -4
  18. package/dist/recorder/index.mjs +2 -2
  19. package/dist/stream-broker/_stub.js +1 -1
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrohyZ5L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Do7lgO8N.mjs} +3 -3
  21. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-FRD2eBuz.mjs +26 -0
  22. package/dist/stream-broker/{hostInit-zLZbYJcg.mjs → hostInit-D5y5VMK8.mjs} +3 -3
  23. package/dist/stream-broker/index.js +8 -8
  24. package/dist/stream-broker/index.mjs +2 -2
  25. package/dist/stream-broker/remoteEntry.js +1 -1
  26. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DJ7ztnFv.js → MaskShapeCanvas-DI4BY7W2-5UPreLSr.js} +1 -1
  27. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CQzEnQoq.js → MotionZonesSettings-NcxxQN8r-Bxqs-CpZ.js} +1 -1
  28. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-Cl0eOy_U.js → PrivacyMaskSettings-APgPLF7p-BDMPeMJd.js} +1 -1
  29. package/embed-dist/assets/{index-CSuLwWK-.js → index-BgGwqHYl.js} +9 -9
  30. package/embed-dist/index.html +1 -1
  31. package/package.json +1 -1
  32. package/python/postprocessors/saliency.py +47 -1
  33. package/python/postprocessors/test_saliency.py +23 -0
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-COa17XL2.mjs +0 -26
@@ -2,17 +2,17 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_model_download_service_RxAOiYvX = require("../model-download-service-RxAOiYvX-C8rTRJy_.js");
6
- const require_dist = require("../dist-BLcTVvol.js");
5
+ const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BnQ_awK4.js");
6
+ const require_dist = require("../dist-DnD2tm7T.js");
7
7
  let node_fs = require("node:fs");
8
- node_fs = require_model_download_service_RxAOiYvX.__toESM(node_fs);
8
+ node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
9
9
  let node_path = require("node:path");
10
- node_path = require_model_download_service_RxAOiYvX.__toESM(node_path);
10
+ node_path = require_model_download_service_C_IHWnXx.__toESM(node_path);
11
11
  let node_os = require("node:os");
12
- node_os = require_model_download_service_RxAOiYvX.__toESM(node_os);
12
+ node_os = require_model_download_service_C_IHWnXx.__toESM(node_os);
13
13
  let node_child_process = require("node:child_process");
14
14
  let sharp = require("sharp");
15
- sharp = require_model_download_service_RxAOiYvX.__toESM(sharp);
15
+ sharp = require_model_download_service_C_IHWnXx.__toESM(sharp);
16
16
  //#region src/detection-pipeline/engine-store-keys.ts
17
17
  /**
18
18
  * Per-node scoping for the detection-pipeline engine cascade.
@@ -1043,6 +1043,26 @@ var ovFormat = (url, sizeMB) => {
1043
1043
  ...files ? { files } : {}
1044
1044
  };
1045
1045
  };
1046
+ /**
1047
+ * Build a precision-variant catalog entry (OpenVINO-only) derived from a base
1048
+ * detection model. fp16 halves the weights (Intel iGPU/NPU sweet spot); int8 is
1049
+ * NNCF post-training-quantized (~4× smaller, fastest on CPU/iGPU at a small
1050
+ * accuracy cost). The IRs live next to the base `.xml` on HF as
1051
+ * `camstack-<id>-<precision>.xml`. Lets an operator scale the model to the node
1052
+ * (e.g. yolo26x-int8 on a 265K, yolo26n-int8 on an N100).
1053
+ */
1054
+ var ovPrecisionVariant = (baseId, ovDir, baseName, precision, sizeMB) => ({
1055
+ id: `${baseId}-${precision}`,
1056
+ name: `${baseName} (${precision.toUpperCase()})`,
1057
+ description: `${baseName} — OpenVINO ${precision.toUpperCase()} variant for Intel iGPU/NPU; scale by hardware`,
1058
+ inputSize: {
1059
+ width: 640,
1060
+ height: 640
1061
+ },
1062
+ labels: [],
1063
+ preprocessMode: "letterbox",
1064
+ formats: { openvino: ovFormat(hf(`${ovDir}/camstack-${baseId}-${precision}.xml`), sizeMB) }
1065
+ });
1046
1066
  var MLPACKAGE_FILES = [
1047
1067
  "Manifest.json",
1048
1068
  "Data/com.apple.CoreML/model.mlmodel",
@@ -1348,7 +1368,21 @@ var OBJECT_DETECTION_MODELS = [
1348
1368
  },
1349
1369
  openvino: ovFormat(hf("objectDetection/scrypted-yolov9-relu/openvino/scrypted_yolov9m_relu.xml"), 38)
1350
1370
  }
1351
- }
1371
+ },
1372
+ ovPrecisionVariant("yolov9t", "objectDetection/yolov9/openvino", "YOLOv9 Tiny", "fp16", 5),
1373
+ ovPrecisionVariant("yolov9t", "objectDetection/yolov9/openvino", "YOLOv9 Tiny", "int8", 3),
1374
+ ovPrecisionVariant("yolov9s", "objectDetection/yolov9/openvino", "YOLOv9 Small", "fp16", 15),
1375
+ ovPrecisionVariant("yolov9s", "objectDetection/yolov9/openvino", "YOLOv9 Small", "int8", 8),
1376
+ ovPrecisionVariant("yolo26n", "objectDetection/yolo26/openvino", "YOLO26 Nano", "fp16", 5),
1377
+ ovPrecisionVariant("yolo26n", "objectDetection/yolo26/openvino", "YOLO26 Nano", "int8", 3),
1378
+ ovPrecisionVariant("yolo26s", "objectDetection/yolo26/openvino", "YOLO26 Small", "fp16", 19),
1379
+ ovPrecisionVariant("yolo26s", "objectDetection/yolo26/openvino", "YOLO26 Small", "int8", 10),
1380
+ ovPrecisionVariant("yolo26m", "objectDetection/yolo26/openvino", "YOLO26 Medium", "fp16", 41),
1381
+ ovPrecisionVariant("yolo26m", "objectDetection/yolo26/openvino", "YOLO26 Medium", "int8", 21),
1382
+ ovPrecisionVariant("yolo26l", "objectDetection/yolo26/openvino", "YOLO26 Large", "fp16", 50),
1383
+ ovPrecisionVariant("yolo26l", "objectDetection/yolo26/openvino", "YOLO26 Large", "int8", 25),
1384
+ ovPrecisionVariant("yolo26x", "objectDetection/yolo26/openvino", "YOLO26 XLarge", "fp16", 112),
1385
+ ovPrecisionVariant("yolo26x", "objectDetection/yolo26/openvino", "YOLO26 XLarge", "int8", 56)
1352
1386
  ];
1353
1387
  var FACE_DETECTION_MODELS = [{
1354
1388
  id: "scrfd-2.5g",
@@ -1795,7 +1829,7 @@ var ObjectDetectionStep = class {
1795
1829
  "animal"
1796
1830
  ],
1797
1831
  models: [...OBJECT_DETECTION_MODELS],
1798
- defaultModelId: "yolov9s",
1832
+ defaultModelId: "yolo26n",
1799
1833
  defaultConfidence: .5,
1800
1834
  labels: require_dist.COCO_80_LABELS.map((l) => l.id),
1801
1835
  classMap: require_dist.COCO_TO_MACRO
@@ -2044,9 +2078,9 @@ var STEP_VEHICLE_CLASSIFIER = new ClassifierWithMinConfidence({
2044
2078
  enabledByDefault: false,
2045
2079
  defaultConfidence: .3
2046
2080
  });
2047
- var STEP_SEGMENTATION_REFINER = new PipelineStepBase({
2048
- id: "segmentation-refiner",
2049
- name: "Saliency Segmentation",
2081
+ var STEP_SEGMENTATION = new PipelineStepBase({
2082
+ id: "segmentation",
2083
+ name: "Segmentation",
2050
2084
  slot: "refiner",
2051
2085
  postprocessor: "saliency",
2052
2086
  extractMode: "crop-roi",
@@ -2058,7 +2092,7 @@ var STEP_SEGMENTATION_REFINER = new PipelineStepBase({
2058
2092
  defaultConfidence: 0,
2059
2093
  group: "Segmentation"
2060
2094
  });
2061
- var STEP_INSTANCE_SEGMENTATION = new PipelineStepBase({
2095
+ new PipelineStepBase({
2062
2096
  id: "instance-segmentation",
2063
2097
  name: "Instance Segmentation",
2064
2098
  slot: "refiner",
@@ -2085,8 +2119,7 @@ var ALL_PIPELINE_STEPS = [
2085
2119
  new AnimalClassifierStep(),
2086
2120
  STEP_BIRD_CLASSIFIER,
2087
2121
  STEP_VEHICLE_CLASSIFIER,
2088
- STEP_SEGMENTATION_REFINER,
2089
- STEP_INSTANCE_SEGMENTATION,
2122
+ STEP_SEGMENTATION,
2090
2123
  STEP_AUDIO_CLASSIFIER_INSTANCE
2091
2124
  ];
2092
2125
  /** Compat: flat array of StepDefinition for existing consumers */
@@ -3257,6 +3290,18 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
3257
3290
  parent.mask = output.mask;
3258
3291
  parent.maskWidth = output.maskWidth;
3259
3292
  parent.maskHeight = output.maskHeight;
3293
+ if (output.maskBbox !== void 0 && output.maskWidth > 0 && output.maskHeight > 0) {
3294
+ const [px1, py1, px2, py2] = parent.bbox;
3295
+ const parentW = px2 - px1;
3296
+ const parentH = py2 - py1;
3297
+ const [mbx, mby, mbw, mbh] = output.maskBbox;
3298
+ parent.refinedBbox = [
3299
+ px1 + mbx / output.maskWidth * parentW,
3300
+ py1 + mby / output.maskHeight * parentH,
3301
+ px1 + (mbx + mbw) / output.maskWidth * parentW,
3302
+ py1 + (mby + mbh) / output.maskHeight * parentH
3303
+ ];
3304
+ }
3260
3305
  break;
3261
3306
  }
3262
3307
  }
@@ -3307,6 +3352,12 @@ function buildFrameResult(input) {
3307
3352
  macroClass: m.macroClass,
3308
3353
  score: m.score,
3309
3354
  bbox,
3355
+ ...m.refinedBbox !== void 0 ? { refinedBbox: bboxTupleToRect(m.refinedBbox) } : {},
3356
+ ...m.mask !== void 0 && m.maskWidth !== void 0 && m.maskHeight !== void 0 ? {
3357
+ mask: m.mask,
3358
+ maskWidth: m.maskWidth,
3359
+ maskHeight: m.maskHeight
3360
+ } : {},
3310
3361
  labels,
3311
3362
  ...m.parentId !== void 0 ? { parentId: m.parentId } : {},
3312
3363
  ...embedding !== void 0 ? {
@@ -3797,6 +3848,40 @@ function walkFieldsForDefaults(fields, out) {
3797
3848
  }
3798
3849
  }
3799
3850
  //#endregion
3851
+ //#region src/detection-pipeline/registry/custom-models.ts
3852
+ /**
3853
+ * Group a flat list of custom-model descriptors (as returned by the
3854
+ * `custom-model-registry` collection cap) into a `stepId → entries` map for
3855
+ * the picker / resolution union. Pure; order within a step preserved.
3856
+ */
3857
+ function groupCustomModelsByStep(descriptors) {
3858
+ const byStep = /* @__PURE__ */ new Map();
3859
+ for (const d of descriptors) {
3860
+ const arr = byStep.get(d.stepId) ?? [];
3861
+ arr.push(d.entry);
3862
+ byStep.set(d.stepId, arr);
3863
+ }
3864
+ return byStep;
3865
+ }
3866
+ /**
3867
+ * Union a step's static catalog models with operator-registered custom
3868
+ * models. On an `id` collision the static catalog entry wins — a custom
3869
+ * model can never shadow a built-in one.
3870
+ *
3871
+ * Pure + side-effect-free so it can be unit-tested in isolation and called
3872
+ * from the (free) `buildSchemaSlots` builder without any addon context.
3873
+ */
3874
+ function mergeCustomModels(staticModels, customModels) {
3875
+ const seen = new Set(staticModels.map((m) => m.id));
3876
+ const merged = [...staticModels];
3877
+ for (const m of customModels) {
3878
+ if (seen.has(m.id)) continue;
3879
+ seen.add(m.id);
3880
+ merged.push(m);
3881
+ }
3882
+ return merged;
3883
+ }
3884
+ //#endregion
3800
3885
  //#region src/detection-pipeline/provider.ts
3801
3886
  /**
3802
3887
  * DetectionPipelineProvider — implements IPipelineExecutorProvider.
@@ -4032,6 +4117,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4032
4117
  /** Addon context — ctx.api resolves lazily (direct caller created after boot) */
4033
4118
  addonCtx = null;
4034
4119
  /**
4120
+ * Short-lived cache of custom models pulled from the `custom-model-registry`
4121
+ * collection cap, grouped by step id. TTL-bounded so the picker / resolution
4122
+ * paths don't issue a cap round-trip on every call. Empty map = no provider
4123
+ * (or a query failure) → behaviour identical to the static-catalog-only path.
4124
+ */
4125
+ customModelsCache = null;
4126
+ /**
4035
4127
  * Per-device {@link DeviceProxy} cache used for zone gating at the
4036
4128
  * runtime path. Reads `state.zones.value` + `state.zoneRules.value`
4037
4129
  * synchronously per frame so detections inside an `exclude` zone
@@ -4360,8 +4452,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4360
4452
  * runtime through installing → verifying → ready.
4361
4453
  */
4362
4454
  async onEngineSelectionChanged() {
4455
+ const prev = this.currentEngine;
4363
4456
  const stored = await this.loadEngine();
4364
4457
  if (stored) this.currentEngine = stored;
4458
+ if ((prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "")) && this.engineFactory) {
4459
+ this.log.info("engine selection changed — rebuilding pool in place", { meta: {
4460
+ from: `${prev.backend}/${prev.device ?? "default"}`,
4461
+ to: `${this.currentEngine.backend}/${this.currentEngine.device ?? "default"}`
4462
+ } });
4463
+ try {
4464
+ await this.engineFactory.dispose();
4465
+ } catch {}
4466
+ this.engineFactory = null;
4467
+ this.executor = null;
4468
+ }
4365
4469
  this.startProvisioningForCurrentEngine();
4366
4470
  }
4367
4471
  /**
@@ -4399,9 +4503,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4399
4503
  if (!steps || steps.length === 0) return;
4400
4504
  for (const step of flattenSteps(steps)) {
4401
4505
  if (!step.enabled) continue;
4402
- const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
4506
+ const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
4403
4507
  if (!modelEntry) continue;
4404
- if (require_model_download_service_RxAOiYvX.isModelDownloaded(this.modelsDir, modelEntry, format)) continue;
4508
+ if (require_model_download_service_C_IHWnXx.isModelDownloaded(this.modelsDir, modelEntry, format)) continue;
4405
4509
  await this.downloadWithRetry(modelEntry, format, 3);
4406
4510
  }
4407
4511
  }
@@ -4477,10 +4581,44 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4477
4581
  return { hardware: null };
4478
4582
  }
4479
4583
  }
4584
+ /**
4585
+ * Pull custom models from the `custom-model-registry` collection cap,
4586
+ * grouped by step id. 5s TTL cache. Fully graceful: when no provider is
4587
+ * registered (or the query fails) it returns an empty map and logs at most
4588
+ * a warning — callers then behave exactly like the static-catalog path.
4589
+ */
4590
+ async getCustomModels() {
4591
+ const now = Date.now();
4592
+ if (this.customModelsCache && now - this.customModelsCache.at < 5e3) return this.customModelsCache.byStep;
4593
+ let byStep = /* @__PURE__ */ new Map();
4594
+ try {
4595
+ const api = this.addonCtx?.api;
4596
+ if (api) {
4597
+ if ((await api.addons.listCapabilityProviders.query({ capName: "custom-model-registry" })).some((p) => p.isActive)) byStep = groupCustomModelsByStep(await api.customModelRegistry.listModels.query());
4598
+ }
4599
+ } catch (err) {
4600
+ this.log.warn("custom-model-registry query failed — using static catalog only", { meta: { error: require_dist.errMsg(err) } });
4601
+ }
4602
+ this.customModelsCache = {
4603
+ at: now,
4604
+ byStep
4605
+ };
4606
+ return byStep;
4607
+ }
4608
+ /**
4609
+ * Resolve a model id within a step to a catalog entry — static catalog
4610
+ * first, then the custom registry. Returns undefined if neither has it.
4611
+ */
4612
+ async resolveModelEntry(addonId, modelId) {
4613
+ const fromCatalog = getStepDefinition(addonId).models.find((m) => m.id === modelId);
4614
+ if (fromCatalog) return fromCatalog;
4615
+ return (await this.getCustomModels()).get(addonId)?.find((m) => m.id === modelId);
4616
+ }
4480
4617
  async getSchema(engine) {
4481
4618
  if (!engine || !engine.runtime) engine = await this.getSelectedEngine();
4482
4619
  const format = engine.format;
4483
- const slots = buildSchemaSlots(format, this.modelsDir);
4620
+ const customByStep = await this.getCustomModels();
4621
+ const slots = buildSchemaSlots(format, this.modelsDir, customByStep);
4484
4622
  const { hardware } = await this.fetchProbeGatingData();
4485
4623
  const env = runtimeEnvFromProcess(toProbedHardware(hardware));
4486
4624
  return {
@@ -4609,7 +4747,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4609
4747
  const formats = {};
4610
4748
  for (const [formatKey, entry] of Object.entries(m.formats)) {
4611
4749
  if (!entry) continue;
4612
- const downloaded = require_model_download_service_RxAOiYvX.isModelDownloaded(this.modelsDir, m, formatKey);
4750
+ const downloaded = require_model_download_service_C_IHWnXx.isModelDownloaded(this.modelsDir, m, formatKey);
4613
4751
  formats[formatKey] = {
4614
4752
  url: entry.url,
4615
4753
  sizeMB: entry.sizeMB,
@@ -4667,7 +4805,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4667
4805
  }
4668
4806
  async downloadModel(input) {
4669
4807
  const { modelId, format, addonId } = input;
4670
- const modelEntry = getStepDefinition(addonId).models.find((m) => m.id === modelId);
4808
+ const modelEntry = await this.resolveModelEntry(addonId, modelId);
4671
4809
  if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${addonId}" catalog`);
4672
4810
  const formatEntry = modelEntry.formats[format];
4673
4811
  if (!formatEntry) throw new Error(`Model "${modelId}" has no ${format} format. Available: ${Object.keys(modelEntry.formats).join(", ")}`);
@@ -4730,9 +4868,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4730
4868
  }
4731
4869
  async deleteModel(input) {
4732
4870
  const { modelId, format, addonId } = input;
4733
- const modelEntry = getStepDefinition(addonId).models.find((m) => m.id === modelId);
4871
+ const modelEntry = await this.resolveModelEntry(addonId, modelId);
4734
4872
  if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${addonId}" catalog`);
4735
- if (!require_model_download_service_RxAOiYvX.deleteModelFromDisk(this.modelsDir, modelEntry, format)) throw new Error(`Model "${modelId}" (${format}) is not downloaded — nothing to delete`);
4873
+ if (!require_model_download_service_C_IHWnXx.deleteModelFromDisk(this.modelsDir, modelEntry, format)) throw new Error(`Model "${modelId}" (${format}) is not downloaded — nothing to delete`);
4736
4874
  this.log.info("Model deleted from disk", { meta: {
4737
4875
  modelId,
4738
4876
  format
@@ -5186,8 +5324,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5186
5324
  const work = (async () => {
5187
5325
  const format = this.currentEngine?.format ?? "onnx";
5188
5326
  for (const step of needed) {
5189
- const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
5190
- if (modelEntry && !require_model_download_service_RxAOiYvX.isModelDownloaded(this.modelsDir, modelEntry, format)) {
5327
+ const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
5328
+ if (modelEntry && !require_model_download_service_C_IHWnXx.isModelDownloaded(this.modelsDir, modelEntry, format)) {
5191
5329
  this.log.info("Downloading model for step", { meta: {
5192
5330
  modelId: step.modelId,
5193
5331
  format,
@@ -5212,7 +5350,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5212
5350
  /** Download a model with retry + exponential backoff */
5213
5351
  async downloadWithRetry(entry, format, maxRetries, onProgress) {
5214
5352
  for (let attempt = 1; attempt <= maxRetries; attempt++) try {
5215
- await require_model_download_service_RxAOiYvX.ensureModel(this.modelsDir, entry, format, onProgress);
5353
+ await require_model_download_service_C_IHWnXx.ensureModel(this.modelsDir, entry, format, onProgress);
5216
5354
  this.log.info("Model downloaded successfully", { meta: { modelId: entry.id } });
5217
5355
  return;
5218
5356
  } catch (err) {
@@ -5681,7 +5819,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5681
5819
  const format = this.currentEngine.format;
5682
5820
  const downloads = [];
5683
5821
  for (const step of flattenSteps(steps)) {
5684
- const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
5822
+ const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
5685
5823
  if (!modelEntry) {
5686
5824
  this.log.warn("Model not found in step catalog — skipping download", { meta: {
5687
5825
  modelId: step.modelId,
@@ -5689,11 +5827,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5689
5827
  } });
5690
5828
  continue;
5691
5829
  }
5692
- if (!require_model_download_service_RxAOiYvX.isModelDownloaded(this.modelsDir, modelEntry, format)) this.log.info("Downloading model", { meta: {
5830
+ if (!require_model_download_service_C_IHWnXx.isModelDownloaded(this.modelsDir, modelEntry, format)) this.log.info("Downloading model", { meta: {
5693
5831
  modelId: step.modelId,
5694
5832
  format
5695
5833
  } });
5696
- downloads.push(require_model_download_service_RxAOiYvX.ensureModel(this.modelsDir, modelEntry, format).then(() => {}));
5834
+ downloads.push(require_model_download_service_C_IHWnXx.ensureModel(this.modelsDir, modelEntry, format).then(() => {}));
5697
5835
  }
5698
5836
  await Promise.all(downloads);
5699
5837
  await this.ensureBackendDeps(this.currentEngine);
@@ -6084,11 +6222,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6084
6222
  }
6085
6223
  }
6086
6224
  };
6087
- function buildSchemaSlots(format, modelsDir) {
6225
+ function buildSchemaSlots(format, modelsDir, customByStep) {
6088
6226
  const slotMap = /* @__PURE__ */ new Map();
6089
6227
  for (const pipelineStep of ALL_PIPELINE_STEPS) {
6090
6228
  const step = pipelineStep.definition;
6091
- const availableModels = step.models.filter((m) => m.formats[format]);
6229
+ const availableModels = mergeCustomModels(step.models, customByStep?.get(step.id) ?? []).filter((m) => m.formats[format]);
6092
6230
  if (availableModels.length === 0) continue;
6093
6231
  const slot = step.slot;
6094
6232
  if (!slotMap.has(slot)) slotMap.set(slot, []);
@@ -6104,7 +6242,7 @@ function buildSchemaSlots(format, modelsDir) {
6104
6242
  id: m.id,
6105
6243
  name: m.name,
6106
6244
  formats: Object.fromEntries(Object.entries(m.formats).map(([f, entry]) => [f, {
6107
- downloaded: require_model_download_service_RxAOiYvX.isModelDownloaded(modelsDir, m, f),
6245
+ downloaded: require_model_download_service_C_IHWnXx.isModelDownloaded(modelsDir, m, f),
6108
6246
  sizeMB: entry.sizeMB
6109
6247
  }]))
6110
6248
  })),
@@ -6193,8 +6331,7 @@ function buildDefaultStepTree(format) {
6193
6331
  makeStep("animal-classifier", [], { enabled: false }),
6194
6332
  makeStep("bird-classifier", [], { enabled: false }),
6195
6333
  makeStep("vehicle-classifier", [], { enabled: false }),
6196
- makeStep("segmentation-refiner", [], { enabled: false }),
6197
- makeStep("instance-segmentation", [], { enabled: false })
6334
+ makeStep("segmentation", [], { enabled: false })
6198
6335
  ].filter((s) => s !== null));
6199
6336
  const audioEngine = getDefaultModelForFormat("audio-classifier", format) === "apple-soundanalysis" ? {
6200
6337
  runtime: "python",
@@ -6484,8 +6621,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6484
6621
  label: "Execution provider",
6485
6622
  options: [...STATIC_BACKEND_OPTIONS],
6486
6623
  default: DEFAULT_CONFIG.engineBackend,
6487
- immediate: true,
6488
- requiresRestart: true
6624
+ immediate: true
6489
6625
  }),
6490
6626
  this.field({
6491
6627
  type: "select",
@@ -6493,8 +6629,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6493
6629
  label: "Hardware device",
6494
6630
  options: [...STATIC_DEFAULT_DEVICE_OPTIONS],
6495
6631
  default: DEFAULT_CONFIG.engineDevice,
6496
- immediate: true,
6497
- requiresRestart: true
6632
+ immediate: true
6498
6633
  })
6499
6634
  ]
6500
6635
  }, {
@@ -6533,8 +6668,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6533
6668
  "onnx",
6534
6669
  "cpu"
6535
6670
  ]
6536
- },
6537
- requiresRestart: true
6671
+ }
6538
6672
  }),
6539
6673
  this.field({
6540
6674
  type: "slider",
@@ -6551,8 +6685,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6551
6685
  showWhen: {
6552
6686
  field: "batchMode",
6553
6687
  notEquals: "none"
6554
- },
6555
- requiresRestart: true
6688
+ }
6556
6689
  }),
6557
6690
  this.field({
6558
6691
  type: "slider",
@@ -6569,8 +6702,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6569
6702
  showWhen: {
6570
6703
  field: "batchMode",
6571
6704
  notEquals: "none"
6572
- },
6573
- requiresRestart: true
6705
+ }
6574
6706
  }),
6575
6707
  this.field({
6576
6708
  type: "slider",
@@ -6583,8 +6715,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6583
6715
  default: void 0,
6584
6716
  showValue: true,
6585
6717
  nullable: true,
6586
- nullLabel: "Auto",
6587
- requiresRestart: true
6718
+ nullLabel: "Auto"
6588
6719
  }),
6589
6720
  this.field({
6590
6721
  type: "slider",
@@ -6597,8 +6728,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6597
6728
  default: void 0,
6598
6729
  showValue: true,
6599
6730
  nullable: true,
6600
- nullLabel: "Auto",
6601
- requiresRestart: true
6731
+ nullLabel: "Auto"
6602
6732
  })
6603
6733
  ]
6604
6734
  }] });
@@ -6971,16 +7101,17 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6971
7101
  return false;
6972
7102
  }
6973
7103
  /**
6974
- * BaseAddon calls `onConfigChanged` after every settings write.
6975
- * Group-runner addons can't honour the schema's `requiresRestart:
6976
- * true` flag (the restart cap returns "process not found" for
6977
- * processes spawned in a kernel group worker). To make tuning
6978
- * changes actually take effect, watch the pool-bound subset and
6979
- * respawn the provider in place when it flips.
6980
- *
6981
- * Engine cascade (`engineRuntime/Backend/Device`) and audio
6982
- * settings don't require this those still rely on the addon
6983
- * lifecycle (engineFactory rebuild on next runPipeline).
7104
+ * BaseAddon calls `onConfigChanged` after every settings write. This is the
7105
+ * SOLE apply path for engine + tuning changes — none of those fields declare
7106
+ * `requiresRestart` anymore (an addon restart re-probes hardware + reloads
7107
+ * every model on every set, which is exactly what we want to avoid). Instead:
7108
+ * - Pool-bound tuning (`concurrency`/`batchMode`/`windowMs`/…) in-place
7109
+ * pool respawn when the snapshot flips (`poolConfigChanged` below).
7110
+ * - Engine cascade (`engineBackend`/`engineDevice`) → the provider's
7111
+ * `onEngineSelectionChanged` disposes the device-bound factory so the next
7112
+ * runPipeline rebuilds the pool on the new selection, in place.
7113
+ * Both apply optimistically the inference gate stays closed for the short
7114
+ * re-spin, frames are dropped (never crashed), no addon bounce.
6984
7115
  */
6985
7116
  async onConfigChanged() {
6986
7117
  await this.refreshNodeEngineFromStore();