@camstack/addon-pipeline 1.1.64 → 1.1.66

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 (42) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/detection-pipeline/index.js +188 -26
  4. package/dist/detection-pipeline/index.mjs +188 -26
  5. package/dist/{dist-BzVZ2I4P.mjs → dist-BpWot5-w.mjs} +13 -55
  6. package/dist/{dist-B2jTt7Lq.js → dist-D5o9fGxq.js} +13 -55
  7. package/dist/motion-wasm/index.js +1 -1
  8. package/dist/motion-wasm/index.mjs +1 -1
  9. package/dist/pipeline-runner/index.js +88 -22
  10. package/dist/pipeline-runner/index.mjs +88 -22
  11. package/dist/recorder/index.js +141 -1
  12. package/dist/recorder/index.mjs +141 -1
  13. package/dist/session-decode/decode-worker-child.js +9 -3
  14. package/dist/session-decode/decode-worker-child.mjs +9 -3
  15. package/dist/{step-definitions-DXkY5Zor.js → step-definitions-81eYyJbW.js} +138 -8
  16. package/dist/{step-definitions-B07M9qhj.mjs → step-definitions-D6R9X0V7.mjs} +138 -8
  17. package/dist/stream-broker/_stub.js +2 -2
  18. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-dTMki_1D.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CIIIVPzB.mjs} +2 -2
  19. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CPbLcza9.mjs +26 -0
  20. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DlALQ_wp.mjs +26 -0
  21. package/dist/stream-broker/{hostInit-BY2oQAEX.mjs → hostInit-CeBNudbY.mjs} +2 -2
  22. package/dist/stream-broker/index.js +51 -3
  23. package/dist/stream-broker/index.mjs +51 -3
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C03fx7Sp.js → MaskShapeCanvas-DI4BY7W2-CbBbcdZb.js} +1 -1
  26. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BTxzn1rQ.js → MotionZonesSettings-NcxxQN8r-aA6zvms_.js} +1 -1
  27. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-GKO8O7rC.js → PrivacyMaskSettings-APgPLF7p-CcGB7iAE.js} +1 -1
  28. package/embed-dist/assets/index-BRG_Fqb5.js +114 -0
  29. package/embed-dist/assets/index-D2pF9Z3W.css +2 -0
  30. package/embed-dist/index.html +2 -2
  31. package/package.json +1 -1
  32. package/python/inference_pool.py +30 -0
  33. package/python/postprocessors/scrfd.py +4 -1
  34. package/python/postprocessors/softmax.py +17 -5
  35. package/python/postprocessors/test_softmax.py +52 -0
  36. package/python/postprocessors/test_yolo_nms.py +45 -0
  37. package/python/postprocessors/yolo.py +4 -1
  38. package/python/postprocessors/yolo_seg.py +4 -1
  39. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-bAh96WNb.mjs +0 -26
  40. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CV2WbUaO.mjs +0 -26
  41. package/embed-dist/assets/index-Bm9LaQPH.css +0 -2
  42. package/embed-dist/assets/index-QdssILAA.js +0 -81
@@ -1,6 +1,6 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
- import { $ as sleep, A as pipelineExecutorCapability, B as errMsg, C as evaluateZoneRules, F as runtimeDevices$1, G as createEvent, K as hydrateSchema, R as supportedRuntimes$1, S as detectionPipelineCapability, V as BaseAddon, X as parseJsonUnknown, b as defaultDeviceFor$1, dt as EventCategory, k as nodePin, m as YAMNET_TO_MACRO, t as APPLE_SA_TO_MACRO } from "../dist-BzVZ2I4P.mjs";
3
- import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-B07M9qhj.mjs";
2
+ import { $ as sleep, A as pipelineExecutorCapability, B as errMsg, C as evaluateZoneRules, F as runtimeDevices$1, G as createEvent, K as hydrateSchema, R as supportedRuntimes$1, S as detectionPipelineCapability, V as BaseAddon, X as parseJsonUnknown, b as defaultDeviceFor$1, dt as EventCategory, k as nodePin, lt as string, m as YAMNET_TO_MACRO, st as object, t as APPLE_SA_TO_MACRO, tt as array, ut as union } from "../dist-BpWot5-w.mjs";
3
+ import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-D6R9X0V7.mjs";
4
4
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
5
5
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
6
6
  import { spawn } from "node:child_process";
