@camstack/addon-pipeline 1.2.329 → 1.2.330

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 (33) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-ChH7Y219.js → default-detection-model-B6gKGnfx.js} +39 -67
  4. package/dist/{default-detection-model-j0nBpRoB.mjs → default-detection-model-CPDT_eFT.mjs} +39 -67
  5. package/dist/detection-pipeline/index.js +197 -31
  6. package/dist/detection-pipeline/index.mjs +197 -31
  7. package/dist/{dist-BlIWhjnu.js → dist-CMeICL07.js} +771 -464
  8. package/dist/{dist-0HoA3wAi.mjs → dist-CuPEyZY2.mjs} +726 -437
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-HOMWRPzW.mjs → node-USq5jYwq.mjs} +1 -1
  12. package/dist/{node-BPpVMnZy.js → node-X_0Tka7-.js} +1 -1
  13. package/dist/pipeline-runner/index.js +355 -104
  14. package/dist/pipeline-runner/index.mjs +355 -104
  15. package/dist/{process-memory-DuF8pa2w.mjs → process-memory-07XYts8V.mjs} +1 -1
  16. package/dist/{process-memory-BW06vOIQ.js → process-memory-o4LFWbBN.js} +1 -1
  17. package/dist/recorder/index.js +7 -7
  18. package/dist/recorder/index.mjs +7 -7
  19. package/dist/{segment-demux-js-BU2MXpln.mjs → segment-demux-js-Bq8DrH0b.mjs} +2 -2
  20. package/dist/{segment-demux-js-Dl52Zz8w.js → segment-demux-js-FqaQjG7n.js} +2 -2
  21. package/dist/stream-broker/_stub.js +2 -2
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-vNfS65In.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-EYRUYv_r.mjs} +2 -2
  23. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BnP7C_HO.mjs +26 -0
  24. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CR0--kRv.mjs +26 -0
  25. package/dist/stream-broker/demux-worker-child.js +1 -1
  26. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  27. package/dist/stream-broker/{hostInit-Ciacp783.mjs → hostInit-iGdBpvGc.mjs} +2 -2
  28. package/dist/stream-broker/index.js +4 -4
  29. package/dist/stream-broker/index.mjs +4 -4
  30. package/dist/stream-broker/remoteEntry.js +1 -1
  31. package/package.json +1 -1
  32. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DfeHvW4k.mjs +0 -26
  33. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BpXPNINd.mjs +0 -26
@@ -1,6 +1,6 @@
1
- import { Bn as array, Bt as pickDetailCropConvention, D as FailureCounters, Dn as customAction, Dt as loadContributionCapability, En as createEvent, Et as linkedStepOf, Gn as number, Kn as object, M as NativeLeaseAdmissionSchema, N as NativeLeaseSettingsSchema, On as defineCustomActions, Pn as nodePin, Q as cameraOwnsMotionViaSensor, Qt as resolveLabelLink, Sn as BaseAddon, Un as lazy, Ut as pipelineRunnerCapability, Vn as boolean, Vt as pickNativeLeaseOverride, Wn as literal, Yn as string, Zn as EventCategory, _t as failureContributionCapability, a as BackgroundRunRegistry, bn as errMsg, g as DEFAULT_DETAIL_CROP_CONVENTION, st as deriveDetailCropRect, y as DEFAULT_NATIVE_LEASE_SETTINGS, zn as _enum } from "../dist-0HoA3wAi.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-HOMWRPzW.mjs";
3
- import { _ as getStep, c as macroGateVerdict, d as isSessionDecodeReadGone, f as createSessionDecodeCoordinator, l as rootObjectGate, o as placeDetailStep, p as resolveFrameViewGeometry, r as CustomModelCatalog, s as stepsForElectedPool, u as localFrameRegistry, v as getStepDefinition } from "../default-detection-model-j0nBpRoB.mjs";
1
+ import { A as NativeLeaseAdmissionSchema, An as customAction, Cn as errMsg, Gt as pipelineRunnerCapability, Hn as _enum, Ht as pickDetailCropConvention, Jn as number, Kn as lazy, Ln as nodePin, Ot as linkedStepOf, Q as cameraOwnsMotionViaSensor, Qn as string, T as FailureCounters, Tn as BaseAddon, Un as array, Ut as pickNativeLeaseOverride, Wn as boolean, Yn as object, _ as DEFAULT_DETAIL_CROP_CONVENTION, a as BackgroundRunRegistry, ct as deriveDetailCropRect, er as EventCategory, in as rootSuppliesOf, j as NativeLeaseSettingsSchema, jn as defineCustomActions, kn as createEvent, kt as loadContributionCapability, m as CROP_SUPPLY_SOURCES, q as allowedSuppliedEffect, qn as literal, rn as resolveRootSupply, ut as deriveRootFaceRefineRect, y as DEFAULT_NATIVE_LEASE_SETTINGS, yt as failureContributionCapability, z as ROOT_SUPPLIABLE_STEP_IDS } from "../dist-CuPEyZY2.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-USq5jYwq.mjs";
3
+ import { _ as getStep, c as macroGateVerdict, d as isSessionDecodeReadGone, f as createSessionDecodeCoordinator, l as rootObjectGate, o as placeDetailStep, p as resolveFrameViewGeometry, r as CustomModelCatalog, s as stepsForElectedPool, u as localFrameRegistry, v as getStepDefinition } from "../default-detection-model-CPDT_eFT.mjs";
4
4
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
6
6
  import { c as withDetectionIntent, o as preRollRequestOf, t as DETECTION_PRE_ROLL_REQUEST_MS } from "../restream-intent-DyGmLAU9.mjs";
