@camstack/addon-pipeline 1.2.74 → 1.2.76

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 (36) hide show
  1. package/dist/{addon-utils-CQs-AjTJ.js → addon-utils-CTpQfjSR.js} +4 -4
  2. package/dist/audio-analyzer/index.js +5 -6
  3. package/dist/audio-analyzer/index.mjs +3 -3
  4. package/dist/detection-pipeline/index.js +9 -10
  5. package/dist/detection-pipeline/index.mjs +5 -5
  6. package/dist/{dist-CgJ8MRAy.js → dist-CZLjObZZ.js} +1996 -29
  7. package/dist/{dist-CKY0efl3.mjs → dist-zksfWnEA.mjs} +1961 -29
  8. package/dist/{event-loop-stall-monitor-CIapFIez.mjs → event-loop-stall-monitor-BuZA3loB.mjs} +1 -1
  9. package/dist/{event-loop-stall-monitor-Dx-zXcNg.js → event-loop-stall-monitor-CcBQAI28.js} +1 -1
  10. package/dist/lazy-sharp-6oymT_yf.mjs +341 -0
  11. package/dist/lazy-sharp-SWR_D1Um.js +360 -0
  12. package/dist/motion-wasm/index.js +3 -3
  13. package/dist/motion-wasm/index.mjs +3 -3
  14. package/dist/pipeline-runner/index.js +280 -18
  15. package/dist/pipeline-runner/index.mjs +278 -15
  16. package/dist/recorder/index.js +138 -32
  17. package/dist/recorder/index.mjs +137 -30
  18. package/dist/session-decode/decode-worker-child.js +311 -126
  19. package/dist/session-decode/decode-worker-child.mjs +311 -123
  20. package/dist/stream-broker/_stub.js +94 -94
  21. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVJ4gKkk.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Dd6XOtVn.mjs} +2 -2
  22. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-RYTOS6B-.mjs +26 -0
  23. 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 +26 -0
  24. package/dist/stream-broker/{hostInit-5SFdfUyc.mjs → hostInit-COc_Q_xX.mjs} +2 -2
  25. package/dist/stream-broker/index.js +12 -13
  26. package/dist/stream-broker/index.mjs +3 -3
  27. package/dist/stream-broker/remoteEntry.js +1 -1
  28. package/dist/{worker-protocol-D44DCkcs.js → worker-protocol-DAQ1iZFK.js} +1 -1
  29. package/dist/{worker-protocol-BzAQPUR-.mjs → worker-protocol-DyX_HbaJ.mjs} +1 -1
  30. package/package.json +1 -1
  31. package/dist/chunk-emK7D4bc.js +0 -35
  32. package/dist/lazy-sharp-B-mb8uWx.mjs +0 -20
  33. package/dist/lazy-sharp-CcYuXtsa.js +0 -25
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C_plRUtD.mjs +0 -26
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CExO0piw.mjs +0 -26
  36. package/dist/{addon-utils-CZ2xo67g.mjs → addon-utils-A2S9D7pu.mjs} +2 -2
@@ -2,20 +2,19 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-CgJ8MRAy.js");
7
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-Dx-zXcNg.js");
8
- const require_worker_protocol = require("../worker-protocol-D44DCkcs.js");
5
+ const require_dist = require("../dist-CZLjObZZ.js");
6
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-CcBQAI28.js");
7
+ const require_worker_protocol = require("../worker-protocol-DAQ1iZFK.js");
9
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
10
- const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
9
+ const require_lazy_sharp = require("../lazy-sharp-SWR_D1Um.js");
11
10
  const require_remote_restream = require("../remote-restream-BYbAsgUf.js");
12
11
  let sharp = require("sharp");
13
- sharp = require_chunk.__toESM(sharp);
12
+ sharp = require_dist.__toESM(sharp);
14
13
  let node_child_process = require("node:child_process");
15
14
  let node_url = require("node:url");