@@ -308,7 +308,7 @@ function projectNodeEngine(store, nodeId) {
308
308
  //#endregion
309
309
  //#region src/detection-pipeline/default-detection-model.ts
310
310
  /** The object-detection step id — the only slot this resolver applies to. */
311
- var OBJECT_DETECTION_STEP_ID = "object-detection";
311
+ var OBJECT_DETECTION_STEP_ID$1 = "object-detection";
312
312
  /**
313
313
  * Balanced default object-detection model per accelerator class (measured on
314
314
  * COCO val2017). `'cpu'` maps to `null` → the caller substitutes the step's
@@ -368,7 +368,7 @@ function classifyAccelerator(backend, device) {
368
368
  function resolveDefaultDetectionModel(backend, device, format, getStepDef = getStepDefinition) {
369
369
  let def;
370
370
  try {
371
- def = getStepDef(OBJECT_DETECTION_STEP_ID);
371
+ def = getStepDef(OBJECT_DETECTION_STEP_ID$1);
372
372
  } catch {
373
373
  return "yolo26n";
374
374
  }
@@ -943,6 +943,9 @@ function serializeModelConfig(config) {
943
943
  postprocessor: config.postprocessor,
944
944
  confidence: config.confidence
945
945
  };
946
+ if (config.nmsIouThreshold !== void 0) result["nmsIouThreshold"] = config.nmsIouThreshold;
947
+ if (config.inputNormalization !== void 0) result["inputNormalization"] = config.inputNormalization;
948
+ if (config.outputProbabilities !== void 0) result["outputProbabilities"] = config.outputProbabilities;
946
949
  if (config.inputWidth !== void 0) result["inputWidth"] = config.inputWidth;
947
950
  if (config.inputHeight !== void 0) result["inputHeight"] = config.inputHeight;
948
951
  if (config.inputChannels !== void 0) result["inputChannels"] = config.inputChannels;
@@ -1250,6 +1253,55 @@ var RUNTIME_TO_FORMAT = {
1250
1253
  onnxruntime: "onnx",
1251
1254
  edgetpu: "tflite"
1252
1255
  };
1256
+ /** The step whose detections are re-filtered downstream by the executor's
1257
+ * per-macro `minConfidence*` sliders (`matchesMacroFilter`). Its pool-side
1258
+ * floor must stay BELOW those sliders or it silently pre-empts them. */
1259
+ var OBJECT_DETECTION_STEP_ID = "object-detection";
1260
+ /**
1261
+ * Pool-side confidence FLOOR for the object-detection step (#4 foot-gun fix).
1262
+ *
1263
+ * The pool used to receive `def.defaultConfidence` (0.5) as its floor and drop
1264
+ * every box < 0.5 BEFORE the executor's per-macro `minConfidence*` sliders run —
1265
+ * so a slider set below 0.5 was a silent no-op. Lower the pool floor to a small
1266
+ * constant so the operator-visible per-macro sliders are the authoritative
1267
+ * filter across their full 0–1 range. The per-macro slider DEFAULTS stay at 0.5
1268
+ * (see `step-definitions.ts` getConfigSchema), so default UI behaviour is
1269
+ * unchanged; only sub-0.5 sliders start having an effect. Non-object-detection
1270
+ * steps keep their own `def.defaultConfidence` floor (no per-macro re-filter). */
1271
+ var OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR = .05;
1272
+ /**
1273
+ * Resolve the pool-side confidence FLOOR for a step (#4). The object-detection
1274
+ * step gets the small {@link OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR} so its
1275
+ * per-macro `minConfidence*` sliders (applied downstream by the executor) are
1276
+ * the authoritative filter; every other step keeps its own `defaultConfidence`.
1277
+ */
1278
+ function resolvePoolConfidenceFloor(stepId, defaultConfidence) {
1279
+ return stepId === OBJECT_DETECTION_STEP_ID ? OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR : defaultConfidence;
1280
+ }
1281
+ /**
1282
+ * Load a model's runtime label list from its `-labels.json` extraFile, if any.
1283
+ *
1284
+ * Accepts BOTH on-disk shapes:
1285
+ * - a flat `string[]` (legacy label files), and
1286
+ * - a `{ "labels": string[], ... }` metadata object (the newer format the
1287
+ * animal / bird / vehicle classifiers ship, which also carries preprocess /
1288
+ * license metadata alongside the label array).
1289
+ *
1290
+ * Returns `undefined` when the model declares no `-labels.json`, the file is
1291
+ * absent, or its contents are not a recognised label shape — the caller then
1292
+ * falls back to the step's `def.labels`.
1293
+ */
1294
+ var LabelsFileSchema = union([array(string()).readonly(), object({ labels: array(string()).readonly() })]);
1295
+ function readModelLabels(modelsDir, modelEntry) {
1296
+ const labelsFile = modelEntry.extraFiles?.find((f) => f.filename.endsWith("-labels.json"));
1297
+ if (!labelsFile) return void 0;
1298
+ const labelsPath = path$1.join(modelsDir, labelsFile.filename);
1299
+ if (!fs.existsSync(labelsPath)) return void 0;
1300
+ const parsed = LabelsFileSchema.safeParse(JSON.parse(fs.readFileSync(labelsPath, "utf8")));
1301
+ if (!parsed.success) return void 0;
1302
+ const data = parsed.data;
1303
+ return "labels" in data ? data.labels : data;
1304
+ }
1253
1305
  var EngineFactory = class {
1254
1306
  pool = null;
1255
1307
  poolManager = null;
@@ -1417,14 +1469,7 @@ var EngineFactory = class {
1417
1469
  const inputHeight = modelEntry.inputSize.height;
1418
1470
  const inputSize = Math.max(inputWidth, inputHeight);
1419
1471
  const inputChannels = modelEntry.inputChannels ?? 3;
1420
- let labels = def.labels;
1421
- if (!labels && modelEntry.extraFiles) {
1422
- const labelsFile = modelEntry.extraFiles.find((f) => f.filename.endsWith("-labels.json"));
1423
- if (labelsFile) {
1424
- const labelsPath = path$1.join(this.opts.modelsDir, labelsFile.filename);
1425
- if (fs.existsSync(labelsPath)) labels = JSON.parse(fs.readFileSync(labelsPath, "utf8"));
1426
- }
1427
- }
1472
+ const labels = readModelLabels(this.opts.modelsDir, modelEntry) ?? def.labels;
1428
1473
  return {
1429
1474
  path: modelPath,
1430
1475
  inputSize,
@@ -1432,8 +1477,11 @@ var EngineFactory = class {
1432
1477
  inputHeight,
1433
1478
  inputChannels,
1434
1479
  preprocessMode: modelEntry.preprocessMode ?? "letterbox",
1480
+ inputNormalization: modelEntry.inputNormalization,
1481
+ outputProbabilities: modelEntry.outputProbabilities,
1435
1482
  postprocessor: modelEntry.postprocessor ?? def.postprocessor,
1436
- confidence: def.defaultConfidence,
1483
+ confidence: resolvePoolConfidenceFloor(stepId, def.defaultConfidence),
1484
+ nmsIouThreshold: this.opts.nmsIouThreshold ?? .45,
1437
1485
  labels,
1438
1486
  charset: def.charset,
1439
1487
  numClasses: labels?.length,
@@ -1552,6 +1600,9 @@ var EngineProvisioner = class {
1552
1600
  };
1553
1601
  //#endregion
1554
1602
  //#region src/detection-pipeline/postprocess/dispatch.ts
1603
+ /** Detector NMS IoU fallback — the historical hardcoded literal, used when the
1604
+ * step carries no `nmsIouThreshold` setting (unset == today). */
1605
+ var DEFAULT_NMS_IOU = .45;
1555
1606
  var VALID_KINDS = new Set([
1556
1607
  "detections",
1557
1608
  "classifications",
@@ -1579,7 +1630,7 @@ function tryStructured(output) {
1579
1630
  if (!isStepOutput(output.structured)) throw new Error(`Python pool returned structured output with unexpected kind: ${JSON.stringify(output.structured["kind"])}`);
1580
1631
  return output.structured;
1581
1632
  }
1582
- function postprocessYolo(output, stepDef) {
1633
+ function postprocessYolo(output, stepDef, nmsIouThreshold) {
1583
1634
  const structured = tryStructured(output);
1584
1635
  if (structured) return structured;
1585
1636
  const tensor = output.tensor;
@@ -1628,7 +1679,7 @@ function postprocessYolo(output, stepDef) {
1628
1679
  }
1629
1680
  return {
1630
1681
  kind: "detections",
1631
- detections: simpleNms(dets, .45)
1682
+ detections: simpleNms(dets, nmsIouThreshold)
1632
1683
  };
1633
1684
  }
1634
1685
  function postprocessSsd(output, _stepDef) {
@@ -1636,7 +1687,7 @@ function postprocessSsd(output, _stepDef) {
1636
1687
  if (structured) return structured;
1637
1688
  throw new Error("SSD postprocessing runs in the Python inference pool (ssd.py); no TypeScript raw-tensor path.");
1638
1689
  }
1639
- function postprocessScrfd(output, stepDef) {
1690
+ function postprocessScrfd(output, stepDef, nmsIouThreshold) {
1640
1691
  const structured = tryStructured(output);
1641
1692
  if (structured) return structured;
1642
1693
  if (!output.tensors) throw new Error("SCRFD postprocessor: no tensors in engine output");
@@ -1714,7 +1765,7 @@ function postprocessScrfd(output, stepDef) {
1714
1765
  score: c.score,
1715
1766
  bbox: c.bbox,
1716
1767
  ...c.landmarks ? { landmarks: c.landmarks } : {}
1717
- })), .45)
1768
+ })), nmsIouThreshold)
1718
1769
  };
1719
1770
  }
1720
1771
  /** Match ONNX output tensors to SCRFD strides by expected anchor count. */
@@ -1919,7 +1970,7 @@ function postprocessSaliency(output, _stepDef) {
1919
1970
  maskHeight: side
1920
1971
  };
1921
1972
  }
1922
- function postprocessYoloSeg(output, stepDef) {
1973
+ function postprocessYoloSeg(output, stepDef, nmsIouThreshold) {
1923
1974
  const structured = tryStructured(output);
1924
1975
  if (structured) return structured;
1925
1976
  const tensors = output.tensors;
@@ -2044,7 +2095,7 @@ function postprocessYoloSeg(output, stepDef) {
2044
2095
  class: d.class,
2045
2096
  score: d.score,
2046
2097
  bbox: d.bbox
2047
- })), .45);
2098
+ })), nmsIouThreshold);
2048
2099
  const keptSet = new Set(keptBoxes.map((d) => `${d.bbox[0]}_${d.bbox[1]}_${d.bbox[2]}_${d.bbox[3]}_${d.score}`));