@@ -1969,33 +1969,34 @@ function onboardRootDetections(slot, frame, gate) {
1969
1969
  };
1970
1970
  }
1971
1971
  //#endregion
1972
- //#region src/pipeline-runner/label-link.ts
1972
+ //#region src/pipeline-runner/root-supply.ts
1973
1973
  /**
1974
- * The runner's half of the detector ↔ step label link (D715).
1974
+ * The runner's half of root-model supply (D715, D730).
1975
1975
  *
1976
1976
  * The runner holds the camera's WHOLE step tree (the attach config), so it is
1977
- * the one place that can answer "who writes this effect?" for a camera: the
1978
- * root model's declaration (`fillsClassifierEffects`) against the filled
1979
- * step's own `labelLink` policy and class list (`resolveLabelLink`). Three
1980
- * consumers read that one answer:
1981
- *
1982
- * - the frame result — a linked label the executor wrote at score 0 is raised
1983
- * to the detection's score when the detector is authoritative;
1984
- * - the `detailSteps` announce — a skipped classifier is announced with
1985
- * `filledBy`, so post-analysis does not dispatch it for a subject the
1986
- * detector already named;
1987
- * - `package-detection` — when the root fills it, the package root is not
1988
- * dispatched at all and the root's own `package` boxes ARE the package
1989
- * result (the operator's package threshold moves onto the root's gate, and
1990
- * the frame says it looked).
1977
+ * the one place that can answer "does this camera use the root's output for
1978
+ * this step?": the root model's declaration (`rootSuppliesOf`) against the
1979
+ * step's own `useRootModel` flag, through the one decision `resolveRootSupply`.
1980
+ * Four consumers read that answer:
1981
+ *
1982
+ * - linked labels — effective ⇒ raised to the detection's score (the root is
1983
+ * the one writer), or DROPPED when the step's class list switched that
1984
+ * class off (the detection stays at its macro); otherwise the score-0
1985
+ * fallback the executor wrote;
1986
+ * - root child boxes — a `plate` / `face` detail the root drew is kept only
1987
+ * when its crop step's supply is effective;
1988
+ * - the `detailSteps` announce — an effective step carries `filledBy` +
1989
+ * `filledAs`, so post-analysis does not infer it;
1990
+ * - `package-detection` — effective ⇒ the package root is not dispatched and
1991
+ * the root's own `package` boxes ARE the package result.
1991
1992
  *
1992
1993
  * Pure: the caller passes the model resolver; no I/O, no clock.
1993
1994
  */
1994
- /** The root step the fills are read from — the always-on camera root. */
1995
- var LABEL_LINK_ROOT_STEP_ID = "object-detection";
1995
+ /** The root step the supplies are read from — the always-on camera root. */
1996
+ var ROOT_SUPPLY_ROOT_STEP_ID = "object-detection";
1996
1997
  var PACKAGE_STEP_ID = "package-detection";
1997
1998
  var PACKAGE_THRESHOLD_KEY = "minConfidencePackage";
