@camstack/addon-pipeline 1.2.75 → 1.2.77

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 (31) hide show
  1. package/dist/{addon-utils-a_36HH_N.js → addon-utils-bZcwLaVW.js} +1 -1
  2. package/dist/audio-analyzer/index.js +2 -2
  3. package/dist/audio-analyzer/index.mjs +1 -1
  4. package/dist/detection-pipeline/index.js +4 -4
  5. package/dist/detection-pipeline/index.mjs +2 -2
  6. package/dist/{dist-6ZJCV0jY.js → dist-DQZzL0Ri.js} +452 -42
  7. package/dist/{dist-T_8jMw7d.mjs → dist-HhcoV-Ky.mjs} +452 -42
  8. package/dist/{event-loop-stall-monitor-Cjof8vA1.js → event-loop-stall-monitor-CBOo1oh_.js} +1 -1
  9. package/dist/{event-loop-stall-monitor-XeGmL4xr.mjs → event-loop-stall-monitor-CqBEWGSZ.mjs} +1 -1
  10. package/dist/{lazy-sharp-DvwPafeP.js → lazy-sharp-DIC1rpea.js} +1 -1
  11. package/dist/motion-wasm/index.js +2 -2
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +278 -15
  14. package/dist/pipeline-runner/index.mjs +277 -14
  15. package/dist/recorder/index.js +368 -18
  16. package/dist/recorder/index.mjs +367 -17
  17. package/dist/session-decode/decode-worker-child.js +2 -2
  18. package/dist/session-decode/decode-worker-child.mjs +1 -1
  19. package/dist/stream-broker/_stub.js +2 -2
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-H2OTmgR6.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-De_xzvjU.mjs} +3 -3
  21. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DHJSxHH1.mjs +26 -0
  22. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dg08SxUW.mjs +26 -0
  23. package/dist/stream-broker/{hostInit-4XDN_Nt0.mjs → hostInit-CDS5frmk.mjs} +3 -3
  24. package/dist/stream-broker/index.js +306 -71
  25. package/dist/stream-broker/index.mjs +306 -71
  26. package/dist/stream-broker/remoteEntry.js +1 -1
  27. package/dist/{worker-protocol-SqED7Y9i.mjs → worker-protocol-BNlLGLBV.mjs} +1 -1
  28. package/dist/{worker-protocol-B0cbTXyw.js → worker-protocol-DZVGVAPU.js} +1 -1
  29. package/package.json +1 -1
  30. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-RYTOS6B-.mjs +0 -26
  31. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DfsLsn2e.mjs +0 -26
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-6ZJCV0jY.js");
1
+ const require_dist = require("./dist-DQZzL0Ri.js");
2
2
  let node_perf_hooks = require("node:perf_hooks");
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -1,4 +1,4 @@
1
- import { a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS, z as hfModelUrl } from "./dist-T_8jMw7d.mjs";
1
+ import { a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS, z as hfModelUrl } from "./dist-HhcoV-Ky.mjs";
2
2
  import { PerformanceObserver } from "node:perf_hooks";
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-6ZJCV0jY.js");
1
+ const require_dist = require("./dist-DQZzL0Ri.js");
2
2
  let node_url = require("node:url");
3
3
  let node_module = require("node:module");
4
4
  let node_path = require("node:path");
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-6ZJCV0jY.js");
6
- const require_lazy_sharp = require("../lazy-sharp-DvwPafeP.js");
5
+ const require_dist = require("../dist-DQZzL0Ri.js");
6
+ const require_lazy_sharp = require("../lazy-sharp-DIC1rpea.js");
7
7
  let node_path = require("node:path");
8
8
  let node_fs = require("node:fs");
9
9
  //#region src/motion-wasm/wasm-motion-detector.ts
@@ -1,4 +1,4 @@
1
- import { H as motionDetectionCapability, R as evaluateZoneRules, ft as hydrateSchema, st as BaseAddon, ut as DeviceType } from "../dist-T_8jMw7d.mjs";
1
+ import { H as motionDetectionCapability, R as evaluateZoneRules, ft as hydrateSchema, st as BaseAddon, ut as DeviceType } from "../dist-HhcoV-Ky.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
3
3
  import { join } from "node:path";