2049
2100
  return {
2050
2101
  kind: "detections",
@@ -2077,10 +2128,11 @@ var DISPATCH = {
2077
2128
  * Both paths pass through normalizeOutput() to ensure consistent format
2078
2129
  * (e.g., classification top-K, alternates).
2079
2130
  */
2080
- function dispatchPostprocess(output, stepDef) {
2131
+ function dispatchPostprocess(output, stepDef, settings) {
2081
2132
  const fn = DISPATCH[stepDef.postprocessor];
2082
2133
  if (!fn) throw new Error(`Unknown postprocessor type: "${stepDef.postprocessor}"`);
2083
- return normalizeOutput(fn(output, stepDef));
2134
+ const raw = settings?.["nmsIouThreshold"];
2135
+ return normalizeOutput(fn(output, stepDef, typeof raw === "number" && raw > 0 ? raw : DEFAULT_NMS_IOU));
2084
2136
  }
2085
2137
  /**
2086
2138
  * Normalize any StepOutput to a consistent format.
@@ -2545,7 +2597,8 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
2545
2597
  case "classifications": {
2546
2598
  const rawEnabled = childStep.settings?.["enabledClasses"];
2547
2599
  const enabledSet = Array.isArray(rawEnabled) && rawEnabled.length > 0 ? new Set(rawEnabled.filter((v) => typeof v === "string")) : null;
2548
- const passesClass = (c) => enabledSet === null || enabledSet.has(c.class);
2600
+ const rejectSet = childStep.definition.rejectClasses && childStep.definition.rejectClasses.length > 0 ? new Set(childStep.definition.rejectClasses.map((c) => c.toLowerCase())) : /* @__PURE__ */ new Set();
2601
+ const passesClass = (c) => (enabledSet === null || enabledSet.has(c.class)) && !rejectSet.has(c.class.toLowerCase());
2549
2602
  const minConfidence = typeof childStep.settings?.["minConfidence"] === "number" ? childStep.settings["minConfidence"] : 0;
2550
2603
  const winner = output.classifications[0];
2551
2604
  if (winner && passesClass(winner) && winner.score >= minConfidence) parent.ownLabels.push({
@@ -2881,6 +2934,26 @@ var NATIVE_FACE_CROP_MAX_WIDTH = 320;
2881
2934
  function isEnrichmentOutput(output) {
2882
2935
  return output.kind === "classifications" || output.kind === "embedding" || output.kind === "text";
2883
2936
  }
2937
+ /**
2938
+ * Re-project a `crop-zone` root's detections from crop-relative coordinates
2939
+ * back to full-frame image space by the crop's origin — the root analogue of
2940
+ * the crop-roi child re-projection (`transformBboxToImageSpace`). Landmarks (if
2941
+ * any) are shifted by the same origin. Immutable: returns a fresh output.
2942
+ */
2943
+ function reprojectCropZoneDetections(output, cropOrigin) {
2944
+ const [ox, oy] = cropOrigin;
2945
+ return {
2946
+ kind: "detections",
2947
+ detections: output.detections.map((det) => ({
2948
+ ...det,
2949
+ bbox: transformBboxToImageSpace(det.bbox, cropOrigin),
2950
+ ...det.landmarks ? { landmarks: det.landmarks.map((l) => ({
2951
+ x: l.x + ox,
2952
+ y: l.y + oy
2953
+ })) } : {}
2954
+ }))
2955
+ };
2956
+ }
2884
2957
  var PipelineExecutor = class {
2885
2958
  opts;
2886
2959
  constructor(opts) {
@@ -2934,7 +3007,7 @@ var PipelineExecutor = class {
2934
3007
  *
2935
3008
  * @returns FrameResult + optional trace
2936
3009
  */
2937
- async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeFaceCropProvider) {
3010
+ async run(tree, rootInput, fullFrameJpegProvider, imageWidth, imageHeight, deviceId, runOpts, nativeFaceCropProvider, cropZoneBbox) {
2938
3011
  const startMs = Date.now();
2939
3012
  const verbosity = runOpts?.traceVerbosity ?? "off";
2940
3013
  const traceBuilder = new ExecutionTraceBuilder(verbosity, deviceId, imageWidth, imageHeight, this.opts.engineRuntime);
@@ -2954,9 +3027,11 @@ var PipelineExecutor = class {
2954
3027
  };
2955
3028
  for (const rootStep of tree.roots) {
2956
3029
  if (debug) console.log(`[executor] rootStep=${rootStep.stepId} settings=${JSON.stringify(rootStep.settings ?? {})}`);
3030
+ const cropPrep = rootStep.definition.extractMode === "crop-zone" && cropZoneBbox !== void 0 ? await this.prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
2957
3031
  const rootStart = Date.now();
2958
- const rootOutput = await this.executeStep(rootStep, rootInput, imageWidth, imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg);
3032
+ const rawRootOutput = await this.executeStep(rootStep, cropPrep?.input ?? rootInput, cropPrep?.width ?? imageWidth, cropPrep?.height ?? imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg);
2959
3033
  const rootMs = Date.now() - rootStart;
3034
+ const rootOutput = cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
2960
3035
  if (rootOutput.kind !== "detections") {
2961
3036
  if (isEnrichmentOutput(rootOutput)) {
2962
3037
  const mutable = this.synthesizeRootDetection(rootOutput, rootStep, idGen, rootMs, imageWidth, imageHeight);
@@ -3051,6 +3126,39 @@ var PipelineExecutor = class {
3051
3126
  ]
3052
3127
  }, rootStep, idGen, rootStepLatencyMs);
3053
3128
  }
3129
+ /**
3130
+ * Crop the full frame to a `crop-zone` root's absolute-pixel zone bbox and
3131
+ * return the cropped JPEG input + the exact origin used, so detections can be
3132
+ * re-projected. Returns null (⇒ full-frame fallback) when the bbox is
3133
+ * degenerate or the crop/encode fails — a package frame is never dropped for
3134
+ * a crop failure. The origin is computed to match `cropJpeg`'s clamp+round
3135
+ * exactly so the re-projection is pixel-accurate.
3136
+ */
3137
+ async prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) {
3138
+ if (isBboxDegenerate(cropZoneBbox)) return null;
3139
+ try {
3140
+ const crop = await cropJpeg(await fullFrameJpegProvider(), cropZoneBbox, imageWidth, imageHeight);
3141
+ const ox = Math.max(0, Math.round(cropZoneBbox[0]));
3142
+ const oy = Math.max(0, Math.round(cropZoneBbox[1]));
3143
+ return {
3144
+ input: {
3145
+ kind: "jpeg",
3146
+ data: crop.jpeg
3147
+ },
3148
+ width: crop.width,
3149
+ height: crop.height,
3150
+ origin: [
3151
+ ox,
3152
+ oy,
3153
+ ox + crop.width,
3154
+ oy + crop.height
3155
+ ]
3156
+ };
3157
+ } catch (err) {
3158
+ this.throttledWarn(`crop-zone-prep:${deviceId}`, 3e4, "crop-zone frame preparation failed — running package-detection full-frame", { error: errMsg(err) }, deviceId);
3159
+ return null;
3160
+ }
3161
+ }
3054
3162
  async executeStep(step, input, inputWidth, inputHeight, inputType, _parentBbox, parentClass, traceBuilder, stepTimings, poolAgg) {
3055
3163
  const preprocessStart = Date.now();
3056
3164
  const preprocessMs = Date.now() - preprocessStart;
@@ -3093,7 +3201,7 @@ var PipelineExecutor = class {
3093
3201
  if (typeof structured.predictMs === "number") poolAgg.predictMs += structured.predictMs;
3094
3202
  if (typeof structured.batchSize === "number" && structured.batchSize > poolAgg.batchSize) poolAgg.batchSize = structured.batchSize;
3095
3203
  const postprocessStart = Date.now();
3096
- const output = dispatchPostprocess(engineOutput, step.definition);
3204
+ const output = dispatchPostprocess(engineOutput, step.definition, step.settings);
3097
3205
  const postprocessMs = Date.now() - postprocessStart;
3098
3206
  stepTimings.push({
3099
3207
  source: step.stepId,
@@ -3240,6 +3348,54 @@ function capitalize(s) {
3240
3348
  return s.charAt(0).toUpperCase() + s.slice(1);
3241
3349
  }
3242
3350
  //#endregion
3351
+ //#region src/detection-pipeline/pipeline/crop-zone.ts
3352
+ /**
3353
+ * Resolve the absolute-pixel crop bbox for a `crop-zone` root detector: the
3354
+ * bounding box of the UNION of every polygon referenced by the device's
3355
+ * ENABLED `package`-stage zone rules. Zone polygons are normalized (0..1); the
3356
+ * union is clamped to [0,1] then scaled to the frame's pixel dimensions.
3357
+ *
3358
+ * Returns `null` when there is nothing usable to crop to — no enabled rules, no
3359
+ * referenced geometry, or a degenerate result — in which case the caller falls
3360
+ * back to full-frame inference (behaviour identical to before the crop-zone
3361
+ * plumbing).
3362
+ */
3363
+ function resolvePackageCropBbox(zones, packageRules, frameWidth, frameHeight) {
3364
+ if (frameWidth <= 0 || frameHeight <= 0) return null;
3365
+ const enabledRules = packageRules.filter((r) => r.enabled !== false);
3366
+ if (enabledRules.length === 0) return null;
3367
+ const zoneIds = /* @__PURE__ */ new Set();
3368
+ for (const rule of enabledRules) for (const id of rule.zoneIds) zoneIds.add(id);
3369
+ if (zoneIds.size === 0) return null;
3370
+ const zoneById = new Map(zones.map((z) => [z.id, z]));
3371
+ let minX = Number.POSITIVE_INFINITY;
3372
+ let minY = Number.POSITIVE_INFINITY;
3373
+ let maxX = Number.NEGATIVE_INFINITY;
3374
+ let maxY = Number.NEGATIVE_INFINITY;
3375
+ let sawPoint = false;
3376
+ for (const id of zoneIds) {
3377
+ const zone = zoneById.get(id);
3378
+ if (!zone || zone.polygon.length === 0) continue;
3379
+ for (const point of zone.polygon) {
3380
+ sawPoint = true;
3381
+ if (point.x < minX) minX = point.x;
3382
+ if (point.y < minY) minY = point.y;
3383
+ if (point.x > maxX) maxX = point.x;
3384
+ if (point.y > maxY) maxY = point.y;
3385
+ }
3386
+ }
3387
+ if (!sawPoint) return null;
3388
+ const clamp01 = (v) => Math.max(0, Math.min(1, v));
3389
+ const bbox = [
3390
+ clamp01(minX) * frameWidth,
3391
+ clamp01(minY) * frameHeight,
3392
+ clamp01(maxX) * frameWidth,
3393
+ clamp01(maxY) * frameHeight
3394
+ ];
3395
+ if (isBboxDegenerate(bbox)) return null;
3396
+ return bbox;
3397
+ }
3398
+ //#endregion
3243
3399
  //#region src/detection-pipeline/pipeline/tree-builder.ts
3244
3400
  /**
3245
3401
  * Build an executable tree from user config.
@@ -4977,10 +5133,16 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4977
5133
  const deviceOverrides = effectiveDeviceId > 0 ? await this.readDeviceStore(effectiveDeviceId) : {};
4978
5134
  const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
4979
5135
  const nativeFaceCropProvider = this.buildNativeFaceCropProvider(input.frameHandle);
5136
+ let cropZoneBbox;
5137
+ if (isRuntime && effectiveDeviceId > 0 && effectiveTree.roots.some((r) => r.definition.extractMode === "crop-zone")) {
5138
+ await this.ensureDeviceProxy(effectiveDeviceId);
5139
+ const proxy = this.deviceProxies.get(effectiveDeviceId);
5140
+ if (proxy) cropZoneBbox = resolvePackageCropBbox(proxy.state.zones.value?.zones ?? [], proxy.state.zoneRules.value?.package ?? [], imageWidth, imageHeight) ?? void 0;
5141
+ }
4980
5142
  const { result, trace } = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
4981
5143
  traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
4982
5144
  plane: input.plane
4983
- }, nativeFaceCropProvider);
5145
+ }, nativeFaceCropProvider, cropZoneBbox);
4984
5146
  if (isRuntime) {
4985
5147
  if (trace && this.eventBus) this.eventBus.emit(createEvent(EventCategory.PipelineTrace, {
4986
5148
  type: "device",
@@ -7207,6 +7207,17 @@ var ModelCatalogEntrySchema = object({
7207
7207
  "imagenet",
7208
7208
  "none"
7209
7209
  ]).optional(),
7210
+ /**
7211
+ * The model already applies softmax IN-GRAPH — its raw output is a
7212
+ * probability distribution, not logits. When set, the `softmax`
7213
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7214
+ * probability vector collapses it toward uniform (top-1 score craters far
7215
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7216
+ * the output is raw logits and the postprocessor applies softmax (the normal
7217
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7218
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7219
+ */
7220
+ outputProbabilities: boolean().optional(),
7210
7221
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7211
7222
  /**
7212
7223
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -14087,6 +14098,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
14087
14098
  kind: "mutation",
14088
14099
  auth: "admin"
14089
14100
  });
14101
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
14102
+ new Set(Object.values(DeviceType));
14090
14103
  /**
14091
14104
  * Evaluate `rules` against `items`. Returns the items partitioned
14092
14105
  * into `passed` (kept by the gate) and `excluded` (dropped). When
@@ -19617,22 +19630,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
19617
19630
  var RestartAddonResultSchema = unknown();
19618
19631
  var InstallPackageResultSchema = unknown();
19619
19632
  var ReloadPackagesResultSchema = unknown();
19620
- /**
19621
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
19622
- * server restarts so the admin UI can react to the `restartingAt`
19623
- * timestamp (shows reconnect overlay). The transition from
19624
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
19625
- * `system.restart-completed` event after the new process boots.
19626
- *
19627
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
19628
- */
19629
- var UpdateFrameworkPackageResultSchema = object({
19630
- packageName: string(),
19631
- fromVersion: string(),
19632
- toVersion: string(),
19633
- /** Ms-epoch the server scheduled its self-restart. */
19634
- restartingAt: number()
19635
- });
19636
19633
  var BulkUpdateItemStatusSchema = _enum([
19637
19634
  "queued",
19638
19635
  "updating",
@@ -19760,13 +19757,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
19760
19757
  }), object({ success: literal(true) }), {
19761
19758
  kind: "mutation",
19762
19759
  auth: "admin"
19763
- }), method(object({
19764
- packageName: string().min(1),
19765
- version: string().optional(),
19766
- deferRestart: boolean().optional()
19767
- }), UpdateFrameworkPackageResultSchema, {
19768
- kind: "mutation",
19769
- auth: "admin"
19770
19760
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
19771
19761
  kind: "mutation",
19772
19762
  auth: "admin"
@@ -21975,12 +21965,6 @@ Object.freeze({
21975
21965
  addonId: null,
21976
21966
  access: "delete"
21977
21967
  },
21978
- "addons.updateFrameworkPackage": {
21979
- capName: "addons",
21980
- capScope: "system",
21981
- addonId: null,
21982
- access: "create"
21983
- },
21984
21968
  "addons.updatePackage": {
21985
21969
  capName: "addons",
21986
21970
  capScope: "system",
@@ -26569,32 +26553,6 @@ Object.freeze({
26569
26553
  "network-access": "ingress",
26570
26554
  "smtp-provider": "email"
26571
26555
  });
26572
- var frameworkSwapPackageSchema = object({
26573
- name: string(),
26574
- stagedPath: string(),
26575
- backupPath: string(),
26576
- toVersion: string(),
26577
- fromVersion: string().nullable()
26578
- });
26579
- object({
26580
- jobId: string(),
26581
- taskId: string(),
26582
- packages: array(frameworkSwapPackageSchema),
26583
- requestedAtMs: number(),
26584
- schemaVersion: literal(1)
26585
- });
26586
- object({
26587
- jobId: string(),
26588
- taskId: string(),
26589
- backups: array(object({
26590
- name: string(),
26591
- backupPath: string(),
26592
- livePath: string()
26593
- })),
26594
- appliedAtMs: number(),
26595
- bootAttempts: number(),
26596
- schemaVersion: literal(1)
26597
- });
26598
26556
  /**
26599
26557
  * Names that, when used as URL query parameters, almost certainly carry
26600
26558
  * a secret. Matching is case-insensitive and anchored to the full param
@@ -7207,6 +7207,17 @@ var ModelCatalogEntrySchema = object({
7207
7207
  "imagenet",
7208
7208
  "none"
7209
7209
  ]).optional(),
7210
+ /**
7211
+ * The model already applies softmax IN-GRAPH — its raw output is a
7212
+ * probability distribution, not logits. When set, the `softmax`
7213
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7214
+ * probability vector collapses it toward uniform (top-1 score craters far
7215
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7216
+ * the output is raw logits and the postprocessor applies softmax (the normal
7217
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7218
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7219
+ */
7220
+ outputProbabilities: boolean().optional(),
7210
7221
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7211
7222
  /**
7212
7223
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -14087,6 +14098,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
14087
14098
  kind: "mutation",
14088
14099
  auth: "admin"
14089
14100
  });
14101
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
14102
+ new Set(Object.values(DeviceType));
14090
14103
  /**
14091
14104
  * Evaluate `rules` against `items`. Returns the items partitioned
14092
14105
  * into `passed` (kept by the gate) and `excluded` (dropped). When
@@ -19617,22 +19630,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
19617
19630
  var RestartAddonResultSchema = unknown();
19618
19631
  var InstallPackageResultSchema = unknown();
19619
19632
  var ReloadPackagesResultSchema = unknown();
19620
- /**
19621
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
19622
- * server restarts so the admin UI can react to the `restartingAt`
19623
- * timestamp (shows reconnect overlay). The transition from
19624
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
19625
- * `system.restart-completed` event after the new process boots.
19626
- *
19627
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
19628
- */
19629
- var UpdateFrameworkPackageResultSchema = object({
19630
- packageName: string(),
19631
- fromVersion: string(),
19632
- toVersion: string(),
19633
- /** Ms-epoch the server scheduled its self-restart. */
19634
- restartingAt: number()
19635
- });
19636
19633
  var BulkUpdateItemStatusSchema = _enum([
19637
19634
  "queued",
19638
19635
  "updating",
@@ -19760,13 +19757,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
19760
19757
  }), object({ success: literal(true) }), {
19761
19758
  kind: "mutation",
19762
19759
  auth: "admin"
19763
- }), method(object({
19764
- packageName: string().min(1),
19765
- version: string().optional(),
19766
- deferRestart: boolean().optional()
19767
- }), UpdateFrameworkPackageResultSchema, {
19768
- kind: "mutation",
19769
- auth: "admin"
19770
19760
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
19771
19761
  kind: "mutation",
19772
19762
  auth: "admin"
@@ -21975,12 +21965,6 @@ Object.freeze({
21975
21965
  addonId: null,
21976
21966
  access: "delete"
21977
21967
  },
21978
- "addons.updateFrameworkPackage": {
21979
- capName: "addons",
21980
- capScope: "system",
21981
- addonId: null,
21982
- access: "create"
21983
- },
21984
21968
  "addons.updatePackage": {
21985
21969
  capName: "addons",
21986
21970
  capScope: "system",
@@ -26569,32 +26553,6 @@ Object.freeze({
26569
26553
  "network-access": "ingress",
26570
26554
  "smtp-provider": "email"
26571
26555
  });
26572
- var frameworkSwapPackageSchema = object({
26573
- name: string(),
26574
- stagedPath: string(),
26575
- backupPath: string(),
26576
- toVersion: string(),
26577
- fromVersion: string().nullable()
26578
- });
26579
- object({
26580
- jobId: string(),
26581
- taskId: string(),
26582
- packages: array(frameworkSwapPackageSchema),
26583
- requestedAtMs: number(),
26584
- schemaVersion: literal(1)
26585
- });
26586
- object({
26587
- jobId: string(),
26588
- taskId: string(),
26589
- backups: array(object({
26590
- name: string(),
26591
- backupPath: string(),
26592
- livePath: string()
26593
- })),
26594
- appliedAtMs: number(),
26595
- bootAttempts: number(),
26596
- schemaVersion: literal(1)
26597
- });
26598
26556
  /**
26599
26557
  * Names that, when used as URL query parameters, almost certainly carry
26600
26558
  * a secret. Matching is case-insensitive and anchored to the full param
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-B2jTt7Lq.js");
5
+ const require_dist = require("../dist-D5o9fGxq.js");
6
6
  let node_fs = require("node:fs");
7
7
  let node_path = require("node:path");
8
8
  //#region src/motion-wasm/wasm-motion-detector.ts
@@ -1,4 +1,4 @@
1
- import { C as evaluateZoneRules, K as hydrateSchema, O as motionDetectionCapability, V as BaseAddon, W as DeviceType } from "../dist-BzVZ2I4P.mjs";
1
+ import { C as evaluateZoneRules, K as hydrateSchema, O as motionDetectionCapability, V as BaseAddon, W as DeviceType } from "../dist-BpWot5-w.mjs";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  //#region src/motion-wasm/wasm-motion-detector.ts