1998
- var EMPTY_LABEL_LINK_PLAN = { decisions: /* @__PURE__ */ new Map() };
1999
+ var EMPTY_ROOT_SUPPLY_PLAN = { states: /* @__PURE__ */ new Map() };
1999
2000
  function findStep(steps, addonId) {
2000
2001
  for (const step of steps) {
2001
2002
  if (step.addonId === addonId) return step;
@@ -2004,70 +2005,108 @@ function findStep(steps, addonId) {
2004
2005
  }
2005
2006
  }
2006
2007
  /**
2007
- * Plan a camera's label links from its configured tree. A root with no pinned
2008
- * model runs the static default — a COCO model, which fills nothing.
2008
+ * Plan a camera's root supplies from its configured tree. A root with no
2009
+ * pinned model runs the step's default — a COCO model, which supplies the
2010
+ * animal and vehicle classifiers like any other root.
2009
2011
  */
2010
- function planLabelLinks(steps, resolveModel) {
2011
- if (steps === void 0) return EMPTY_LABEL_LINK_PLAN;
2012
+ function planRootSupplies(steps, resolveModel) {
2013
+ if (steps === void 0) return EMPTY_ROOT_SUPPLY_PLAN;
2012
2014
  const root = steps.find((s) => s.addonId === "object-detection" && s.enabled !== false);
2013
- if (root?.modelId === void 0) return EMPTY_LABEL_LINK_PLAN;
2014
- const fills = resolveModel("object-detection", root.modelId)?.fillsClassifierEffects ?? [];
2015
- if (fills.length === 0) return {
2016
- rootModelId: root.modelId,
2017
- decisions: /* @__PURE__ */ new Map()
2018
- };
2019
- const decisions = /* @__PURE__ */ new Map();
2020
- for (const fill of fills) {
2021
- const step = findStep(steps, fill.stepId);
2022
- decisions.set(fill.stepId, resolveLabelLink(fill, step === void 0 ? void 0 : {
2023
- enabled: step.enabled !== false,
2024
- ...step.settings !== void 0 ? { settings: step.settings } : {}
2025
- }));
2015
+ if (root === void 0) return EMPTY_ROOT_SUPPLY_PLAN;
2016
+ const definition = getStepDefinition(ROOT_SUPPLY_ROOT_STEP_ID);
2017
+ const rootModelId = root.modelId ?? definition.defaultModelId;
2018
+ const supplies = rootSuppliesOf(resolveModel(ROOT_SUPPLY_ROOT_STEP_ID, rootModelId), {
2019
+ labels: definition.labels ?? [],
2020
+ ...definition.classMap !== void 0 ? { classMap: definition.classMap } : {}
2021
+ });
2022
+ const states = /* @__PURE__ */ new Map();
2023
+ for (const stepId of ROOT_SUPPLIABLE_STEP_IDS) {
2024
+ const settings = findStep(steps, stepId)?.settings;
2025
+ states.set(stepId, {
2026
+ ...resolveRootSupply(stepId, settings, supplies),
2027
+ ...settings !== void 0 ? { settings } : {}
2028
+ });
2026
2029
  }
2027
2030
  return {
2028
- rootModelId: root.modelId,
2029
- decisions
2031
+ rootModelId,
2032
+ states
2030
2033
  };
2031
2034
  }
2032
- /** Steps this plan says must not be dispatched, with the model that fills them. */
2033
- function filledStepIds(plan) {
2035
+ /** The steps whose supply is effective, with the model and the kind. */
2036
+ function suppliedSteps(plan) {
2034
2037
  const out = /* @__PURE__ */ new Map();
2035
2038
  if (plan.rootModelId === void 0) return out;
2036
- for (const [stepId, decision] of plan.decisions) if (decision.skip) out.set(stepId, plan.rootModelId);
2039
+ for (const [stepId, state] of plan.states) if (state.effective && state.supply !== void 0) out.set(stepId, {
2040
+ modelId: plan.rootModelId,
2041
+ kind: state.supply.kind
2042
+ });
2037
2043
  return out;
2038
2044
  }
2039
- function raiseLinkedLabels(detection, plan) {
2045
+ /** The steps whose supply just BECAME effective between two plans. */
2046
+ function newlySuppliedSteps(previous, next) {
2047
+ const out = [];
2048
+ for (const [stepId, state] of next.states) if (state.effective && previous?.states.get(stepId)?.effective !== true) out.push(stepId);
2049
+ return out;
2050
+ }
2051
+ function authoritativeLabels(detection, plan) {
2040
2052
  let changed = false;
2041
- const labels = detection.labels.map((label) => {
2053
+ const labels = [];
2054
+ for (const label of detection.labels) {
2042
2055
  const stepId = linkedStepOf(label.stepId);
2043
- if (stepId === void 0 || plan.decisions.get(stepId)?.authoritative !== true) return label;
2044
- if (label.score === detection.score) return label;
2056
+ const state = stepId !== void 0 ? plan.states.get(stepId) : void 0;
2057
+ if (state === void 0 || !state.effective) {
2058
+ labels.push(label);
2059
+ continue;
2060
+ }
2061
+ if (!allowedSuppliedEffect(label.label, state.settings)) {
2062
+ changed = true;
2063
+ continue;
2064
+ }
2065
+ if (label.score === detection.score) {
2066
+ labels.push(label);
2067
+ continue;
2068
+ }
2045
2069
  changed = true;
2046
- return {
2070
+ labels.push({
2047
2071
  ...label,
2048
2072
  score: detection.score
2049
- };
2050
- });
2073
+ });
2074
+ }
2051
2075
  return changed ? labels : void 0;
2052
2076
  }
2077
+ /** Whether a root child box survives: only under an effective crop supply. */
2078
+ function keepsDetection(detection, plan) {
2079
+ const supplied = detection.rootSupplied;
2080
+ if (supplied === void 0 || detection.kind !== "detail") return true;
2081
+ const stepId = ROOT_SUPPLIABLE_STEP_IDS.find((id) => id === supplied.stepId);
2082
+ return stepId !== void 0 && plan.states.get(stepId)?.effective === true;
2083
+ }
2053
2084
  /**
2054
- * Raise each linked label to its detection's score where the detector is the
2055
- * authoritative writer of that effect. Every other linked label stays the
2056
- * score-0 fallback the executor wrote. Never mutates; returns the input when
2057
- * nothing changes.
2085
+ * Apply the plan to a frame: linked labels of an effective step become
2086
+ * authoritative (or are dropped by its class list), and root child boxes of a
2087
+ * crop step that is not effective are removed. Never mutates; returns the
2088
+ * input when nothing changes.
2058
2089
  */
2059
- function applyLabelLinkAuthority(result, plan) {
2060
- if (plan.decisions.size === 0) return result;
2090
+ function applyRootSupplyAuthority(result, plan) {
2091
+ if (plan.states.size === 0) return result;
2061
2092
  let changed = false;
2062
- const detections = result.detections.map((d) => {
2063
- const labels = raiseLinkedLabels(d, plan);
2064
- if (labels === void 0) return d;
2093
+ const detections = [];
2094
+ for (const d of result.detections) {
2095
+ if (!keepsDetection(d, plan)) {
2096
+ changed = true;
2097
+ continue;
2098
+ }
2099
+ const labels = authoritativeLabels(d, plan);
2100
+ if (labels === void 0) {
2101
+ detections.push(d);
2102
+ continue;
2103
+ }
2065
2104
  changed = true;
2066
- return {
2105
+ detections.push({
2067
2106
  ...d,
2068
2107
  labels
2069
- };
2070
- });
2108
+ });
2109
+ }
2071
2110
  return changed ? {
2072
2111
  ...result,
2073
2112
  detections
@@ -2075,7 +2114,7 @@ function applyLabelLinkAuthority(result, plan) {
2075
2114
  }
2076
2115
  /** The root step's schema default for its class allow-list. */
2077
2116
  function rootDefaultMacroClasses() {
2078
- const field = getStep(LABEL_LINK_ROOT_STEP_ID).getConfigSchema().find((f) => "key" in f && f.key === "enabledMacroClasses");
2117
+ const field = getStep(ROOT_SUPPLY_ROOT_STEP_ID).getConfigSchema().find((f) => "key" in f && f.key === "enabledMacroClasses");
2079
2118
  const value = field !== void 0 && "default" in field ? field.default : void 0;
2080
2119
  return Array.isArray(value) ? value : [];
2081
2120
  }
@@ -2096,18 +2135,19 @@ function withPackageAdmitted(root, packageStep) {
2096
2135
  };
2097
2136
  }
2098
2137
  /**
2099
- * When the root fills `package-detection` and that step's policy lets it be
2100
- * skipped, drop the package root from the dispatch and let the camera root
2101
- * report packages: its gate admits `package` at the package step's threshold.
2102
- * Otherwise the tree is returned untouched.
2138
+ * When the root's `package` supply is effective for this camera, drop the
2139
+ * package root from the dispatch and let the camera root report packages: its
2140
+ * gate admits `package` at the package step's threshold. Otherwise (the root
2141
+ * declares no `package`, or the flag is off) the tree is returned untouched
2142
+ * and the package step runs its own model.
2103
2143
  */
2104
2144
  function applyPackageFill(steps, plan) {
2105
- if (plan.decisions.get(PACKAGE_STEP_ID)?.skip !== true) return {
2145
+ if (plan.states.get(PACKAGE_STEP_ID)?.effective !== true) return {
2106
2146
  steps,
2107
2147
  filledByRoot: false
2108
2148
  };
2109
2149
  const packageStep = steps.find((s) => s.addonId === PACKAGE_STEP_ID);
2110
- if (packageStep === void 0) return {
2150
+ if (packageStep === void 0 || packageStep.enabled === false) return {
2111
2151
  steps,
2112
2152
  filledByRoot: false
2113
2153
  };
@@ -3288,6 +3328,150 @@ var FaceOutsideParentLog = class {
3288
3328
  }
3289
3329
  };
3290
3330
  //#endregion
3331
+ //#region src/pipeline-runner/root-face-refine.ts
3332
+ /**
3333
+ * Root face refinement (D731) — how a ROOT model's `face` box becomes a face
3334
+ * the recogniser can use, without ever embedding an unaligned crop.
3335
+ *
3336
+ * A root face box has no landmarks. It is used as a LOCATION only:
3337
+ *
3338
+ * 1. one face-scoped square around it (`deriveRootFaceRefineRect`, box ×1.6)
3339
+ * is cut from the frame's NATIVE surface — the native lease or nothing;
3340
+ * 2. the square is shrunk to {@link ROOT_FACE_REFINE_INPUT_PX} and laid in the
3341
+ * top-left of a black canvas the size of the face detector's input, so the
3342
+ * detector runs at an effective 160 px input on its own 640 graph — the
3343
+ * network is convolutional, the black margin contains nothing, and no 160 px
3344
+ * build of the model is needed. The canvas maps back to the frame through
3345
+ * a "virtual" frame rectangle 640/160 times the square, so every existing
3346
+ * crop→frame mapping (results, native child re-cuts) stays exact;
3347
+ * 3. the detector's best face in the square is kept — its landmarks come from
3348
+ * the refinement input, where the face is large, which is the space the
3349
+ * landmark-precision gate then measures it in;
3350
+ * 4. that face is handed back to the SAME step as its output
3351
+ * (`suppliedRoot`), so the embedder child aligns it through the existing
3352
+ * native-crop alignment, exactly like a detected face.
3353
+ *
3354
+ * No face in the square ⇒ nothing is embedded, and the drop is logged.
3355
+ */
3356
+ /** The face detector's input side for the step as it will run. */
3357
+ function detectorInputPx(step) {
3358
+ const def = getStepDefinition(step.addonId);
3359
+ const modelId = step.modelId ?? def.defaultModelId;
3360
+ return def.models.find((m) => m.id === modelId)?.inputSize.width ?? 160;
3361
+ }
3362
+ /** Lay the refinement square, shrunk to the refine input, on a black canvas. */
3363
+ async function buildRefineCanvas(squareJpeg, canvasPx) {
3364
+ const sharp = await getSharp();
3365
+ const small = await sharp(squareJpeg).resize(160, 160, { fit: "fill" }).toBuffer();
3366
+ const pad = Math.max(0, canvasPx - 160);
3367
+ return sharp(small).extend({
3368
+ top: 0,
3369
+ left: 0,
3370
+ bottom: pad,
3371
+ right: pad,
3372
+ background: {
3373
+ r: 0,
3374
+ g: 0,
3375
+ b: 0
3376
+ }
3377
+ }).jpeg({ quality: 92 }).toBuffer();
3378
+ }
3379
+ /** The best face of a refinement pass, or `undefined` when it found none. */
3380
+ function bestFace(detections) {
3381
+ return detections.filter((d) => d.macroClass === "face").reduce((best, d) => best === void 0 || d.score > best.score ? d : best, void 0);
3382
+ }
3383
+ /** The canvas-space face as the step's own output for the second pass. */
3384
+ function refinedAsSupplied(face, stepId, modelId) {
3385
+ return {
3386
+ stepId,
3387
+ modelId,
3388
+ detections: [{
3389
+ bbox: [
3390
+ face.bbox.x,
3391
+ face.bbox.y,
3392
+ face.bbox.x + face.bbox.width,
3393
+ face.bbox.y + face.bbox.height
3394
+ ],
3395
+ score: face.score,
3396
+ ...face.landmarks !== void 0 ? { landmarks: face.landmarks.map((p) => ({
3397
+ x: p.x,
3398
+ y: p.y
3399
+ })) } : {}
3400
+ }]
3401
+ };
3402
+ }
3403
+ /**
3404
+ * Refine the root's face box for one step and run the requested chain on the
3405
+ * face it finds. `null` ⇒ no pixels (the native lease is the only source);
3406
+ * an empty list ⇒ the square held no face. Every drop is logged.
3407
+ */
3408
+ async function refineRootFace(deps, input, supplied, step, requested, deviceKey) {
3409
+ const handle = input.frameHandle;
3410
+ const tags = { deviceId: input.deviceId };
3411
+ if (handle === void 0) {
3412
+ deps.logger.warn("root face refinement needs the native frame — no frame handle, dropped", {
3413
+ tags,
3414
+ meta: { stepId: step.addonId }
3415
+ });
3416
+ return null;
3417
+ }
3418
+ const box = supplied.boxes.reduce((best, b) => b.score > best.score ? b : best);
3419
+ const square = deriveRootFaceRefineRect(box.bbox, handle.width, handle.height);
3420
+ const native = await deps.getNativeCrop(input.deviceId, handle, normalizeRect(square, handle.width, handle.height));
3421
+ if (native === null || isNativeCropMiss(native)) {
3422
+ deps.logger.warn("root face refinement: the native square missed — dropped", {
3423
+ tags,
3424
+ meta: {
3425
+ stepId: step.addonId,
3426
+ nativeMissReason: native === null ? null : native.miss
3427
+ }
3428
+ });
3429
+ return null;
3430
+ }
3431
+ const canvasPx = detectorInputPx(step);
3432
+ const scale = canvasPx / 160;
3433
+ const virtualFrameSpace = {
3434
+ x: native.frameSpace.x,
3435
+ y: native.frameSpace.y,
3436
+ w: native.frameSpace.w * scale,
3437
+ h: native.frameSpace.h * scale
3438
+ };
3439
+ const crop = {
3440
+ jpeg: await buildRefineCanvas(native.jpeg, canvasPx),
3441
+ width: canvasPx,
3442
+ height: canvasPx,
3443
+ frameSpace: virtualFrameSpace,
3444
+ source: "native"
3445
+ };
3446
+ const nativeCropRef = {
3447
+ handle,
3448
+ cropFrameSpace: virtualFrameSpace
3449
+ };
3450
+ const detectorOnly = {
3451
+ ...step,
3452
+ children: []
3453
+ };
3454
+ const found = await deps.runPipeline([detectorOnly], crop.jpeg, input.deviceId, deviceKey, nativeCropRef);
3455
+ const face = found === null ? void 0 : bestFace(found.detections);
3456
+ if (face === void 0) {
3457
+ deps.logger.warn("root face refinement found no face in the square — dropped, never embedded unaligned", {
3458
+ tags,
3459
+ meta: {
3460
+ stepId: step.addonId,
3461
+ rootModelId: supplied.modelId,
3462
+ rootScore: box.score
3463
+ }
3464
+ });
3465
+ return [];
3466
+ }
3467
+ const chain = requested === void 0 ? step : pruneChildStepsToRequested(step, requested);
3468
+ if (chain.children === void 0 || chain.children.length === 0) return [toDetailResult(face, step.addonId, crop)];
3469
+ const modelId = step.modelId ?? getStepDefinition(step.addonId).defaultModelId;
3470
+ const embedded = await deps.runPipeline([chain], crop.jpeg, input.deviceId, deviceKey, nativeCropRef, refinedAsSupplied(face, step.addonId, modelId));
3471
+ if (embedded === null) return [toDetailResult(face, step.addonId, crop)];
3472
+ return embedded.detections.map((d) => toDetailResult(d, step.addonId, crop));
3473
+ }
3474
+ //#endregion
3291
3475
  //#region src/pipeline-runner/detail-subtree.ts
3292
3476
  /** Did the subtree run refuse because the camera is not attached (park lapsed)? */
3293
3477
  function isDetailNotAttachedRefusal(outcome) {
@@ -3380,6 +3564,43 @@ function mapBboxToFrameSpace(bbox, crop) {
3380
3564
  h: bbox.height * scaleY
3381
3565
  };
3382
3566
  }
3567
+ /**
3568
+ * Map the root's FRAME-space boxes (D730) into the crop's pixels — the inverse
3569
+ * of {@link mapBboxToFrameSpace} — clamped to the crop. A box with nothing left
3570
+ * inside the crop is dropped. `null` when no box survives.
3571
+ */
3572
+ function suppliedRootForCrop(supplied, crop) {
3573
+ const scaleX = crop.width / crop.frameSpace.w;
3574
+ const scaleY = crop.height / crop.frameSpace.h;
3575
+ const detections = [];
3576
+ for (const box of supplied.boxes) {
3577
+ const x1 = Math.max(0, (box.bbox.x - crop.frameSpace.x) * scaleX);
3578
+ const y1 = Math.max(0, (box.bbox.y - crop.frameSpace.y) * scaleY);
3579
+ const x2 = Math.min(crop.width, (box.bbox.x + box.bbox.w - crop.frameSpace.x) * scaleX);
3580
+ const y2 = Math.min(crop.height, (box.bbox.y + box.bbox.h - crop.frameSpace.y) * scaleY);
3581
+ if (x2 <= x1 || y2 <= y1) continue;
3582
+ detections.push({
3583
+ bbox: [
3584
+ x1,
3585
+ y1,
3586
+ x2,
3587
+ y2
3588
+ ],
3589
+ score: box.score
3590
+ });
3591
+ }
3592
+ const [first, ...rest] = detections;
3593
+ if (first === void 0) return null;
3594
+ return {
3595
+ stepId: supplied.stepId,
3596
+ modelId: supplied.modelId,
3597
+ detections: [first, ...rest]
3598
+ };
3599
+ }
3600
+ /** Whether the root's box for this step is a location to refine in (a face). */
3601
+ function refinesSuppliedStep(stepId) {
3602
+ return (stepId === "plate-detection" || stepId === "face-detection") && CROP_SUPPLY_SOURCES[stepId].use === "refine";
3603
+ }
3383
3604
  /** Base64-encode a normalized float32 embedding vector. */
3384
3605
  function encodeEmbeddingBase64(embedding) {
3385
3606
  return Buffer.from(Float32Array.from(embedding).buffer).toString("base64");
@@ -3583,6 +3804,18 @@ async function runDetailSubtree(deps, input) {
3583
3804
  }
3584
3805
  const requested = input.steps;
3585
3806
  const steps = requested === void 0 ? matched : matched.map((step) => pruneChildStepsToRequested(step, requested));
3807
+ const supplied = input.supplied;
3808
+ if (supplied !== void 0 && refinesSuppliedStep(supplied.stepId)) {
3809
+ const details = [];
3810
+ for (const step of matched) {
3811
+ if (step.addonId !== supplied.stepId) continue;
3812
+ const placement = deps.placeStep?.(input.deviceId, step);
3813
+ const refined = await refineRootFace(deps, input, supplied, placement?.step ?? step, requested, placement?.deviceKey);
3814
+ if (refined === null) return null;
3815
+ details.push(...refined);
3816
+ }
3817
+ return { details };
3818
+ }
3586
3819
  const { crop, nativeMiss } = await resolveCrop(deps, input, deps.resolveCropConvention(input.deviceId));
3587
3820
  if (!crop) {
3588
3821
  deps.logger.warn("runDetailSubtree: no crop source resolved — the dispatch produced nothing", {
@@ -3611,7 +3844,18 @@ async function runDetailSubtree(deps, input) {
3611
3844
  let unjudgedFaces = 0;
3612
3845
  for (const step of steps) {
3613
3846
  const placement = deps.placeStep?.(input.deviceId, step);
3614
- const result = await deps.runPipeline([placement?.step ?? step], crop.jpeg, input.deviceId, placement?.deviceKey, nativeCropRef);
3847
+ const suppliedRoot = input.supplied !== void 0 && input.supplied.stepId === step.addonId ? suppliedRootForCrop(input.supplied, crop) : void 0;
3848
+ if (suppliedRoot === null) {
3849
+ deps.logger.warn("runDetailSubtree: the root-supplied boxes fall outside the crop — nothing to read", {
3850
+ tags: { deviceId: input.deviceId },
3851
+ meta: {
3852
+ stepId: step.addonId,
3853
+ parentClass: input.parent.className
3854
+ }
3855
+ });
3856
+ continue;
3857
+ }
3858
+ const result = await deps.runPipeline([placement?.step ?? step], crop.jpeg, input.deviceId, placement?.deviceKey, nativeCropRef, ...suppliedRoot !== void 0 ? [suppliedRoot] : []);
3615
3859
  if (!result) continue;
3616
3860
  for (const detection of result.detections) {
3617
3861
  const detail = toDetailResult(detection, step.addonId, crop);
@@ -7334,15 +7578,15 @@ function isDetailPlaneStepId(stepId) {
7334
7578
  * Extracted as a pure function so the unit suite can exercise the
7335
7579
  * derivation without instantiating the full addon.
7336
7580
  */
7337
- function deriveDetailSteps(steps, plan = EMPTY_LABEL_LINK_PLAN) {
7581
+ function deriveDetailSteps(steps, plan = EMPTY_ROOT_SUPPLY_PLAN) {
7338
7582
  const stepIds = /* @__PURE__ */ new Set();
7339
7583
  collectEnabledStepIds(steps, stepIds);
7340
- const filled = filledStepIds(plan);
7584
+ const supplied = suppliedSteps(plan);
7341
7585
  const announces = [];
7342
7586
  for (const stepId of stepIds) {
7343
7587
  const def = tryGetStepDefinition(stepId);
7344
7588
  if (!def || def.inputClasses === null) continue;
7345
- const filledBy = filled.get(def.id);
7589
+ const by = supplied.get(def.id);
7346
7590
  announces.push({
7347
7591
  stepId: def.id,
7348
7592
  inputClasses: def.inputClasses,
@@ -7350,7 +7594,10 @@ function deriveDetailSteps(steps, plan = EMPTY_LABEL_LINK_PLAN) {
7350
7594
  trigger: "once",
7351
7595
  maxPerTrack: 3
7352
7596
  },
7353
- ...filledBy !== void 0 ? { filledBy } : {}
7597
+ ...by !== void 0 ? {
7598
+ filledBy: by.modelId,
7599
+ filledAs: by.kind
7600
+ } : {}
7354
7601
  });
7355
7602
  }
7356
7603
  return announces;
@@ -8482,7 +8729,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8482
8729
  if (!ctx.api) throw new Error("runCapacityRamp: ctx.api unavailable");
8483
8730
  if (this.capacityRuns.get(input.sessionId)?.status.state === "running") throw new Error(`runCapacityRamp: session '${input.sessionId}' is already running`);
8484
8731
  for (const [id, entry] of this.capacityRuns) if (entry.status.state === "running") throw new Error(`runCapacityRamp: ramp '${id}' is already running on this node — one at a time, the measurement owns the node`);
8485
- if (input.nodeId !== void 0 && input.nodeId !== this.nodeId) throw new Error(`runCapacityRamp: driver is node-local — this runner is '${this.nodeId}', refusing nodeId '${input.nodeId}'. Frames must not cross a node boundary per call (D9).`);
8732
+ if (input.nodeId !== void 0 && input.nodeId !== this.nodeId) throw new Error(`runCapacityRamp: driver is node-local — this runner is '${this.nodeId}', refusing nodeId '${input.nodeId}'. Frames must not cross a node boundary per call.`);
8486
8733
  const nonIdle = (this.runner?.getAllCameraMetrics() ?? []).filter((m) => m.phase !== "idle");
8487
8734
  const contaminated = nonIdle.length > 0;
8488
8735
  if (contaminated && input.allowActiveCameras !== true) throw new Error(`runCapacityRamp: node not quiet — ${nonIdle.length} camera(s) not idle (deviceIds ${nonIdle.map((m) => m.deviceId).join(", ")}). Pause detection first (scripts/capacity/pause-restore.mjs) or pass allowActiveCameras: true to run a knowingly contaminated measurement.`);
@@ -9532,7 +9779,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
9532
9779
  const outcome = await runDetailSubtree({
9533
9780
  resolveChildSteps: (deviceId, parentClassName, filter) => this.resolveDetailChildSteps(deviceId, parentClassName, filter),
9534
9781
  getNativeCrop: (deviceId, handle, paddedBbox) => this.getNativeCropForDetail(deviceId, handle, paddedBbox),
9535
- runPipeline: (steps, imageJpeg, deviceId, deviceKey, nativeCropRef) => this.runDetailPipeline(steps, imageJpeg, deviceId, deviceKey, nativeCropRef),
9782
+ runPipeline: (steps, imageJpeg, deviceId, deviceKey, nativeCropRef, suppliedRoot) => this.runDetailPipeline(steps, imageJpeg, deviceId, deviceKey, nativeCropRef, suppliedRoot),
9536
9783
  placeStep: (deviceId, step) => this.placeDetailStepForCamera(deviceId, step),
9537
9784
  resolveCropConvention: () => convention,
9538
9785
  logger: this.detailSubtreeLogger(),
@@ -9667,39 +9914,42 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
9667
9914
  */
9668
9915
  customModels = new CustomModelCatalog({ warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras) });
9669
9916
  /**
9670
- * Per camera, the label-link plan (D715) and the announce derived from it,
9917
+ * Per camera, the root-supply plan (D730) and the announce derived from it,
9671
9918
  * keyed on the two inputs it reads: the attach tree and the custom-model
9672
9919
  * snapshot (a Frigate+ root is a custom model). Recomputed when either
9673
9920
  * changes identity — at most once per catalog refresh.
9674
9921
  */
9675
- labelLinkCache = /* @__PURE__ */ new Map();
9676
- /** The camera's label-link plan; logs once per change of what is filled. */
9677
- labelLinkPlanFor(deviceId, steps) {
9922
+ rootSupplyCache = /* @__PURE__ */ new Map();
9923
+ /**
9924
+ * The camera's root-supply plan. One INFO per (camera, step) when a step
9925
+ * BECOMES supplied by the root — a COCO root now stops dispatching the
9926
+ * animal and vehicle classifiers by default, and that must be readable per
9927
+ * camera rather than inferred from a missing dispatch (D391).
9928
+ */
9929
+ rootSupplyPlanFor(deviceId, steps) {
9678
9930
  const byStep = this.customModels.current();
9679
- const cached = this.labelLinkCache.get(deviceId);
9931
+ const cached = this.rootSupplyCache.get(deviceId);
9680
9932
  if (cached !== void 0 && cached.steps === steps && cached.byStep === byStep) return cached.plan;
9681
- const plan = planLabelLinks(steps, (stepId, modelId) => this.customModels.stepDefinition(stepId)?.models.find((m) => m.id === modelId));
9933
+ const plan = planRootSupplies(steps, (stepId, modelId) => this.customModels.stepDefinition(stepId)?.models.find((m) => m.id === modelId));
9682
9934
  const announces = deriveDetailSteps(steps, plan);
9683
- this.labelLinkCache.set(deviceId, {
9935
+ this.rootSupplyCache.set(deviceId, {
9684
9936
  steps,
9685
9937
  byStep,
9686
9938
  plan,
9687
9939
  announces
9688
9940
  });
9689
- const describe = (p) => p === void 0 ? "" : [...p.decisions].map(([id, d]) => `${id}:${d.skip ? "skip" : d.authoritative ? "sole" : "fallback"}`).join(",");
9690
- if (describe(cached?.plan) !== describe(plan) && plan.decisions.size > 0) this.ctxIfReady?.logger.info("label link: the root model fills pipeline steps", {
9691
- tags: { deviceId },
9692
- meta: {
9693
- rootModelId: plan.rootModelId ?? null,
9694
- steps: [...plan.decisions].map(([stepId, d]) => ({
9941
+ for (const stepId of newlySuppliedSteps(cached?.plan, plan)) {
9942
+ const state = plan.states.get(stepId);
9943
+ this.ctxIfReady?.logger.info("root model supplies a pipeline step — the step is not dispatched", {
9944
+ tags: { deviceId },
9945
+ meta: {
9695
9946
  stepId,
9696
- policy: d.policy,
9697
- authoritative: d.authoritative,
9698
- skip: d.skip,
9699
- openGaps: d.openGaps
9700
- }))
9701
- }
9702
- });
9947
+ rootModelId: plan.rootModelId ?? null,
9948
+ kind: state?.supply?.kind ?? null,
9949
+ uncovered: state?.supply?.kind === "label" ? state.supply.uncovered : []
9950
+ }
9951
+ });
9952
+ }
9703
9953
  return plan;
9704
9954
  }
9705
9955
  cropConvention = new CropConventionSource({ logger: {
@@ -9967,7 +10217,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
9967
10217
  };
9968
10218
  }
9969
10219
  /** Real `runPipeline` dep: node-local `pipelineExecutor.runPipeline` on the `'full'` plane. */
9970
- async runDetailPipeline(steps, imageJpeg, deviceId, stepDeviceKey, nativeCropRef) {
10220
+ async runDetailPipeline(steps, imageJpeg, deviceId, stepDeviceKey, nativeCropRef, suppliedRoot) {
9971
10221
  const api = this.ctxIfReady?.api;
9972
10222
  if (!api) return null;
9973
10223
  const detailDeviceKey = stepDeviceKey ?? this.attached.get(deviceId)?.config?.deviceKey ?? this.freshParkedDetail(deviceId)?.deviceKey;
@@ -9978,7 +10228,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
9978
10228
  deviceId,
9979
10229
  ...detailDeviceKey ? { deviceKey: detailDeviceKey } : {},
9980
10230
  plane: "full",
9981
- ...nativeCropRef ? { nativeCropRef } : {}
10231
+ ...nativeCropRef ? { nativeCropRef } : {},
10232
+ ...suppliedRoot ? { suppliedRoot } : {}
9982
10233
  });
9983
10234
  } catch (err) {
9984
10235
  this.ctxIfReady?.logger.warn("runDetailSubtree: runPipeline failed", {
@@ -10097,7 +10348,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
10097
10348
  this.lastDecodeUnexpectedEndAt.delete(deviceId);
10098
10349
  this.packageGovernor.forget(deviceId);
10099
10350
  this.packageGateSkips.delete(deviceId);
10100
- this.labelLinkCache.delete(deviceId);
10351
+ this.rootSupplyCache.delete(deviceId);
10101
10352
  this.packageZoneUnionByDevice.delete(deviceId);
10102
10353
  this.packageZoneUnionAt.delete(deviceId);
10103
10354
  this.lastMotionNearPackageZone.delete(deviceId);
@@ -10813,8 +11064,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
10813
11064
  return null;
10814
11065
  }
10815
11066
  if (steps.length === 0) return null;
10816
- const labelLinkPlan = this.labelLinkPlanFor(deviceId, steps);
10817
- const packageFill = applyPackageFill(steps, labelLinkPlan);
11067
+ const rootSupplyPlan = this.rootSupplyPlanFor(deviceId, steps);
11068
+ const packageFill = applyPackageFill(steps, rootSupplyPlan);
10818
11069
  let dispatchSteps = packageFill.steps;
10819
11070
  if (dispatchSteps.some((s) => s.addonId === "package-detection" && s.enabled !== false)) {
10820
11071
  this.refreshPackageZoneUnion(deviceId);
@@ -10903,7 +11154,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
10903
11154
  return null;
10904
11155
  }
10905
11156
  if (result === null) return null;
10906
- const looked = withPackageLook(applyLabelLinkAuthority(result, labelLinkPlan), dispatchSteps, packageFill.filledByRoot);
11157
+ const looked = withPackageLook(applyRootSupplyAuthority(result, rootSupplyPlan), dispatchSteps, packageFill.filledByRoot);
10907
11158
  return this.recoverSmallSubjects(deviceId, frame, looked, dispatchSteps, camConfig?.deviceKey);
10908
11159
  }
10909
11160
  /**
@@ -11600,7 +11851,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
11600
11851
  emitInferenceResult(deviceId, frame, result, handle, source, occasion) {
11601
11852
  if (!this.ctx?.eventBus) return;
11602
11853
  const capturedAt = frame.capturedAt;
11603
- const detailSteps = this.labelLinkCache.get(deviceId)?.announces ?? this.attached.get(deviceId)?.detailSteps;
11854
+ const detailSteps = this.rootSupplyCache.get(deviceId)?.announces ?? this.attached.get(deviceId)?.detailSteps;
11604
11855
  const payload = {
11605
11856
  deviceId,
11606
11857
  frame: result,
@@ -1,4 +1,4 @@
1
- import { Rt as parseProcStatus } from "./dist-0HoA3wAi.mjs";
1
+ import { Bt as parseProcStatus } from "./dist-CuPEyZY2.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
4
  //#region src/shared/node-topology-platform.ts