4
4
  import { readFileSync } from "node:fs";
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-6ZJCV0jY.js");
6
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-Cjof8vA1.js");
7
- const require_worker_protocol = require("../worker-protocol-B0cbTXyw.js");
5
+ const require_dist = require("../dist-DQZzL0Ri.js");
6
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-CBOo1oh_.js");
7
+ const require_worker_protocol = require("../worker-protocol-DZVGVAPU.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
- const require_lazy_sharp = require("../lazy-sharp-DvwPafeP.js");
9
+ const require_lazy_sharp = require("../lazy-sharp-DIC1rpea.js");
10
10
  const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
11
11
  let sharp = require("sharp");
12
12
  sharp = require_dist.__toESM(sharp);
@@ -2366,13 +2366,36 @@ function summarizeRunnerHardware(hardware, cpuCores = node_os.cpus().length) {
2366
2366
  * just picked one up — which coincide with motion in the package zone
2367
2367
  * SETTLING (transitioning from moving to still). This governor fires:
2368
2368
  *
2369
- * - on a **settle edge**: motion was observed, then has been absent for
2370
- * `settleMs` (kills flicker between motion frames and the "bag briefly
2371
- * set down then snatched back" case), and
2369
+ * - on a **settle edge**: motion NEAR THE PACKAGE ZONE was observed, then has
2370
+ * been absent for `settleMs` (kills flicker between motion frames and the
2371
+ * "bag briefly set down then snatched back" case). The settle edge opens a
2372
+ * **BURST**, and
2372
2373
  * - on a **low-cadence safety poll** (`pollIntervalMs`, ~30–60s): a
2373
2374
  * backstop so a delivery whose motion was never observed (a session-gap
2374
2375
  * camera that only decodes intermittently) is still eventually caught.
2375
2376
  *
2377
+ * ## The burst, and why the poll alone was not enough
2378
+ *
2379
+ * A parcel appears at the END of a motion episode — the courier walks up, puts
2380
+ * it down, walks away. That is a settle edge, and it is the moment we know
2381
+ * something MIGHT have been left behind. So the settle edge does not fire one
2382
+ * inference, it opens a burst: `burstCount` runs `burstIntervalMs` apart.
2383
+ *
2384
+ * The poll alone made the downstream promotion criterion unreachable in
2385
+ * PRACTICE and the notification useless in LATENCY. One observation per 45s
2386
+ * (and on a session-dispatch camera the measured gap was ~2 minutes) meant a
2387
+ * parcel needed minutes to accumulate enough evidence to be called parked, if
2388
+ * it ever did. The burst supplies the whole evidence chain inside ~20s, so a
2389
+ * delivery is reported while the courier is still in the street.
2390
+ *
2391
+ * The poll stays, unchanged in role: it is the SAFETY NET for the parcel whose
2392
+ * motion was never observed at all — a camera that just came up, an occlusion,
2393
+ * a missed analyzer frame. It is not the primary path any more.
2394
+ *
2395
+ * The two feed the SAME downstream round counter (`PackageStillnessTracker`),
2396
+ * which is what lets one promotion criterion serve both cadences. That is the
2397
+ * relation `scripts/check-package-promotion-satisfiable.ts` enforces.
2398
+ *
2376
2399
  * Pure logic with an injected clock (every method takes `now`), mirroring
2377
2400
  * `FrameGovernor` / `recomputeGovernor(now)` — no timers, no `Date.now()`
2378
2401
  * inside, fully unit-testable. Per-device state lives in a Map; the caller
@@ -2391,14 +2414,37 @@ function summarizeRunnerHardware(hardware, cpuCores = node_os.cpus().length) {
2391
2414
  */
2392
2415
  /** The step id (`PipelineStepInput.addonId`) the governor gates. */
2393
2416
  var PACKAGE_DETECTION_STEP_ID = "package-detection";
2417
+ /**
2418
+ * Burst: 5 runs, 5s apart — 0s, 5s, 10s, 15s, 20s after the settle edge.
2419
+ *
2420
+ * Sized against the two downstream numbers it has to satisfy, both pinned by
2421
+ * `scripts/check-package-promotion-satisfiable.ts`:
2422
+ *
2423
+ * - `PACKAGE_STILLNESS_ROUND_GAP_MS` (5 000) — the spacing must be ≥ this or
2424
+ * the runs collapse into one round;
2425
+ * - `packageDropDwellSec` (15s default) — the burst SPAN
2426
+ * (`(burstCount - 1) × burstIntervalMs` = 20s) must clear the dwell, or the
2427
+ * burst ends before the parcel has been still long enough to count and the
2428
+ * delivery falls back to the poll. 20s over 15s is one whole run of margin,
2429
+ * which is what absorbs a dropped analyzer frame.
2430
+ *
2431
+ * Cost: 5 extra inferences per motion episode, on cameras with an active
2432
+ * package zone only (the caller consults this governor only while the
2433
+ * `package-detection` step is in the camera's tree, and the resolver appends
2434
+ * that step only for an enabled `package`-stage zone rule).
2435
+ */
2394
2436
  var DEFAULT_MOTION_SETTLE_CONFIG = {
2395
2437
  settleMs: 3e3,
2396
- pollIntervalMs: 45e3
2438
+ pollIntervalMs: 45e3,
2439
+ burstCount: 5,
2440
+ burstIntervalMs: 5e3
2397
2441
  };
2398
2442
  var INITIAL_STATE = {
2399
2443
  lastMotionAt: null,
2400
2444
  armed: false,
2401
- lastRunAt: Number.NEGATIVE_INFINITY
2445
+ lastRunAt: Number.NEGATIVE_INFINITY,
2446
+ burstRemaining: 0,
2447
+ burstNextAt: 0
2402
2448
  };
2403
2449
  var NO_RUN = {
2404
2450
  run: false,
@@ -2424,18 +2470,35 @@ var MotionSettleGovernor = class {
2424
2470
  this.state.set(sample.deviceId, {
2425
2471
  lastMotionAt,
2426
2472
  armed: false,
2427
- lastRunAt: sample.now
2473
+ lastRunAt: sample.now,
2474
+ burstRemaining: Math.max(0, this.config.burstCount - 1),
2475
+ burstNextAt: sample.now + this.config.burstIntervalMs
2428
2476
  });
2429
2477
  return {
2430
2478
  run: true,
2431
2479
  reason: "settle"
2432
2480
  };
2433
2481
  }
2482
+ if (prev.burstRemaining > 0 && sample.now >= prev.burstNextAt) {
2483
+ this.state.set(sample.deviceId, {
2484
+ lastMotionAt,
2485
+ armed,
2486
+ lastRunAt: sample.now,
2487
+ burstRemaining: prev.burstRemaining - 1,
2488
+ burstNextAt: sample.now + this.config.burstIntervalMs
2489
+ });
2490
+ return {
2491
+ run: true,
2492
+ reason: "burst"
2493
+ };
2494
+ }
2434
2495
  if (pollReady) {
2435
2496
  this.state.set(sample.deviceId, {
2436
2497
  lastMotionAt,
2437
2498
  armed,
2438
- lastRunAt: sample.now
2499
+ lastRunAt: sample.now,
2500
+ burstRemaining: prev.burstRemaining,
2501
+ burstNextAt: prev.burstNextAt
2439
2502
  });
2440
2503
  return {
2441
2504
  run: true,
@@ -2445,7 +2508,9 @@ var MotionSettleGovernor = class {
2445
2508
  this.state.set(sample.deviceId, {
2446
2509
  lastMotionAt,
2447
2510
  armed,
2448
- lastRunAt: prev.lastRunAt
2511
+ lastRunAt: prev.lastRunAt,
2512
+ burstRemaining: prev.burstRemaining,
2513
+ burstNextAt: prev.burstNextAt
2449
2514
  });
2450
2515
  return NO_RUN;
2451
2516
  }
@@ -2459,6 +2524,90 @@ var MotionSettleGovernor = class {
2459
2524
  }
2460
2525
  };
2461
2526
  //#endregion
2527
+ //#region src/pipeline-runner/package-zone-proximity.ts
2528
+ /**
2529
+ * How much the zone-union box grows on EVERY side, as a fraction of its own
2530
+ * width / height.
2531
+ *
2532
+ * 0.5 means a doormat 271px wide on a 640px frame is approached through a
2533
+ * ~135px apron on each side — about one adult's width at that distance, which
2534
+ * is the thing being detected: a person standing next to the mat, not on it.
2535
+ * Larger and a passer-by on the pavement opens a burst; smaller and the
2536
+ * courier who never steps on the mat does not.
2537
+ */
2538
+ var PACKAGE_ZONE_DILATION = .5;
2539
+ /** Grow a box by {@link PACKAGE_ZONE_DILATION}, clamped to the frame. */
2540
+ function dilateZoneBox(box, dilation = PACKAGE_ZONE_DILATION) {
2541
+ const dx = box.w * dilation;
2542
+ const dy = box.h * dilation;
2543
+ const x = Math.max(0, box.x - dx);
2544
+ const y = Math.max(0, box.y - dy);
2545
+ return {
2546
+ x,
2547
+ y,
2548
+ w: Math.min(1, box.x + box.w + dx) - x,
2549
+ h: Math.min(1, box.y + box.h + dy) - y
2550
+ };
2551
+ }
2552
+ function intersects(a, b) {
2553
+ return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
2554
+ }
2555
+ /**
2556
+ * Does any motion region reach the dilated package zone?
2557
+ *
2558
+ * **Fail-open on unknown geometry.** `zoneUnion === undefined` means the caller
2559
+ * has not (or could not) resolve the zones — a cold cache, a failed cap read.
2560
+ * It answers "yes, if there is any motion at all", which degrades to exactly
2561
+ * the whole-frame behaviour this replaced. The alternative — answering "no" —
2562
+ * would let one failed read silence package detection on a camera for as long
2563
+ * as the read kept failing, and be indistinguishable from a quiet doorstep.
2564
+ * That is the shape of D49, and it has cost this repo real deliveries.
2565
+ */
2566
+ function motionNearPackageZone(input) {
2567
+ if (input.regions.length === 0) return false;
2568
+ if (input.zoneUnion === void 0) return true;
2569
+ if (input.frameWidth <= 0 || input.frameHeight <= 0) return true;
2570
+ const target = dilateZoneBox(input.zoneUnion);
2571
+ for (const region of input.regions) if (intersects({
2572
+ x: region.bbox.x / input.frameWidth,
2573
+ y: region.bbox.y / input.frameHeight,
2574
+ w: region.bbox.w / input.frameWidth,
2575
+ h: region.bbox.h / input.frameHeight
2576
+ }, target)) return true;
2577
+ return false;
2578
+ }
2579
+ /**
2580
+ * The bounding box of a set of normalised polygons — the package zone union.
2581
+ * Returns `undefined` when there is no usable geometry, which the caller must
2582
+ * pass through as "unknown" rather than as an empty zone.
2583
+ */
2584
+ function packageZoneUnion(polygons) {
2585
+ let minX = Number.POSITIVE_INFINITY;
2586
+ let minY = Number.POSITIVE_INFINITY;
2587
+ let maxX = Number.NEGATIVE_INFINITY;
2588
+ let maxY = Number.NEGATIVE_INFINITY;
2589
+ let sawPoint = false;
2590
+ for (const polygon of polygons) for (const point of polygon) {
2591
+ sawPoint = true;
2592
+ if (point.x < minX) minX = point.x;
2593
+ if (point.y < minY) minY = point.y;
2594
+ if (point.x > maxX) maxX = point.x;
2595
+ if (point.y > maxY) maxY = point.y;
2596
+ }
2597
+ if (!sawPoint) return void 0;
2598
+ const x = Math.max(0, Math.min(1, minX));
2599
+ const y = Math.max(0, Math.min(1, minY));
2600
+ const w = Math.max(0, Math.min(1, maxX)) - x;
2601
+ const h = Math.max(0, Math.min(1, maxY)) - y;
2602
+ if (w <= 0 || h <= 0) return void 0;
2603
+ return {
2604
+ x,
2605
+ y,
2606
+ w,
2607
+ h
2608
+ };
2609
+ }
2610
+ //#endregion
2462
2611
  //#region src/pipeline-runner/native-crop-tier.ts
2463
2612
  /**
2464
2613
  * Pure tier decision for `getNativeCrop`. A native HIT always resolves to the
@@ -3836,6 +3985,13 @@ function isUsable(bbox) {
3836
3985
  //#endregion
3837
3986
  //#region src/pipeline-runner/index.ts
3838
3987
  /**
3988
+ * How often a device's package zone-union geometry is re-read. An operator
3989
+ * redrawing a package zone takes effect within one interval; the read is a
3990
+ * cross-addon cap call reached from the per-frame package gate, so it must be
3991
+ * throttled to something far slower than the frame rate.
3992
+ */
3993
+ var PACKAGE_ZONE_UNION_TTL_MS = 6e4;
3994
+ /**
3839
3995
  * Bound on the frames whose cutter is bound but whose result has not come back.
3840
3996
  * A few per camera in flight; the ceiling only catches results that never
3841
3997
  * arrive (shed, inference timeout, detach mid-flight).
@@ -4206,6 +4362,36 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4206
4362
  */
4207
4363
  packageGovernor = new MotionSettleGovernor();
4208
4364
  /**
4365
+ * Frames on which the governor DROPPED `package-detection` since that
4366
+ * device last ran it. The drop is the single most consequential silent
4367
+ * branch in the package chain — it decides the cadence that the whole
4368
+ * downstream promotion criterion has to be satisfiable at — and it logged
4369
+ * nothing at all, so "the parcel was never detected" and "the step never
4370
+ * ran" were indistinguishable in the logs. Counted here and reported on the
4371
+ * transition (first skip) and on every run, never per frame.
4372
+ */
4373
+ packageGateSkips = /* @__PURE__ */ new Map();
4374
+ /**
4375
+ * Per-device package zone-union box (normalised), the target the motion
4376
+ * proximity test measures against.
4377
+ *
4378
+ * A device MISSING from this map is "unknown", not "no zones" — and unknown
4379
+ * FAILS OPEN in `motionNearPackageZone`, degrading to the whole-frame motion
4380
+ * signal. Resolved off the frame path (on attach, and refreshed on the
4381
+ * package-step gate's own cadence), never inside `runMotionAnalysis`.
4382
+ */
4383
+ packageZoneUnionByDevice = /* @__PURE__ */ new Map();
4384
+ /** In-flight/last zone-union resolution time per device, so the refresh does
4385
+ * not stampede the zone caps from the motion path. */
4386
+ packageZoneUnionAt = /* @__PURE__ */ new Map();
4387
+ /**
4388
+ * Whether the last analyzer frame saw motion NEAR the device's package
4389
+ * zones — the boolean the settle-burst governor consumes. Distinct from
4390
+ * `lastAnalyzerDetected` (whole-frame), which is what the phase machine
4391
+ * uses and which must not change meaning.
4392
+ */
4393
+ lastMotionNearPackageZone = /* @__PURE__ */ new Map();
4394
+ /**
4209
4395
  * Last positive motion timestamp per device — preserved across the
4210
4396
  * OFF transition so the motion runtime-state slice keeps a stable
4211
4397
  * `lastDetectedAt` after the cooldown closes the phase. Cleared on
@@ -4406,6 +4592,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4406
4592
  }
4407
4593
  this.lastAnalyzerDetected.clear();
4408
4594
  this.packageGovernor.reset();
4595
+ this.packageGateSkips.clear();
4596
+ this.packageZoneUnionByDevice.clear();
4597
+ this.packageZoneUnionAt.clear();
4598
+ this.lastMotionNearPackageZone.clear();
4409
4599
  for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) this.clearOnboardAnalyzer(deviceId);
4410
4600
  if (this.runner) {
4411
4601
  this.runner.stop();
@@ -5668,6 +5858,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
5668
5858
  this.lastMotionFrameAt.delete(deviceId);
5669
5859
  this.lastDecodeUnexpectedEndAt.delete(deviceId);
5670
5860
  this.packageGovernor.forget(deviceId);
5861
+ this.packageGateSkips.delete(deviceId);
5862
+ this.packageZoneUnionByDevice.delete(deviceId);
5863
+ this.packageZoneUnionAt.delete(deviceId);
5864
+ this.lastMotionNearPackageZone.delete(deviceId);
5671
5865
  const warnPrefix = `${deviceId}:`;
5672
5866
  for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
5673
5867
  this.attached.delete(deviceId);
@@ -6244,13 +6438,31 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6244
6438
  if (steps.length === 0) return null;
6245
6439
  let dispatchSteps = steps;
6246
6440
  if (steps.some((s) => s.addonId === "package-detection" && s.enabled !== false)) {
6247
- if (!this.packageGovernor.evaluate({
6441
+ this.refreshPackageZoneUnion(deviceId);
6442
+ const motionInZone = this.lastMotionNearPackageZone.get(deviceId) ?? this.lastAnalyzerDetected.get(deviceId) ?? false;
6443
+ const decision = this.packageGovernor.evaluate({
6248
6444
  deviceId,
6249
- motionInZone: this.lastAnalyzerDetected.get(deviceId) ?? false,
6445
+ motionInZone,
6250
6446
  now: Date.now()
6251
- }).run) {
6447
+ });
6448
+ const skipped = this.packageGateSkips.get(deviceId) ?? 0;
6449
+ if (!decision.run) {
6252
6450
  dispatchSteps = steps.filter((s) => s.addonId !== PACKAGE_DETECTION_STEP_ID);
6451
+ if (skipped === 0) log.info("package-detection gated off by the motion-settle governor", {
6452
+ tags: { deviceId },
6453
+ meta: { motionInZone: this.lastAnalyzerDetected.get(deviceId) ?? false }
6454
+ });
6455
+ this.packageGateSkips.set(deviceId, skipped + 1);
6253
6456
  if (dispatchSteps.length === 0) return null;
6457
+ } else {
6458
+ this.packageGateSkips.set(deviceId, 0);
6459
+ log.info("package-detection dispatched by the motion-settle governor", {
6460
+ tags: { deviceId },
6461
+ meta: {
6462
+ reason: decision.reason,
6463
+ framesSkippedSinceLastRun: skipped
6464
+ }
6465
+ });
6254
6466
  }
6255
6467
  }
6256
6468
  let image;
@@ -6299,6 +6511,51 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6299
6511
  }
6300
6512
  return false;
6301
6513
  }
6514
+ /**
6515
+ * Refresh a device's package zone-union box, off the caller's path.
6516
+ *
6517
+ * Cross-addon reads go through `ctx.api` (the only cross-addon layer), and
6518
+ * they are RATE-LIMITED per device: this is reached from the per-frame
6519
+ * package gate, and two cap calls per frame would be a self-inflicted load
6520
+ * problem on the exact cameras the feature exists for.
6521
+ *
6522
+ * A FAILED read leaves the mirror untouched (D49): the last-known-good
6523
+ * geometry keeps being used, and a device that has never been read stays
6524
+ * absent from the map, which `motionNearPackageZone` treats as unknown and
6525
+ * fails OPEN on. Nothing here can make package detection quieter.
6526
+ */
6527
+ refreshPackageZoneUnion(deviceId) {
6528
+ const now = Date.now();
6529
+ if (now - (this.packageZoneUnionAt.get(deviceId) ?? 0) < PACKAGE_ZONE_UNION_TTL_MS) return;
6530
+ this.packageZoneUnionAt.set(deviceId, now);
6531
+ const api = this.ctx.api;
6532
+ if (!api) return;
6533
+ (async () => {
6534
+ try {
6535
+ const [zones, rules] = await Promise.all([api.zones.listZones.query({ deviceId }), api.zoneRules.listRules.query({
6536
+ deviceId,
6537
+ stage: "package"
6538
+ })]);
6539
+ const zoneIds = new Set(rules.filter((r) => r.enabled !== false).flatMap((r) => r.zoneIds));
6540
+ const polygons = zones.filter((z) => zoneIds.has(z.id)).map((z) => z.polygon);
6541
+ const union = packageZoneUnion(polygons);
6542
+ const previous = this.packageZoneUnionByDevice.get(deviceId);
6543
+ this.packageZoneUnionByDevice.set(deviceId, union);
6544
+ if (previous === void 0 && union !== void 0) this.ctx.logger.info("package zone geometry resolved — motion proximity armed", {
6545
+ tags: { deviceId },
6546
+ meta: {
6547
+ zoneUnion: union,
6548
+ zones: polygons.length
6549
+ }
6550
+ });
6551
+ } catch (err) {
6552
+ this.ctx.logger.warn("package zone geometry read failed — motion proximity fails open", {
6553
+ tags: { deviceId },
6554
+ meta: { error: err instanceof Error ? err.message : String(err) }
6555
+ });
6556
+ }
6557
+ })();
6558
+ }
6302
6559
  async runMotionAnalysis(deviceId, frame, handle) {
6303
6560
  const ctx = this.ctx;
6304
6561
  const runner = this.runner;
@@ -6319,6 +6576,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6319
6576
  });
6320
6577
  if (!result) return;
6321
6578
  const detected = result.regions.length > 0;
6579
+ this.lastMotionNearPackageZone.set(deviceId, motionNearPackageZone({
6580
+ regions: result.regions,
6581
+ frameWidth: result.frameWidth,
6582
+ frameHeight: result.frameHeight,
6583
+ zoneUnion: this.packageZoneUnionByDevice.get(deviceId)
6584
+ }));
6322
6585
  if (detected !== (this.lastAnalyzerDetected.get(deviceId) ?? false)) {
6323
6586
  this.lastAnalyzerDetected.set(deviceId, detected);
6324
6587
  if (this.ctx.eventBus) this.ctx.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.MotionOnMotionChanged, {