16
15
  let node_perf_hooks = require("node:perf_hooks");
17
16
  let node_os = require("node:os");
18
- node_os = require_chunk.__toESM(node_os);
17
+ node_os = require_dist.__toESM(node_os);
19
18
  //#region src/session-decode/fork-decode-worker.ts
20
19
  /** Absolute path to the built decode-worker child entry (a vite build entry). */
21
20
  var CHILD_ENTRY_PATH = (0, node_url.fileURLToPath)(new URL("../session-decode/decode-worker-child.js", require("url").pathToFileURL(__filename).href));
@@ -2367,13 +2366,36 @@ function summarizeRunnerHardware(hardware, cpuCores = node_os.cpus().length) {
2367
2366
  * just picked one up — which coincide with motion in the package zone
2368
2367
  * SETTLING (transitioning from moving to still). This governor fires:
2369
2368
  *
2370
- * - on a **settle edge**: motion was observed, then has been absent for
2371
- * `settleMs` (kills flicker between motion frames and the "bag briefly
2372
- * 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
2373
2373
  * - on a **low-cadence safety poll** (`pollIntervalMs`, ~30–60s): a
2374
2374
  * backstop so a delivery whose motion was never observed (a session-gap
2375
2375
  * camera that only decodes intermittently) is still eventually caught.
2376
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
+ *
2377
2399
  * Pure logic with an injected clock (every method takes `now`), mirroring
2378
2400
  * `FrameGovernor` / `recomputeGovernor(now)` — no timers, no `Date.now()`
2379
2401
  * inside, fully unit-testable. Per-device state lives in a Map; the caller
@@ -2392,14 +2414,37 @@ function summarizeRunnerHardware(hardware, cpuCores = node_os.cpus().length) {
2392
2414
  */
2393
2415
  /** The step id (`PipelineStepInput.addonId`) the governor gates. */
2394
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
+ */
2395
2436
  var DEFAULT_MOTION_SETTLE_CONFIG = {
2396
2437
  settleMs: 3e3,
2397
- pollIntervalMs: 45e3
2438
+ pollIntervalMs: 45e3,
2439
+ burstCount: 5,
2440
+ burstIntervalMs: 5e3
2398
2441
  };
2399
2442
  var INITIAL_STATE = {
2400
2443
  lastMotionAt: null,
2401
2444
  armed: false,
2402
- lastRunAt: Number.NEGATIVE_INFINITY
2445
+ lastRunAt: Number.NEGATIVE_INFINITY,
2446
+ burstRemaining: 0,
2447
+ burstNextAt: 0
2403
2448
  };
2404
2449
  var NO_RUN = {
2405
2450
  run: false,
@@ -2425,18 +2470,35 @@ var MotionSettleGovernor = class {
2425
2470
  this.state.set(sample.deviceId, {
2426
2471
  lastMotionAt,
2427
2472
  armed: false,
2428
- lastRunAt: sample.now
2473
+ lastRunAt: sample.now,
2474
+ burstRemaining: Math.max(0, this.config.burstCount - 1),
2475
+ burstNextAt: sample.now + this.config.burstIntervalMs
2429
2476
  });
2430
2477
  return {
2431
2478
  run: true,
2432
2479
  reason: "settle"
2433
2480
  };
2434
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
+ }
2435
2495
  if (pollReady) {
2436
2496
  this.state.set(sample.deviceId, {
2437
2497
  lastMotionAt,
2438
2498
  armed,
2439
- lastRunAt: sample.now
2499
+ lastRunAt: sample.now,
2500
+ burstRemaining: prev.burstRemaining,
2501
+ burstNextAt: prev.burstNextAt
2440
2502
  });
2441
2503
  return {
2442
2504
  run: true,
@@ -2446,7 +2508,9 @@ var MotionSettleGovernor = class {
2446
2508
  this.state.set(sample.deviceId, {
2447
2509
  lastMotionAt,
2448
2510
  armed,
2449
- lastRunAt: prev.lastRunAt
2511
+ lastRunAt: prev.lastRunAt,
2512
+ burstRemaining: prev.burstRemaining,
2513
+ burstNextAt: prev.burstNextAt
2450
2514
  });
2451
2515
  return NO_RUN;
2452
2516
  }
@@ -2460,6 +2524,90 @@ var MotionSettleGovernor = class {
2460
2524
  }
2461
2525
  };
2462
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
2463
2611
  //#region src/pipeline-runner/native-crop-tier.ts
2464
2612
  /**
2465
2613
  * Pure tier decision for `getNativeCrop`. A native HIT always resolves to the
@@ -3837,6 +3985,13 @@ function isUsable(bbox) {
3837
3985
  //#endregion
3838
3986
  //#region src/pipeline-runner/index.ts
3839
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
+ /**
3840
3995
  * Bound on the frames whose cutter is bound but whose result has not come back.
3841
3996
  * A few per camera in flight; the ceiling only catches results that never
3842
3997
  * arrive (shed, inference timeout, detach mid-flight).
@@ -4207,6 +4362,36 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4207
4362
  */
4208
4363
  packageGovernor = new MotionSettleGovernor();
4209
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
+ /**
4210
4395
  * Last positive motion timestamp per device — preserved across the
4211
4396
  * OFF transition so the motion runtime-state slice keeps a stable
4212
4397
  * `lastDetectedAt` after the cooldown closes the phase. Cleared on
@@ -4407,6 +4592,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4407
4592
  }
4408
4593
  this.lastAnalyzerDetected.clear();
4409
4594
  this.packageGovernor.reset();
4595
+ this.packageGateSkips.clear();
4596
+ this.packageZoneUnionByDevice.clear();
4597
+ this.packageZoneUnionAt.clear();
4598
+ this.lastMotionNearPackageZone.clear();
4410
4599
  for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) this.clearOnboardAnalyzer(deviceId);
4411
4600
  if (this.runner) {
4412
4601
  this.runner.stop();
@@ -5669,6 +5858,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
5669
5858
  this.lastMotionFrameAt.delete(deviceId);
5670
5859
  this.lastDecodeUnexpectedEndAt.delete(deviceId);
5671
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);
5672
5865
  const warnPrefix = `${deviceId}:`;
5673
5866
  for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
5674
5867
  this.attached.delete(deviceId);
@@ -6245,13 +6438,31 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6245
6438
  if (steps.length === 0) return null;
6246
6439
  let dispatchSteps = steps;
6247
6440
  if (steps.some((s) => s.addonId === "package-detection" && s.enabled !== false)) {
6248
- 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({
6249
6444
  deviceId,
6250
- motionInZone: this.lastAnalyzerDetected.get(deviceId) ?? false,
6445
+ motionInZone,
6251
6446
  now: Date.now()
6252
- }).run) {
6447
+ });
6448
+ const skipped = this.packageGateSkips.get(deviceId) ?? 0;
6449
+ if (!decision.run) {
6253
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);
6254
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
+ });
6255
6466
  }
6256
6467
  }
6257
6468
  let image;
@@ -6300,6 +6511,51 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6300
6511
  }
6301
6512
  return false;
6302
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
+ }
6303
6559
  async runMotionAnalysis(deviceId, frame, handle) {
6304
6560
  const ctx = this.ctx;
6305
6561
  const runner = this.runner;
@@ -6320,6 +6576,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6320
6576
  });
6321
6577
  if (!result) return;
6322
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
+ }));
6323
6585
  if (detected !== (this.lastAnalyzerDetected.get(deviceId) ?? false)) {
6324
6586
  this.lastAnalyzerDetected.set(deviceId, detected);
6325
6587
  if (this.ctx.eventBus) this.ctx.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.MotionOnMotionChanged, {