@camstack/addon-pipeline 1.2.245 → 1.2.246

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 (28) 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-DLFtoi4W.js → default-detection-model-BokEDZLK.js} +42 -1
  4. package/dist/{default-detection-model-UXQCoRj_.mjs → default-detection-model-b1HWNGyF.mjs} +31 -2
  5. package/dist/detection-pipeline/index.js +5 -16
  6. package/dist/detection-pipeline/index.mjs +5 -16
  7. package/dist/{dist-CWWQf3ci.js → dist-FF-KPXS7.js} +47 -8
  8. package/dist/{dist-CoZwNgzu.mjs → dist-XAp9FVoN.mjs} +47 -8
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-DLZWItaN.js → node-C-KslKQf.js} +1 -1
  12. package/dist/{node-Uh59Eo57.mjs → node-umwnvSsV.mjs} +1 -1
  13. package/dist/pipeline-runner/index.js +38 -24
  14. package/dist/pipeline-runner/index.mjs +38 -24
  15. package/dist/{process-memory-Bv9SiLn-.js → process-memory-ByvPOmQm.js} +1 -1
  16. package/dist/{process-memory-BnFnR_nP.mjs → process-memory-DkBPSh44.mjs} +1 -1
  17. package/dist/recorder/index.js +2 -9
  18. package/dist/recorder/index.mjs +2 -9
  19. package/dist/{segment-demux-js-C6B53KjT.js → segment-demux-js-BCzxobGz.js} +1 -1
  20. package/dist/{segment-demux-js-7DukFIT6.mjs → segment-demux-js-BodSgaxj.mjs} +1 -1
  21. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B20FCUk4.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2-mCqUf.mjs} +3 -3
  22. package/dist/stream-broker/demux-worker-child.js +1 -1
  23. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  24. package/dist/stream-broker/{hostInit-DCTAdB0n.mjs → hostInit-BVEP1sys.mjs} +3 -3
  25. package/dist/stream-broker/index.js +69 -8
  26. package/dist/stream-broker/index.mjs +69 -8
  27. package/dist/stream-broker/remoteEntry.js +1 -1
  28. package/package.json +1 -1
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-CWWQf3ci.js");
7
- const require_process_memory = require("../process-memory-Bv9SiLn-.js");
6
+ const require_dist = require("../dist-FF-KPXS7.js");
7
+ const require_process_memory = require("../process-memory-ByvPOmQm.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
10
10
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { $t as hydrateSchema, Jt as audioChunkBytesPerSample, Lt as errMsg, N as audioAnalysisCapability, Ot as resolvePoolMemoryPolicy, P as audioAnalyzerCapability, Qt as expandAudioChunkToF32le, Wt as BaseAddon, bn as EventCategory, n as AUDIO_BACKEND_CHOICES, ot as mapAudioLabelToMacro, rn as nodePin, s as DEFAULT_AUDIO_ANALYZER_CONFIG, w as PoolMemoryWatchdog, y as HF_BASE_URL } from "../dist-CoZwNgzu.mjs";
3
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BnFnR_nP.mjs";
2
+ import { $t as hydrateSchema, Jt as audioChunkBytesPerSample, Lt as errMsg, N as audioAnalysisCapability, Ot as resolvePoolMemoryPolicy, P as audioAnalyzerCapability, Qt as expandAudioChunkToF32le, Wt as BaseAddon, bn as EventCategory, n as AUDIO_BACKEND_CHOICES, ot as mapAudioLabelToMacro, rn as nodePin, s as DEFAULT_AUDIO_ANALYZER_CONFIG, w as PoolMemoryWatchdog, y as HF_BASE_URL } from "../dist-XAp9FVoN.mjs";
3
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-DkBPSh44.mjs";
4
4
  import * as fs from "node:fs";
5
5
  import * as path$1 from "node:path";
6
6
  import { downloadFile } from "@camstack/system/addon-utils";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-CWWQf3ci.js");
1
+ const require_dist = require("./dist-FF-KPXS7.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/detection-pipeline/pipeline/landmark-precision-gate.ts
4
4
  /**
@@ -2114,6 +2114,35 @@ var localFrameRegistry = new LocalFrameRegistry({
2114
2114
  maxBytes: envPositiveInt("CAMSTACK_LOCAL_FRAME_BUDGET_MB", DEFAULT_MAX_BYTES / 1048576) * 1048576
2115
2115
  });
2116
2116
  //#endregion
2117
+ //#region src/detection-pipeline/pipeline/macro-gate.ts
2118
+ function capitalize(s) {
2119
+ if (s.length === 0) return s;
2120
+ return s.charAt(0).toUpperCase() + s.slice(1);
2121
+ }
2122
+ /** Apply the operator's class list and per-macro confidence floor. */
2123
+ function macroGateVerdict(macroClass, score, settings) {
2124
+ if (!settings) return "pass";
2125
+ const enabled = settings["enabledMacroClasses"];
2126
+ if (Array.isArray(enabled) && enabled.length > 0) {
2127
+ if (!enabled.includes(macroClass)) return "class-filter";
2128
+ }
2129
+ const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
2130
+ if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return "below-threshold";
2131
+ return "pass";
2132
+ }
2133
+ /**
2134
+ * The operator's gate, read off the ROOT step.
2135
+ *
2136
+ * The root is the first ENABLED step — the same one `executor.ts` picks
2137
+ * (`enabledSteps[0]`) — because that is the step whose settings the device
2138
+ * UI writes `enabledMacroClasses` and `minConfidence<Macro>` into. `undefined`
2139
+ * when there is no enabled step or it carries no settings, which the gate
2140
+ * itself reads as "pass everything": an absent gate must never mute a camera.
2141
+ */
2142
+ function rootObjectGate(steps) {
2143
+ return (steps?.find((s) => s.enabled !== false))?.settings;
2144
+ }
2145
+ //#endregion
2117
2146
  //#region src/detection-pipeline/default-detection-model.ts
2118
2147
  /** The object-detection step id — the only slot this resolver applies to. */
2119
2148
  var OBJECT_DETECTION_STEP_ID = "object-detection";
@@ -2244,6 +2273,12 @@ Object.defineProperty(exports, "localFrameRegistry", {
2244
2273
  return localFrameRegistry;
2245
2274
  }
2246
2275
  });
2276
+ Object.defineProperty(exports, "macroGateVerdict", {
2277
+ enumerable: true,
2278
+ get: function() {
2279
+ return macroGateVerdict;
2280
+ }
2281
+ });
2247
2282
  Object.defineProperty(exports, "resolveDefaultDetectionModel", {
2248
2283
  enumerable: true,
2249
2284
  get: function() {
@@ -2262,3 +2297,9 @@ Object.defineProperty(exports, "resolveModelForFormat", {
2262
2297
  return resolveModelForFormat;
2263
2298
  }
2264
2299
  });
2300
+ Object.defineProperty(exports, "rootObjectGate", {
2301
+ enumerable: true,
2302
+ get: function() {
2303
+ return rootObjectGate;
2304
+ }
2305
+ });
@@ -1,4 +1,4 @@
1
- import { F as audioKindId, a as COCO_80_LABELS, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS, tt as hfModelUrl } from "./dist-CoZwNgzu.mjs";
1
+ import { F as audioKindId, a as COCO_80_LABELS, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS, tt as hfModelUrl } from "./dist-XAp9FVoN.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  //#region src/detection-pipeline/pipeline/landmark-precision-gate.ts
4
4
  /**
@@ -2114,6 +2114,35 @@ var localFrameRegistry = new LocalFrameRegistry({
2114
2114
  maxBytes: envPositiveInt("CAMSTACK_LOCAL_FRAME_BUDGET_MB", DEFAULT_MAX_BYTES / 1048576) * 1048576
2115
2115
  });
2116
2116
  //#endregion
2117
+ //#region src/detection-pipeline/pipeline/macro-gate.ts
2118
+ function capitalize(s) {
2119
+ if (s.length === 0) return s;
2120
+ return s.charAt(0).toUpperCase() + s.slice(1);
2121
+ }
2122
+ /** Apply the operator's class list and per-macro confidence floor. */
2123
+ function macroGateVerdict(macroClass, score, settings) {
2124
+ if (!settings) return "pass";
2125
+ const enabled = settings["enabledMacroClasses"];
2126
+ if (Array.isArray(enabled) && enabled.length > 0) {
2127
+ if (!enabled.includes(macroClass)) return "class-filter";
2128
+ }
2129
+ const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
2130
+ if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return "below-threshold";
2131
+ return "pass";
2132
+ }
2133
+ /**
2134
+ * The operator's gate, read off the ROOT step.
2135
+ *
2136
+ * The root is the first ENABLED step — the same one `executor.ts` picks
2137
+ * (`enabledSteps[0]`) — because that is the step whose settings the device
2138
+ * UI writes `enabledMacroClasses` and `minConfidence<Macro>` into. `undefined`
2139
+ * when there is no enabled step or it carries no settings, which the gate
2140
+ * itself reads as "pass everything": an absent gate must never mute a camera.
2141
+ */
2142
+ function rootObjectGate(steps) {
2143
+ return (steps?.find((s) => s.enabled !== false))?.settings;
2144
+ }
2145
+ //#endregion
2117
2146
  //#region src/detection-pipeline/default-detection-model.ts
2118
2147
  /** The object-detection step id — the only slot this resolver applies to. */
2119
2148
  var OBJECT_DETECTION_STEP_ID = "object-detection";
@@ -2196,4 +2225,4 @@ function resolveDefaultDetectionModel(backend, device, format, getStepDef = getS
2196
2225
  return getDefaultModelForFormatFromDef(def, format);
2197
2226
  }
2198
2227
  //#endregion
2199
- export { ALL_PIPELINE_STEPS as a, getStep as c, landmarkPrecisionVerdict as d, resolveFrameViewGeometry as i, getStepDefinition as l, resolveDefaultDetectionModel as n, ALL_STEPS as o, localFrameRegistry as r, getDefaultModelForFormat as s, OBJECT_DETECTION_STEP_ID as t, resolveModelForFormat as u };
2228
+ export { localFrameRegistry as a, ALL_STEPS as c, getStepDefinition as d, resolveModelForFormat as f, rootObjectGate as i, getDefaultModelForFormat as l, resolveDefaultDetectionModel as n, resolveFrameViewGeometry as o, landmarkPrecisionVerdict as p, macroGateVerdict as r, ALL_PIPELINE_STEPS as s, OBJECT_DETECTION_STEP_ID as t, getStep as u };
@@ -3,11 +3,11 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-CWWQf3ci.js");
7
- const require_node = require("../node-DLZWItaN.js");
8
- const require_default_detection_model = require("../default-detection-model-DLFtoi4W.js");
6
+ const require_dist = require("../dist-FF-KPXS7.js");
7
+ const require_node = require("../node-C-KslKQf.js");
8
+ const require_default_detection_model = require("../default-detection-model-BokEDZLK.js");
9
9
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
10
- const require_process_memory = require("../process-memory-Bv9SiLn-.js");
10
+ const require_process_memory = require("../process-memory-ByvPOmQm.js");
11
11
  let node_os = require("node:os");
12
12
  node_os = require_chunk.__toESM(node_os);
13
13
  let node_fs = require("node:fs");
@@ -5891,20 +5891,9 @@ var PipelineExecutor = class {
5891
5891
  * - `minConfidence<Macro>`: number (e.g. `minConfidencePerson: 0.5`)
5892
5892
  */
5893
5893
  macroFilterVerdict(macroClass, score, settings) {
5894
- if (!settings) return "pass";
5895
- const enabled = settings["enabledMacroClasses"];
5896
- if (Array.isArray(enabled) && enabled.length > 0) {
5897
- if (!enabled.includes(macroClass)) return "class-filter";
5898
- }
5899
- const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
5900
- if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return "below-threshold";
5901
- return "pass";
5894
+ return require_default_detection_model.macroGateVerdict(macroClass, score, settings);
5902
5895
  }
5903
5896
  };
5904
- function capitalize(s) {
5905
- if (s.length === 0) return s;
5906
- return s.charAt(0).toUpperCase() + s.slice(1);
5907
- }
5908
5897
  //#endregion
5909
5898
  //#region src/detection-pipeline/pipeline/native-crop-compose.ts
5910
5899
  /** Clamp `v` into `[lo, hi]`. */
@@ -1,9 +1,9 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { $t as hydrateSchema, At as runtimeDevices$1, Ft as supportedRuntimes$1, G as detectionPipelineCapability, J as enumerateInferenceDevices, Lt as errMsg, Ot as resolvePoolMemoryPolicy, Tt as resolveClusterStepModelId, V as defaultDeviceFor$1, Wt as BaseAddon, X as evaluateZoneRules, Yt as createEvent, bn as EventCategory, c as DEFAULT_CLUSTER_STEP_MODELS, gt as pickClusterStepSettings, hn as object, ht as pickClusterStepModels, in as parseJsonUnknown, it as loadContributionCapability, j as YAMNET_TO_MACRO, l as DEFAULT_CLUSTER_STEP_SETTINGS, ln as array, m as DEVICE_BACKEND_TO_FORMAT, nt as inferModelProvider, pt as overlayClusterStepSettings, rn as nodePin, sn as sleep, t as APPLE_SA_TO_MACRO, vn as string, w as PoolMemoryWatchdog, yn as union, yt as pipelineExecutorCapability } from "../dist-CoZwNgzu.mjs";
3
- import { o as readProcessCost } from "../node-Uh59Eo57.mjs";
4
- import { a as ALL_PIPELINE_STEPS, c as getStep, d as landmarkPrecisionVerdict, l as getStepDefinition, n as resolveDefaultDetectionModel, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as resolveModelForFormat } from "../default-detection-model-UXQCoRj_.mjs";
2
+ import { $t as hydrateSchema, At as runtimeDevices$1, Ft as supportedRuntimes$1, G as detectionPipelineCapability, J as enumerateInferenceDevices, Lt as errMsg, Ot as resolvePoolMemoryPolicy, Tt as resolveClusterStepModelId, V as defaultDeviceFor$1, Wt as BaseAddon, X as evaluateZoneRules, Yt as createEvent, bn as EventCategory, c as DEFAULT_CLUSTER_STEP_MODELS, gt as pickClusterStepSettings, hn as object, ht as pickClusterStepModels, in as parseJsonUnknown, it as loadContributionCapability, j as YAMNET_TO_MACRO, l as DEFAULT_CLUSTER_STEP_SETTINGS, ln as array, m as DEVICE_BACKEND_TO_FORMAT, nt as inferModelProvider, pt as overlayClusterStepSettings, rn as nodePin, sn as sleep, t as APPLE_SA_TO_MACRO, vn as string, w as PoolMemoryWatchdog, yn as union, yt as pipelineExecutorCapability } from "../dist-XAp9FVoN.mjs";
3
+ import { o as readProcessCost } from "../node-umwnvSsV.mjs";
4
+ import { a as localFrameRegistry, c as ALL_STEPS, d as getStepDefinition, f as resolveModelForFormat, l as getDefaultModelForFormat, n as resolveDefaultDetectionModel, p as landmarkPrecisionVerdict, r as macroGateVerdict, s as ALL_PIPELINE_STEPS, u as getStep } from "../default-detection-model-b1HWNGyF.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
6
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BnFnR_nP.mjs";
6
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-DkBPSh44.mjs";
7
7
  import * as os from "node:os";
8
8
  import * as fs from "node:fs";
9
9
  import * as path$1 from "node:path";
@@ -5884,20 +5884,9 @@ var PipelineExecutor = class {
5884
5884
  * - `minConfidence<Macro>`: number (e.g. `minConfidencePerson: 0.5`)
5885
5885
  */
5886
5886
  macroFilterVerdict(macroClass, score, settings) {
5887
- if (!settings) return "pass";
5888
- const enabled = settings["enabledMacroClasses"];
5889
- if (Array.isArray(enabled) && enabled.length > 0) {
5890
- if (!enabled.includes(macroClass)) return "class-filter";
5891
- }
5892
- const perMacroThreshold = settings[`minConfidence${capitalize(macroClass)}`];
5893
- if (typeof perMacroThreshold === "number" && score < perMacroThreshold) return "below-threshold";
5894
- return "pass";
5887
+ return macroGateVerdict(macroClass, score, settings);
5895
5888
  }
5896
5889
  };
5897
- function capitalize(s) {
5898
- if (s.length === 0) return s;
5899
- return s.charAt(0).toUpperCase() + s.slice(1);
5900
- }
5901
5890
  //#endregion
5902
5891
  //#region src/detection-pipeline/pipeline/native-crop-compose.ts
5903
5892
  /** Clamp `v` into `[lo, hi]`. */
@@ -21905,13 +21905,26 @@ var TrackAudioLabelSchema = object({
21905
21905
  * - `audio` — an audio event on the camera itself that was anomalous for
21906
21906
  * THAT camera, loud, and heard while nothing visual was happening (D62).
21907
21907
  *
21908
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
21909
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
21910
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
21911
+ * detector.
21912
+ *
21913
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
21914
+ * a persisted onboard track failed `safeParse` on read and came back with NO
21915
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
21916
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
21917
+ * could not name.
21918
+ *
21908
21919
  * The spatial subsystems (tracker association, occupancy count, re-id /
21909
21920
  * embedding, resurrection) MUST skip every synthetic source. Test for that
21910
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
21911
- * check silently readmits every source added after it was written.
21921
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
21922
+ * `!== 'sensor'` check silently readmits every source added after it was
21923
+ * written.
21912
21924
  */
21913
21925
  var TrackSourceSchema = _enum([
21914
21926
  "pipeline",
21927
+ "onboard",
21915
21928
  "sensor",
21916
21929
  "audio"
21917
21930
  ]);
@@ -23993,13 +24006,39 @@ var MotionSourceEnum = _enum([
23993
24006
  */
23994
24007
  var MotionSourcesSchema = array(MotionSourceEnum);
23995
24008
  /**
23996
- * Which root detectors a camera runs. Deliberately the SAME vocabulary
23997
- * post-analysis already tags every detection with (`DetectionSource`) rather
23998
- * than a second spelling of the same two ideas — the value an operator picks
23999
- * here is the value that comes back on the track, the overlay and the debug
24000
- * row.
24009
+ * Which root detector a camera runs — EXACTLY ONE.
24010
+ *
24011
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
24012
+ * with (`DetectionSource`) rather than a second spelling of the same two
24013
+ * ideas: the value an operator picks here is the value that comes back on the
24014
+ * track, the overlay and the debug row.
24015
+ *
24016
+ * ## Why one, and why it is still an array
24017
+ *
24018
+ * Two roots on one camera is a capability nobody asked for and the operator has
24019
+ * since ruled out. It was never free: both planes feed the SAME per-device
24020
+ * stationary registry, both can promote the same parked object, and the two
24021
+ * detectors disagree about the same box by construction — which is the
24022
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
24023
+ *
24024
+ * The ARRAY shape survives because the stored settings and the attach payload
24025
+ * already speak it on every camera of every fleet, and a cap input that
24026
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
24027
+ * in the one direction that costs an operator their cameras. So the wire stays
24028
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
24029
+ * element rather than refused, and `max(1)` is what every consumer can then
24030
+ * rely on.
24031
+ *
24032
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
24033
+ * a camera an operator left with no root detector — and `planDetectionSources`
24034
+ * has always said so: inventing a root for them is how a default acts without
24035
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
24036
+ * suite caught it.
24037
+ *
24038
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
24039
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
24001
24040
  */
24002
- var DetectionSourcesSchema = array(DetectionSourceSchema);
24041
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
24003
24042
  /**
24004
24043
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
24005
24044
  * so cap-side consumers (the orchestrator forward, the runner addon's
@@ -21905,13 +21905,26 @@ var TrackAudioLabelSchema = object({
21905
21905
  * - `audio` — an audio event on the camera itself that was anomalous for
21906
21906
  * THAT camera, loud, and heard while nothing visual was happening (D62).
21907
21907
  *
21908
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
21909
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
21910
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
21911
+ * detector.
21912
+ *
21913
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
21914
+ * a persisted onboard track failed `safeParse` on read and came back with NO
21915
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
21916
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
21917
+ * could not name.
21918
+ *
21908
21919
  * The spatial subsystems (tracker association, occupancy count, re-id /
21909
21920
  * embedding, resurrection) MUST skip every synthetic source. Test for that
21910
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
21911
- * check silently readmits every source added after it was written.
21921
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
21922
+ * `!== 'sensor'` check silently readmits every source added after it was
21923
+ * written.
21912
21924
  */
21913
21925
  var TrackSourceSchema = _enum([
21914
21926
  "pipeline",
21927
+ "onboard",
21915
21928
  "sensor",
21916
21929
  "audio"
21917
21930
  ]);
@@ -23993,13 +24006,39 @@ var MotionSourceEnum = _enum([
23993
24006
  */
23994
24007
  var MotionSourcesSchema = array(MotionSourceEnum);
23995
24008
  /**
23996
- * Which root detectors a camera runs. Deliberately the SAME vocabulary
23997
- * post-analysis already tags every detection with (`DetectionSource`) rather
23998
- * than a second spelling of the same two ideas — the value an operator picks
23999
- * here is the value that comes back on the track, the overlay and the debug
24000
- * row.
24009
+ * Which root detector a camera runs — EXACTLY ONE.
24010
+ *
24011
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
24012
+ * with (`DetectionSource`) rather than a second spelling of the same two
24013
+ * ideas: the value an operator picks here is the value that comes back on the
24014
+ * track, the overlay and the debug row.
24015
+ *
24016
+ * ## Why one, and why it is still an array
24017
+ *
24018
+ * Two roots on one camera is a capability nobody asked for and the operator has
24019
+ * since ruled out. It was never free: both planes feed the SAME per-device
24020
+ * stationary registry, both can promote the same parked object, and the two
24021
+ * detectors disagree about the same box by construction — which is the
24022
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
24023
+ *
24024
+ * The ARRAY shape survives because the stored settings and the attach payload
24025
+ * already speak it on every camera of every fleet, and a cap input that
24026
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
24027
+ * in the one direction that costs an operator their cameras. So the wire stays
24028
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
24029
+ * element rather than refused, and `max(1)` is what every consumer can then
24030
+ * rely on.
24031
+ *
24032
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
24033
+ * a camera an operator left with no root detector — and `planDetectionSources`
24034
+ * has always said so: inventing a root for them is how a default acts without
24035
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
24036
+ * suite caught it.
24037
+ *
24038
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
24039
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
24001
24040
  */
24002
- var DetectionSourcesSchema = array(DetectionSourceSchema);
24041
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
24003
24042
  /**
24004
24043
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
24005
24044
  * so cap-side consumers (the orchestrator forward, the runner addon's
@@ -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-CWWQf3ci.js");
5
+ const require_dist = require("../dist-FF-KPXS7.js");
6
6
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { $t as hydrateSchema, Wt as BaseAddon, X as evaluateZoneRules, ft as motionDetectionCapability, qt as DeviceType } from "../dist-CoZwNgzu.mjs";
1
+ import { $t as hydrateSchema, Wt as BaseAddon, X as evaluateZoneRules, ft as motionDetectionCapability, qt as DeviceType } from "../dist-XAp9FVoN.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-CWWQf3ci.js");
2
+ const require_dist = require("./dist-FF-KPXS7.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs, 1);
@@ -1,4 +1,4 @@
1
- import { Bt as buildFfmpegArgs, Ht as isSoftwareDecode, Lt as errMsg, zt as Fmp4BoxSplitter } from "./dist-CoZwNgzu.mjs";
1
+ import { Bt as buildFfmpegArgs, Ht as isSoftwareDecode, Lt as errMsg, zt as Fmp4BoxSplitter } from "./dist-XAp9FVoN.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-CWWQf3ci.js");
7
- const require_node = require("../node-DLZWItaN.js");
8
- const require_default_detection_model = require("../default-detection-model-DLFtoi4W.js");
6
+ const require_dist = require("../dist-FF-KPXS7.js");
7
+ const require_node = require("../node-C-KslKQf.js");
8
+ const require_default_detection_model = require("../default-detection-model-BokEDZLK.js");
9
9
  const require_worker_protocol = require("../worker-protocol-B2MfQLlu.js");
10
10
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
11
11
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
@@ -2129,7 +2129,7 @@ function clamp(value, max) {
2129
2129
  * the boxes travel with a real frame and a real handle, so they are scaled to
2130
2130
  * that frame — the same space every other detection on this plane lives in.
2131
2131
  */
2132
- function onboardRootDetections(slot, frame) {
2132
+ function onboardRootDetections(slot, frame, gate) {
2133
2133
  if (slot === null || slot.detections.length === 0) return { kind: "none" };
2134
2134
  const ageMs = Math.abs(frame.timestamp - slot.at);
2135
2135
  if (ageMs > 1e3) return {
@@ -2141,26 +2141,33 @@ function onboardRootDetections(slot, frame) {
2141
2141
  kind: "no-geometry",
2142
2142
  classes: slot.detections.map((d) => d.class)
2143
2143
  };
2144
+ const detections = boxed.map((d, i) => {
2145
+ const [nx, ny, nw, nh] = d.bbox;
2146
+ const x = clamp(nx * frame.width, frame.width);
2147
+ const y = clamp(ny * frame.height, frame.height);
2148
+ return {
2149
+ id: `onboard-${String(slot.at)}-${String(i)}`,
2150
+ kind: "first-level",
2151
+ macroClass: d.class,
2152
+ score: d.confidence ?? 1,
2153
+ labels: [],
2154
+ bbox: {
2155
+ x,
2156
+ y,
2157
+ width: clamp(nw * frame.width, frame.width - x),
2158
+ height: clamp(nh * frame.height, frame.height - y)
2159
+ }
2160
+ };
2161
+ });
2162
+ const admitted = detections.filter((d) => require_default_detection_model.macroGateVerdict(d.macroClass, d.score, gate) === "pass");
2163
+ if (admitted.length === 0) return {
2164
+ kind: "gated",
2165
+ refused: detections.length
2166
+ };
2144
2167
  return {
2145
2168
  kind: "paired",
2146
- detections: boxed.map((d, i) => {
2147
- const [nx, ny, nw, nh] = d.bbox;
2148
- const x = clamp(nx * frame.width, frame.width);
2149
- const y = clamp(ny * frame.height, frame.height);
2150
- return {
2151
- id: `onboard-${String(slot.at)}-${String(i)}`,
2152
- kind: "first-level",
2153
- macroClass: d.class,
2154
- score: d.confidence ?? 1,
2155
- labels: [],
2156
- bbox: {
2157
- x,
2158
- y,
2159
- width: clamp(nw * frame.width, frame.width - x),
2160
- height: clamp(nh * frame.height, frame.height - y)
2161
- }
2162
- };
2163
- })
2169
+ detections: admitted,
2170
+ ...admitted.length < detections.length ? { refused: detections.length - admitted.length } : {}
2164
2171
  };
2165
2172
  }
2166
2173
  //#endregion
@@ -6352,7 +6359,7 @@ var PipelineRunner = class {
6352
6359
  timestamp: Date.now(),
6353
6360
  width: frame.width,
6354
6361
  height: frame.height
6355
- });
6362
+ }, state.registration.objectGate);
6356
6363
  if (pairing.kind !== "paired") return result;
6357
6364
  state.onboardSlot = null;
6358
6365
  return {
@@ -6389,8 +6396,9 @@ var PipelineRunner = class {
6389
6396
  timestamp: Date.now(),
6390
6397
  width: frame.width,
6391
6398
  height: frame.height
6392
- });
6399
+ }, state.registration.objectGate);
6393
6400
  if (pairing.kind !== "paired") {
6401
+ if (pairing.kind === "gated") state.onboardSlot = null;
6394
6402
  this.noteOnboardRootIdle(deviceId, pairing.kind);
6395
6403
  return;
6396
6404
  }
@@ -6406,6 +6414,7 @@ var PipelineRunner = class {
6406
6414
  detections: pairing.detections
6407
6415
  };
6408
6416
  state.lastResultDetections = pairing.detections.length;
6417
+ if (handle) this.config.markFrameAskable?.(handle);
6409
6418
  await this.notifyCallbacks(deviceId, frame, result, streamType, handle, "onboard", entry.occasion ?? "session");
6410
6419
  frameReleaseReason = "success";
6411
6420
  } catch {
@@ -7587,6 +7596,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
7587
7596
  this.frameCutters.drop(handle);
7588
7597
  },
7589
7598
  hydrateRetainedFrame: (handle) => this.retainedFrames?.ensureContent(handle) ?? Promise.resolve(null),
7599
+ markFrameAskable: (handle) => {
7600
+ const found = this.nativeCropRegistry.lookup(handle);
7601
+ if (found.kind === "live") found.request.markInferred?.();
7602
+ },
7590
7603
  onPhaseChanged: (deviceId, phase, meta) => this.handlePhaseChanged(deviceId, phase, meta),
7591
7604
  logger: this.ctx.logger,
7592
7605
  onOccupancyRecheck: (deviceId, frames) => {
@@ -8455,6 +8468,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8455
8468
  runner.registerCamera(config.deviceId, {
8456
8469
  detectionMode: config.detectionMode,
8457
8470
  ...config.detectionSources !== void 0 ? { detectionSources: config.detectionSources } : {},
8471
+ ...require_default_detection_model.rootObjectGate(config.steps) !== void 0 ? { objectGate: require_default_detection_model.rootObjectGate(config.steps) } : {},
8458
8472
  fps: config.detectionFps,
8459
8473
  motionCooldownMs: config.motionCooldownMs,
8460
8474
  occupancyRecheckEnabled: config.occupancyRecheckEnabled,
@@ -1,6 +1,6 @@
1
- import { $ as failureContributionCapability, Lt as errMsg, S as NativeLeaseSettingsSchema, U as deriveDetailCropRect, Wt as BaseAddon, Xt as customAction, Yt as createEvent, Zt as defineCustomActions, _ as FailureCounters, _t as pickDetailCropConvention, b as MOTION_CLOSE_AFTER_MS, bn as EventCategory, bt as pipelineRunnerCapability, cn as _enum, fn as lazy, hn as object, it as loadContributionCapability, ln as array, m as DEVICE_BACKEND_TO_FORMAT, mn as number, p as DEFAULT_NATIVE_LEASE_SETTINGS, pn as literal, rn as nodePin, u as DEFAULT_DETAIL_CROP_CONVENTION, un as boolean, vn as string, vt as pickNativeLeaseOverride, x as NativeLeaseAdmissionSchema } from "../dist-CoZwNgzu.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-Uh59Eo57.mjs";
3
- import { i as resolveFrameViewGeometry, l as getStepDefinition, r as localFrameRegistry } from "../default-detection-model-UXQCoRj_.mjs";
1
+ import { $ as failureContributionCapability, Lt as errMsg, S as NativeLeaseSettingsSchema, U as deriveDetailCropRect, Wt as BaseAddon, Xt as customAction, Yt as createEvent, Zt as defineCustomActions, _ as FailureCounters, _t as pickDetailCropConvention, b as MOTION_CLOSE_AFTER_MS, bn as EventCategory, bt as pipelineRunnerCapability, cn as _enum, fn as lazy, hn as object, it as loadContributionCapability, ln as array, m as DEVICE_BACKEND_TO_FORMAT, mn as number, p as DEFAULT_NATIVE_LEASE_SETTINGS, pn as literal, rn as nodePin, u as DEFAULT_DETAIL_CROP_CONVENTION, un as boolean, vn as string, vt as pickNativeLeaseOverride, x as NativeLeaseAdmissionSchema } from "../dist-XAp9FVoN.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-umwnvSsV.mjs";
3
+ import { a as localFrameRegistry, d as getStepDefinition, i as rootObjectGate, o as resolveFrameViewGeometry, r as macroGateVerdict } from "../default-detection-model-b1HWNGyF.mjs";
4
4
  import { t as isWorkerReply } from "../worker-protocol-C_W-P_g-.mjs";
5
5
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
6
6
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
@@ -2122,7 +2122,7 @@ function clamp(value, max) {
2122
2122
  * the boxes travel with a real frame and a real handle, so they are scaled to
2123
2123
  * that frame — the same space every other detection on this plane lives in.
2124
2124
  */
2125
- function onboardRootDetections(slot, frame) {
2125
+ function onboardRootDetections(slot, frame, gate) {
2126
2126
  if (slot === null || slot.detections.length === 0) return { kind: "none" };
2127
2127
  const ageMs = Math.abs(frame.timestamp - slot.at);
2128
2128
  if (ageMs > 1e3) return {
@@ -2134,26 +2134,33 @@ function onboardRootDetections(slot, frame) {
2134
2134
  kind: "no-geometry",
2135
2135
  classes: slot.detections.map((d) => d.class)
2136
2136
  };
2137
+ const detections = boxed.map((d, i) => {
2138
+ const [nx, ny, nw, nh] = d.bbox;
2139
+ const x = clamp(nx * frame.width, frame.width);
2140
+ const y = clamp(ny * frame.height, frame.height);
2141
+ return {
2142
+ id: `onboard-${String(slot.at)}-${String(i)}`,
2143
+ kind: "first-level",
2144
+ macroClass: d.class,
2145
+ score: d.confidence ?? 1,
2146
+ labels: [],
2147
+ bbox: {
2148
+ x,
2149
+ y,
2150
+ width: clamp(nw * frame.width, frame.width - x),
2151
+ height: clamp(nh * frame.height, frame.height - y)
2152
+ }
2153
+ };
2154
+ });
2155
+ const admitted = detections.filter((d) => macroGateVerdict(d.macroClass, d.score, gate) === "pass");
2156
+ if (admitted.length === 0) return {
2157
+ kind: "gated",
2158
+ refused: detections.length
2159
+ };
2137
2160
  return {
2138
2161
  kind: "paired",
2139
- detections: boxed.map((d, i) => {
2140
- const [nx, ny, nw, nh] = d.bbox;
2141
- const x = clamp(nx * frame.width, frame.width);
2142
- const y = clamp(ny * frame.height, frame.height);
2143
- return {
2144
- id: `onboard-${String(slot.at)}-${String(i)}`,
2145
- kind: "first-level",
2146
- macroClass: d.class,
2147
- score: d.confidence ?? 1,
2148
- labels: [],
2149
- bbox: {
2150
- x,
2151
- y,
2152
- width: clamp(nw * frame.width, frame.width - x),
2153
- height: clamp(nh * frame.height, frame.height - y)
2154
- }
2155
- };
2156
- })
2162
+ detections: admitted,
2163
+ ...admitted.length < detections.length ? { refused: detections.length - admitted.length } : {}
2157
2164
  };
2158
2165
  }
2159
2166
  //#endregion
@@ -6344,7 +6351,7 @@ var PipelineRunner = class {
6344
6351
  timestamp: Date.now(),
6345
6352
  width: frame.width,
6346
6353
  height: frame.height
6347
- });
6354
+ }, state.registration.objectGate);
6348
6355
  if (pairing.kind !== "paired") return result;
6349
6356
  state.onboardSlot = null;
6350
6357
  return {
@@ -6381,8 +6388,9 @@ var PipelineRunner = class {
6381
6388
  timestamp: Date.now(),
6382
6389
  width: frame.width,
6383
6390
  height: frame.height
6384
- });
6391
+ }, state.registration.objectGate);
6385
6392
  if (pairing.kind !== "paired") {
6393
+ if (pairing.kind === "gated") state.onboardSlot = null;
6386
6394
  this.noteOnboardRootIdle(deviceId, pairing.kind);
6387
6395
  return;
6388
6396
  }
@@ -6398,6 +6406,7 @@ var PipelineRunner = class {
6398
6406
  detections: pairing.detections
6399
6407
  };
6400
6408
  state.lastResultDetections = pairing.detections.length;
6409
+ if (handle) this.config.markFrameAskable?.(handle);
6401
6410
  await this.notifyCallbacks(deviceId, frame, result, streamType, handle, "onboard", entry.occasion ?? "session");
6402
6411
  frameReleaseReason = "success";
6403
6412
  } catch {
@@ -7579,6 +7588,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
7579
7588
  this.frameCutters.drop(handle);
7580
7589
  },
7581
7590
  hydrateRetainedFrame: (handle) => this.retainedFrames?.ensureContent(handle) ?? Promise.resolve(null),
7591
+ markFrameAskable: (handle) => {
7592
+ const found = this.nativeCropRegistry.lookup(handle);
7593
+ if (found.kind === "live") found.request.markInferred?.();
7594
+ },
7582
7595
  onPhaseChanged: (deviceId, phase, meta) => this.handlePhaseChanged(deviceId, phase, meta),
7583
7596
  logger: this.ctx.logger,
7584
7597
  onOccupancyRecheck: (deviceId, frames) => {
@@ -8447,6 +8460,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8447
8460
  runner.registerCamera(config.deviceId, {
8448
8461
  detectionMode: config.detectionMode,
8449
8462
  ...config.detectionSources !== void 0 ? { detectionSources: config.detectionSources } : {},
8463
+ ...rootObjectGate(config.steps) !== void 0 ? { objectGate: rootObjectGate(config.steps) } : {},
8450
8464
  fps: config.detectionFps,
8451
8465
  motionCooldownMs: config.motionCooldownMs,
8452
8466
  occupancyRecheckEnabled: config.occupancyRecheckEnabled,
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-CWWQf3ci.js");
2
+ const require_dist = require("./dist-FF-KPXS7.js");
3
3
  let node_fs = require("node:fs");
4
4
  node_fs = require_chunk.__toESM(node_fs);
5
5
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { mt as parseProcStatus } from "./dist-CoZwNgzu.mjs";
1
+ import { mt as parseProcStatus } from "./dist-XAp9FVoN.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
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-CWWQf3ci.js");
7
- const require_node = require("../node-DLZWItaN.js");
6
+ const require_dist = require("../dist-FF-KPXS7.js");
7
+ const require_node = require("../node-C-KslKQf.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
9
  const require_restream_intent = require("../restream-intent-C4adO0P8.js");
10
10
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
@@ -555,8 +555,6 @@ var MfraTableStore = class {
555
555
  */
556
556
  /** Il `kind` dell'unico ramo del topic che può aprire una finestra. */
557
557
  var OBJECT_EVENT_KIND = "object";
558
- /** La sorgente implicita delle righe legacy senza `source` (`DetectionSourceSchema`). */
559
- var DEFAULT_DETECTION_SOURCE = "pipeline";
560
558
  /** Type guard, non cast: `event.data` è `unknown` finché non passa di qui. */
561
559
  function isDetectionEventEnvelope(value) {
562
560
  if (typeof value !== "object" || value === null) return false;
@@ -589,11 +587,6 @@ function qualifyObjectTrigger(data) {
589
587
  reason: "malformed-object",
590
588
  deviceId
591
589
  };
592
- if ((data.source ?? DEFAULT_DETECTION_SOURCE) !== DEFAULT_DETECTION_SOURCE) return {
593
- qualified: false,
594
- reason: "not-pipeline",
595
- deviceId
596
- };
597
590
  const className = data.className.trim().toLowerCase();
598
591
  if (!require_dist.isFirstLevelMacroClass(className)) return {
599
592
  qualified: false,
@@ -1,5 +1,5 @@
1
- import { $t as hydrateSchema, C as OpsLogEntrySchema, Ct as recordingExportCapability, D as RECORDING_EXPORT_MAX_READ_BYTES, Dt as resolveLocationMode, H as deriveBatteryPresence, I as batteryCapability, Kt as DeviceFeature, Lt as errMsg, Mt as storageOccupancyCapability, O as RecordingConfigSchema, Pt as stripRetiredBandPreBufferSec, Q as evictionPolicyOfLocation, St as recordingCapability, W as deriveRecordingMode, Wt as BaseAddon, Xt as customAction, Y as evaluateSensorEdge, Z as evictionPolicyForMode, Zt as defineCustomActions, _n as record, bn as EventCategory, cn as _enum, ct as mayReadLocation, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, dt as modeMayWrite, et as gbToBytes, g as ExportRecordSchema, gn as preprocess, hn as object, i as BatteryStatusSchema, it as loadContributionCapability, jt as storageEvictableCapability, k as RecordingSignalStatusSchema, kt as resolveRecordingProfiles, ln as array, lt as mayWriteToLocation, mn as number, on as selectAssignedProfileSlots, qt as DeviceType, rn as nodePin, rt as isFirstLevelMacroClass, sn as sleep$1, un as boolean, ut as migrateLegacyDeviceSignalConfig, vn as string, wt as recordingSignalCapability } from "../dist-CoZwNgzu.mjs";
2
- import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-Uh59Eo57.mjs";
1
+ import { $t as hydrateSchema, C as OpsLogEntrySchema, Ct as recordingExportCapability, D as RECORDING_EXPORT_MAX_READ_BYTES, Dt as resolveLocationMode, H as deriveBatteryPresence, I as batteryCapability, Kt as DeviceFeature, Lt as errMsg, Mt as storageOccupancyCapability, O as RecordingConfigSchema, Pt as stripRetiredBandPreBufferSec, Q as evictionPolicyOfLocation, St as recordingCapability, W as deriveRecordingMode, Wt as BaseAddon, Xt as customAction, Y as evaluateSensorEdge, Z as evictionPolicyForMode, Zt as defineCustomActions, _n as record, bn as EventCategory, cn as _enum, ct as mayReadLocation, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, dt as modeMayWrite, et as gbToBytes, g as ExportRecordSchema, gn as preprocess, hn as object, i as BatteryStatusSchema, it as loadContributionCapability, jt as storageEvictableCapability, k as RecordingSignalStatusSchema, kt as resolveRecordingProfiles, ln as array, lt as mayWriteToLocation, mn as number, on as selectAssignedProfileSlots, qt as DeviceType, rn as nodePin, rt as isFirstLevelMacroClass, sn as sleep$1, un as boolean, ut as migrateLegacyDeviceSignalConfig, vn as string, wt as recordingSignalCapability } from "../dist-XAp9FVoN.mjs";
2
+ import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-umwnvSsV.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
4
  import { c as PREBUFFER_MAX_MS, r as RECORDING_PRE_ROLL_REQUEST_MS, s as withRecordingIntent } from "../restream-intent-MeJhGCK9.mjs";
5
5
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
@@ -549,8 +549,6 @@ var MfraTableStore = class {
549
549
  */
550
550
  /** Il `kind` dell'unico ramo del topic che può aprire una finestra. */
551
551
  var OBJECT_EVENT_KIND = "object";
552
- /** La sorgente implicita delle righe legacy senza `source` (`DetectionSourceSchema`). */
553
- var DEFAULT_DETECTION_SOURCE = "pipeline";
554
552
  /** Type guard, non cast: `event.data` è `unknown` finché non passa di qui. */
555
553
  function isDetectionEventEnvelope(value) {
556
554
  if (typeof value !== "object" || value === null) return false;
@@ -583,11 +581,6 @@ function qualifyObjectTrigger(data) {
583
581
  reason: "malformed-object",
584
582
  deviceId
585
583
  };
586
- if ((data.source ?? DEFAULT_DETECTION_SOURCE) !== DEFAULT_DETECTION_SOURCE) return {
587
- qualified: false,
588
- reason: "not-pipeline",
589
- deviceId
590
- };
591
584
  const className = data.className.trim().toLowerCase();
592
585
  if (!isFirstLevelMacroClass(className)) return {
593
586
  qualified: false,
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-CWWQf3ci.js");
2
+ const require_dist = require("./dist-FF-KPXS7.js");
3
3
  let node_child_process = require("node:child_process");
4
4
  //#region ../../node_modules/mp4box/dist/mp4box.all.js
5
5
  var require_mp4box_all = /* @__PURE__ */ require_chunk.__commonJSMin(((exports) => {
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "./chunk-DnnnRqeS.mjs";
2
- import { Lt as errMsg } from "./dist-CoZwNgzu.mjs";
2
+ import { Lt as errMsg } from "./dist-XAp9FVoN.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region ../../node_modules/mp4box/dist/mp4box.all.js
5
5
  var require_mp4box_all = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.106",
6
+ version: "1.2.107",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_stream_broker_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.200",
21
+ version: "1.2.201",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.165",
36
+ version: "1.2.166",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",
@@ -1,4 +1,4 @@
1
- const require_segment_demux_js = require("../segment-demux-js-C6B53KjT.js");
1
+ const require_segment_demux_js = require("../segment-demux-js-BCzxobGz.js");
2
2
  let node_worker_threads = require("node:worker_threads");
3
3
  //#region src/stream-broker/recorded/demux-worker-child.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { t as demuxSegmentFullJs } from "../segment-demux-js-7DukFIT6.mjs";
1
+ import { t as demuxSegmentFullJs } from "../segment-demux-js-BodSgaxj.mjs";
2
2
  import { parentPort } from "node:worker_threads";
3
3
  //#region src/stream-broker/recorded/demux-worker-child.ts
4
4
  /**
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.200",
39
+ version: "1.2.201",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.2.106",
48
+ version: "1.2.107",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.165",
84
+ version: "1.2.166",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -3,12 +3,12 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-CWWQf3ci.js");
7
- const require_node = require("../node-DLZWItaN.js");
6
+ const require_dist = require("../dist-FF-KPXS7.js");
7
+ const require_node = require("../node-C-KslKQf.js");
8
8
  const require_restream_intent = require("../restream-intent-C4adO0P8.js");
9
9
  const require_remote_restream = require("../remote-restream-CO36Sr30.js");
10
10
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
11
- const require_segment_demux_js = require("../segment-demux-js-C6B53KjT.js");
11
+ const require_segment_demux_js = require("../segment-demux-js-BCzxobGz.js");
12
12
  let node_os = require("node:os");
13
13
  node_os = require_chunk.__toESM(node_os);
14
14
  let node_crypto = require("node:crypto");
@@ -4207,6 +4207,8 @@ var RtspListenServer = class RtspListenServer {
4207
4207
  brokerEnabled = /* @__PURE__ */ new Map();
4208
4208
  /** brokerId → warm-up closure registered alongside the restreamer */
4209
4209
  brokerWarmUps = /* @__PURE__ */ new Map();
4210
+ /** brokerId → prestream picture-window opener */
4211
+ brokerPrestreamOpeners = /* @__PURE__ */ new Map();
4210
4212
  /** Generate a random token for a stream path */
4211
4213
  static generateToken() {
4212
4214
  return (0, node_crypto.randomBytes)(16).toString("hex");
@@ -4217,7 +4219,7 @@ var RtspListenServer = class RtspListenServer {
4217
4219
  * If not, generate a new one.
4218
4220
  * Returns the token used.
4219
4221
  */
4220
- registerRestreamer(brokerId, restreamer, warmUp, existingToken) {
4222
+ registerRestreamer(brokerId, restreamer, warmUp, existingToken, prestreamOpener) {
4221
4223
  const oldToken = this.brokerTokens.get(brokerId);
4222
4224
  if (oldToken) {
4223
4225
  this.restreamers.delete(oldToken);
@@ -4228,6 +4230,7 @@ var RtspListenServer = class RtspListenServer {
4228
4230
  this.brokerTokens.set(brokerId, token);
4229
4231
  this.tokenBrokers.set(token, brokerId);
4230
4232
  if (warmUp) this.brokerWarmUps.set(brokerId, warmUp);
4233
+ if (prestreamOpener) this.brokerPrestreamOpeners.set(brokerId, prestreamOpener);
4231
4234
  return token;
4232
4235
  }
4233
4236
  unregisterRestreamer(brokerId) {
@@ -4238,6 +4241,7 @@ var RtspListenServer = class RtspListenServer {
4238
4241
  }
4239
4242
  this.brokerTokens.delete(brokerId);
4240
4243
  this.brokerWarmUps.delete(brokerId);
4244
+ this.brokerPrestreamOpeners.delete(brokerId);
4241
4245
  }
4242
4246
  /** Regenerate a random token for a broker. Returns the new token. */
4243
4247
  regenerateToken(brokerId) {
@@ -4338,8 +4342,10 @@ var RtspListenServer = class RtspListenServer {
4338
4342
  socket.destroy();
4339
4343
  return;
4340
4344
  }
4341
- const warmUp = brokerId ? this.brokerWarmUps.get(brokerId) : void 0;
4342
- const releaseWarmUp = warmUp ? warmUp() : null;
4345
+ const prestreamDial = declaration.intent === "prestream";
4346
+ const warmUp = brokerId && !prestreamDial ? this.brokerWarmUps.get(brokerId) : void 0;
4347
+ const opener = brokerId && prestreamDial ? this.brokerPrestreamOpeners.get(brokerId) : void 0;
4348
+ const releaseWarmUp = warmUp ? warmUp() : opener?.() ?? null;
4343
4349
  let released = false;
4344
4350
  const releaseOnce = () => {
4345
4351
  if (released) return;
@@ -13445,9 +13451,36 @@ var StreamBroker = class StreamBroker {
13445
13451
  */
13446
13452
  hasAnyConsumer() {
13447
13453
  if (this.hasLiveIntent()) return true;
13454
+ if (this.prestreamHolds > 0) return true;
13448
13455
  return this.rtspRestreamer.getPrestreamSessionCount() > 0;
13449
13456
  }
13450
13457
  /**
13458
+ * Somebody is DESCRIBING a prestream and has no session yet.
13459
+ *
13460
+ * The RTSP dispatch reads the SDP before `addSession` runs, so a window that
13461
+ * waits for the session is too late: the consumer is handed an empty SDP and
13462
+ * quits. Measured against the live hub (2026-09-15 14:47): the window opened
13463
+ * on the attach and closed 42 ms later having emitted zero frames, because
13464
+ * ffmpeg had already given up.
13465
+ *
13466
+ * This is the prestream counterpart of `BrokerWarmUp` and its exact opposite
13467
+ * in effect: the warm-up subscribes demand, which DIALS; this opens the
13468
+ * picture, which never does. A battery camera is not touched.
13469
+ */
13470
+ openPrestreamWindow() {
13471
+ this.prestreamHolds += 1;
13472
+ this.checkDemand();
13473
+ let released = false;
13474
+ return () => {
13475
+ if (released) return;
13476
+ released = true;
13477
+ this.prestreamHolds = Math.max(0, this.prestreamHolds - 1);
13478
+ this.checkDemand();
13479
+ };
13480
+ }
13481
+ /** DESCRIBE-window prestream holds: sessions that do not exist yet. */
13482
+ prestreamHolds = 0;
13483
+ /**
13451
13484
  * Consumers that may DIAL. Every term of the former `hasDemand`, with the
13452
13485
  * RTSP session count filtered to exclude prestream dials, plus the wake hold.
13453
13486
  *
@@ -13607,6 +13640,31 @@ var StreamBroker = class StreamBroker {
13607
13640
  }, SUSPEND_GRACE_MS);
13608
13641
  }
13609
13642
  this.prestreamWanted = this.hasAnyConsumer() && !this.hasLiveIntent();
13643
+ this.reconcilePrestreamWindow();
13644
+ }
13645
+ /**
13646
+ * Open or close the prestream window to match {@link prestreamWanted}.
13647
+ *
13648
+ * This is the half that was missing. `prestreamWanted` has been computed
13649
+ * correctly since the feature shipped, but the only thing that ever STARTED
13650
+ * the pump was the lazy branch of `start()` — a DIAL, which needs live
13651
+ * intent, which a prestream session does not have by construction. So the
13652
+ * window opened only when something else had already woken the camera, and
13653
+ * `suspend()` closed it for good.
13654
+ *
13655
+ * Driving it from the consumer edge is what the contract already claimed:
13656
+ * the pump follows CONSUMERS, and it neither dials nor wakes nor holds
13657
+ * anything open (D173). `start()` is idempotent, so being called on every
13658
+ * demand check costs one encode per window and no more.
13659
+ */
13660
+ reconcilePrestreamWindow() {
13661
+ if (!this.prestreamPump) return;
13662
+ if (this.prestreamWanted && !this.prestreamWindowClosed) {
13663
+ const url = this.resolveCurrentSource()?.url ?? this.source?.url;
13664
+ if (url !== void 0) this.prestreamPump.start(url);
13665
+ return;
13666
+ }
13667
+ if (!this.prestreamWanted) this.prestreamPump.stop("no prestream consumer");
13610
13668
  }
13611
13669
  /**
13612
13670
  * Whether a prestream picture should be flowing right now: somebody is
@@ -13642,6 +13700,7 @@ var StreamBroker = class StreamBroker {
13642
13700
  }
13643
13701
  this.prestreamPump?.stop("broker suspended");
13644
13702
  this.prestreamWindowClosed = false;
13703
+ this.reconcilePrestreamWindow();
13645
13704
  this.preBuffer.clear();
13646
13705
  this.decoderSeedBuffer.clear();
13647
13706
  this.clipBuffer?.clear();
@@ -19308,7 +19367,8 @@ var StreamBrokerManager = class StreamBrokerManager {
19308
19367
  if (!sourceBroker) continue;
19309
19368
  const existingToken = this.persistedTokens.get(profileBrokerId);
19310
19369
  const warmUp = () => sourceBroker.onEncodedData(() => {});
19311
- this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken);
19370
+ const openPrestreamWindow = () => sourceBroker.openPrestreamWindow();
19371
+ this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
19312
19372
  this.profileRestreamerBindings.set(profileBrokerId, sourceBrokerId);
19313
19373
  if (!existingToken) this.rtspProvider.persistTokens();
19314
19374
  }
@@ -20967,7 +21027,8 @@ var StreamBrokerManager = class StreamBrokerManager {
20967
21027
  this.applyClipRetention(deviceId);
20968
21028
  const existingToken = this.persistedTokens.get(brokerId);
20969
21029
  const warmUp = () => broker.onEncodedData(() => {});
20970
- this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken);
21030
+ const openPrestreamWindow = () => broker.openPrestreamWindow();
21031
+ this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
20971
21032
  if (!existingToken) this.rtspProvider.persistTokens();
20972
21033
  await this.fanOutRegistration(deviceId, brokerId, cam, broker);
20973
21034
  this.syncProfileRestreamers(deviceId);
@@ -1,10 +1,10 @@
1
1
  import { r as __toESM } from "../chunk-DnnnRqeS.mjs";
2
- import { A as RingBuffer, B as declareLogChannel, Bt as buildFfmpegArgs$1, E as RATE_CONTROL_TIGHT, Et as resolveEgressDecodeHwAccel, Gt as CAM_PROFILE_ORDER, H as deriveBatteryPresence, Ht as isSoftwareDecode, I as batteryCapability, It as webrtcSessionCapability, K as egressTranscodeSharingKey, Kt as DeviceFeature, L as cameraStreamsCapability, Lt as errMsg, M as addonWidgetsSourceCapability, Nt as streamBrokerCapability, Qt as expandAudioChunkToF32le, R as createHwAccelCache, Rt as AUDIO_PRESETS, T as RATE_CONTROL_RELAXED, Ut as BOOT_RECOVERY_BACKOFF_MS, Vt as invocationFromEncodeProfile, Wt as BaseAddon, Yt as createEvent, _n as record, an as parseProfileBrokerId, at as logChannelsCapability, bn as EventCategory, cn as _enum, dn as discriminatedUnion, en as isEvent, h as EncodeProfileSchema, hn as object, i as BatteryStatusSchema, it as loadContributionCapability, ln as array, mn as number, nn as makeSourceBrokerId, pn as literal, q as egressTransportFromRequest, qt as DeviceType, rn as nodePin, st as maskUrlCredentials, tn as makeProfileBrokerId, un as boolean, v as G711_SCALE_CORRECTION_DB, vn as string, xt as rankStreams, yn as union, z as createLogChannelsProvider } from "../dist-CoZwNgzu.mjs";
3
- import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-Uh59Eo57.mjs";
2
+ import { A as RingBuffer, B as declareLogChannel, Bt as buildFfmpegArgs$1, E as RATE_CONTROL_TIGHT, Et as resolveEgressDecodeHwAccel, Gt as CAM_PROFILE_ORDER, H as deriveBatteryPresence, Ht as isSoftwareDecode, I as batteryCapability, It as webrtcSessionCapability, K as egressTranscodeSharingKey, Kt as DeviceFeature, L as cameraStreamsCapability, Lt as errMsg, M as addonWidgetsSourceCapability, Nt as streamBrokerCapability, Qt as expandAudioChunkToF32le, R as createHwAccelCache, Rt as AUDIO_PRESETS, T as RATE_CONTROL_RELAXED, Ut as BOOT_RECOVERY_BACKOFF_MS, Vt as invocationFromEncodeProfile, Wt as BaseAddon, Yt as createEvent, _n as record, an as parseProfileBrokerId, at as logChannelsCapability, bn as EventCategory, cn as _enum, dn as discriminatedUnion, en as isEvent, h as EncodeProfileSchema, hn as object, i as BatteryStatusSchema, it as loadContributionCapability, ln as array, mn as number, nn as makeSourceBrokerId, pn as literal, q as egressTransportFromRequest, qt as DeviceType, rn as nodePin, st as maskUrlCredentials, tn as makeProfileBrokerId, un as boolean, v as G711_SCALE_CORRECTION_DB, vn as string, xt as rankStreams, yn as union, z as createLogChannelsProvider } from "../dist-XAp9FVoN.mjs";
3
+ import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-umwnvSsV.mjs";
4
4
  import { c as PREBUFFER_MAX_MS, d as resolvePrebuffer, i as parseRestreamPath, l as capPreRollRequestMs, n as NO_DECLARATION, u as prebufferRetentionMs } from "../restream-intent-MeJhGCK9.mjs";
5
5
  import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
6
6
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
7
- import { i as require_mp4box_all, n as demuxSegmentPreferJs, r as runFfmpegOnce, t as demuxSegmentFullJs } from "../segment-demux-js-7DukFIT6.mjs";
7
+ import { i as require_mp4box_all, n as demuxSegmentPreferJs, r as runFfmpegOnce, t as demuxSegmentFullJs } from "../segment-demux-js-BodSgaxj.mjs";
8
8
  import { createRequire } from "node:module";
9
9
  import * as os from "node:os";
10
10
  import { networkInterfaces, tmpdir } from "node:os";
@@ -4203,6 +4203,8 @@ var RtspListenServer = class RtspListenServer {
4203
4203
  brokerEnabled = /* @__PURE__ */ new Map();
4204
4204
  /** brokerId → warm-up closure registered alongside the restreamer */
4205
4205
  brokerWarmUps = /* @__PURE__ */ new Map();
4206
+ /** brokerId → prestream picture-window opener */
4207
+ brokerPrestreamOpeners = /* @__PURE__ */ new Map();
4206
4208
  /** Generate a random token for a stream path */
4207
4209
  static generateToken() {
4208
4210
  return randomBytes(16).toString("hex");
@@ -4213,7 +4215,7 @@ var RtspListenServer = class RtspListenServer {
4213
4215
  * If not, generate a new one.
4214
4216
  * Returns the token used.
4215
4217
  */
4216
- registerRestreamer(brokerId, restreamer, warmUp, existingToken) {
4218
+ registerRestreamer(brokerId, restreamer, warmUp, existingToken, prestreamOpener) {
4217
4219
  const oldToken = this.brokerTokens.get(brokerId);
4218
4220
  if (oldToken) {
4219
4221
  this.restreamers.delete(oldToken);
@@ -4224,6 +4226,7 @@ var RtspListenServer = class RtspListenServer {
4224
4226
  this.brokerTokens.set(brokerId, token);
4225
4227
  this.tokenBrokers.set(token, brokerId);
4226
4228
  if (warmUp) this.brokerWarmUps.set(brokerId, warmUp);
4229
+ if (prestreamOpener) this.brokerPrestreamOpeners.set(brokerId, prestreamOpener);
4227
4230
  return token;
4228
4231
  }
4229
4232
  unregisterRestreamer(brokerId) {
@@ -4234,6 +4237,7 @@ var RtspListenServer = class RtspListenServer {
4234
4237
  }
4235
4238
  this.brokerTokens.delete(brokerId);
4236
4239
  this.brokerWarmUps.delete(brokerId);
4240
+ this.brokerPrestreamOpeners.delete(brokerId);
4237
4241
  }
4238
4242
  /** Regenerate a random token for a broker. Returns the new token. */
4239
4243
  regenerateToken(brokerId) {
@@ -4334,8 +4338,10 @@ var RtspListenServer = class RtspListenServer {
4334
4338
  socket.destroy();
4335
4339
  return;
4336
4340
  }
4337
- const warmUp = brokerId ? this.brokerWarmUps.get(brokerId) : void 0;
4338
- const releaseWarmUp = warmUp ? warmUp() : null;
4341
+ const prestreamDial = declaration.intent === "prestream";
4342
+ const warmUp = brokerId && !prestreamDial ? this.brokerWarmUps.get(brokerId) : void 0;
4343
+ const opener = brokerId && prestreamDial ? this.brokerPrestreamOpeners.get(brokerId) : void 0;
4344
+ const releaseWarmUp = warmUp ? warmUp() : opener?.() ?? null;
4339
4345
  let released = false;
4340
4346
  const releaseOnce = () => {
4341
4347
  if (released) return;
@@ -13441,9 +13447,36 @@ var StreamBroker = class StreamBroker {
13441
13447
  */
13442
13448
  hasAnyConsumer() {
13443
13449
  if (this.hasLiveIntent()) return true;
13450
+ if (this.prestreamHolds > 0) return true;
13444
13451
  return this.rtspRestreamer.getPrestreamSessionCount() > 0;
13445
13452
  }
13446
13453
  /**
13454
+ * Somebody is DESCRIBING a prestream and has no session yet.
13455
+ *
13456
+ * The RTSP dispatch reads the SDP before `addSession` runs, so a window that
13457
+ * waits for the session is too late: the consumer is handed an empty SDP and
13458
+ * quits. Measured against the live hub (2026-09-15 14:47): the window opened
13459
+ * on the attach and closed 42 ms later having emitted zero frames, because
13460
+ * ffmpeg had already given up.
13461
+ *
13462
+ * This is the prestream counterpart of `BrokerWarmUp` and its exact opposite
13463
+ * in effect: the warm-up subscribes demand, which DIALS; this opens the
13464
+ * picture, which never does. A battery camera is not touched.
13465
+ */
13466
+ openPrestreamWindow() {
13467
+ this.prestreamHolds += 1;
13468
+ this.checkDemand();
13469
+ let released = false;
13470
+ return () => {
13471
+ if (released) return;
13472
+ released = true;
13473
+ this.prestreamHolds = Math.max(0, this.prestreamHolds - 1);
13474
+ this.checkDemand();
13475
+ };
13476
+ }
13477
+ /** DESCRIBE-window prestream holds: sessions that do not exist yet. */
13478
+ prestreamHolds = 0;
13479
+ /**
13447
13480
  * Consumers that may DIAL. Every term of the former `hasDemand`, with the
13448
13481
  * RTSP session count filtered to exclude prestream dials, plus the wake hold.
13449
13482
  *
@@ -13603,6 +13636,31 @@ var StreamBroker = class StreamBroker {
13603
13636
  }, SUSPEND_GRACE_MS);
13604
13637
  }
13605
13638
  this.prestreamWanted = this.hasAnyConsumer() && !this.hasLiveIntent();
13639
+ this.reconcilePrestreamWindow();
13640
+ }
13641
+ /**
13642
+ * Open or close the prestream window to match {@link prestreamWanted}.
13643
+ *
13644
+ * This is the half that was missing. `prestreamWanted` has been computed
13645
+ * correctly since the feature shipped, but the only thing that ever STARTED
13646
+ * the pump was the lazy branch of `start()` — a DIAL, which needs live
13647
+ * intent, which a prestream session does not have by construction. So the
13648
+ * window opened only when something else had already woken the camera, and
13649
+ * `suspend()` closed it for good.
13650
+ *
13651
+ * Driving it from the consumer edge is what the contract already claimed:
13652
+ * the pump follows CONSUMERS, and it neither dials nor wakes nor holds
13653
+ * anything open (D173). `start()` is idempotent, so being called on every
13654
+ * demand check costs one encode per window and no more.
13655
+ */
13656
+ reconcilePrestreamWindow() {
13657
+ if (!this.prestreamPump) return;
13658
+ if (this.prestreamWanted && !this.prestreamWindowClosed) {
13659
+ const url = this.resolveCurrentSource()?.url ?? this.source?.url;
13660
+ if (url !== void 0) this.prestreamPump.start(url);
13661
+ return;
13662
+ }
13663
+ if (!this.prestreamWanted) this.prestreamPump.stop("no prestream consumer");
13606
13664
  }
13607
13665
  /**
13608
13666
  * Whether a prestream picture should be flowing right now: somebody is
@@ -13638,6 +13696,7 @@ var StreamBroker = class StreamBroker {
13638
13696
  }
13639
13697
  this.prestreamPump?.stop("broker suspended");
13640
13698
  this.prestreamWindowClosed = false;
13699
+ this.reconcilePrestreamWindow();
13641
13700
  this.preBuffer.clear();
13642
13701
  this.decoderSeedBuffer.clear();
13643
13702
  this.clipBuffer?.clear();
@@ -19304,7 +19363,8 @@ var StreamBrokerManager = class StreamBrokerManager {
19304
19363
  if (!sourceBroker) continue;
19305
19364
  const existingToken = this.persistedTokens.get(profileBrokerId);
19306
19365
  const warmUp = () => sourceBroker.onEncodedData(() => {});
19307
- this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken);
19366
+ const openPrestreamWindow = () => sourceBroker.openPrestreamWindow();
19367
+ this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
19308
19368
  this.profileRestreamerBindings.set(profileBrokerId, sourceBrokerId);
19309
19369
  if (!existingToken) this.rtspProvider.persistTokens();
19310
19370
  }
@@ -20963,7 +21023,8 @@ var StreamBrokerManager = class StreamBrokerManager {
20963
21023
  this.applyClipRetention(deviceId);
20964
21024
  const existingToken = this.persistedTokens.get(brokerId);
20965
21025
  const warmUp = () => broker.onEncodedData(() => {});
20966
- this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken);
21026
+ const openPrestreamWindow = () => broker.openPrestreamWindow();
21027
+ this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
20967
21028
  if (!existingToken) this.rtspProvider.persistTokens();
20968
21029
  await this.fanOutRegistration(deviceId, brokerId, cam, broker);
20969
21030
  this.syncProfileRestreamers(deviceId);
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B20FCUk4.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2-mCqUf.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.245",
3
+ "version": "1.2.246",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",