@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,6 +1,6 @@
1
- import { At as string, Ct as boolean, Dt as number, Et as literal, K as pipelineRunnerCapability, M as deriveDetailCropRect, Mt as EventCategory, Ot as object, St as array, Tt as lazy, U as pickDetailCropConvention, W as pickNativeLeaseOverride, dt as createEvent, gt as nodePin, j as defineCustomActions, k as customAction, s as DEFAULT_DETAIL_CROP_CONVENTION, st as BaseAddon, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT, xt as _enum } from "../dist-T_8jMw7d.mjs";
2
- import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-XeGmL4xr.mjs";
3
- import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-SqED7Y9i.mjs";
1
+ import { At as string, Ct as boolean, Dt as number, Et as literal, K as pipelineRunnerCapability, M as deriveDetailCropRect, Mt as EventCategory, Ot as object, St as array, Tt as lazy, U as pickDetailCropConvention, W as pickNativeLeaseOverride, dt as createEvent, gt as nodePin, j as defineCustomActions, k as customAction, s as DEFAULT_DETAIL_CROP_CONVENTION, st as BaseAddon, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT, xt as _enum } from "../dist-HhcoV-Ky.mjs";
2
+ import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-CqBEWGSZ.mjs";
3
+ import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-BNlLGLBV.mjs";
4
4
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
6
6
  import { i as withDetectionIntent, n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-Ci7RXNGb.mjs";
@@ -2359,13 +2359,36 @@ function summarizeRunnerHardware(hardware, cpuCores = os.cpus().length) {
2359
2359
  * just picked one up — which coincide with motion in the package zone
2360
2360
  * SETTLING (transitioning from moving to still). This governor fires:
2361
2361
  *
2362
- * - on a **settle edge**: motion was observed, then has been absent for
2363
- * `settleMs` (kills flicker between motion frames and the "bag briefly
2364
- * set down then snatched back" case), and
2362
+ * - on a **settle edge**: motion NEAR THE PACKAGE ZONE was observed, then has
2363
+ * been absent for `settleMs` (kills flicker between motion frames and the
2364
+ * "bag briefly set down then snatched back" case). The settle edge opens a
2365
+ * **BURST**, and
2365
2366
  * - on a **low-cadence safety poll** (`pollIntervalMs`, ~30–60s): a
2366
2367
  * backstop so a delivery whose motion was never observed (a session-gap
2367
2368
  * camera that only decodes intermittently) is still eventually caught.
2368
2369
  *
2370
+ * ## The burst, and why the poll alone was not enough
2371
+ *
2372
+ * A parcel appears at the END of a motion episode — the courier walks up, puts
2373
+ * it down, walks away. That is a settle edge, and it is the moment we know
2374
+ * something MIGHT have been left behind. So the settle edge does not fire one
2375
+ * inference, it opens a burst: `burstCount` runs `burstIntervalMs` apart.
2376
+ *
2377
+ * The poll alone made the downstream promotion criterion unreachable in
2378
+ * PRACTICE and the notification useless in LATENCY. One observation per 45s
2379
+ * (and on a session-dispatch camera the measured gap was ~2 minutes) meant a
2380
+ * parcel needed minutes to accumulate enough evidence to be called parked, if
2381
+ * it ever did. The burst supplies the whole evidence chain inside ~20s, so a
2382
+ * delivery is reported while the courier is still in the street.
2383
+ *
2384
+ * The poll stays, unchanged in role: it is the SAFETY NET for the parcel whose
2385
+ * motion was never observed at all — a camera that just came up, an occlusion,
2386
+ * a missed analyzer frame. It is not the primary path any more.
2387
+ *
2388
+ * The two feed the SAME downstream round counter (`PackageStillnessTracker`),
2389
+ * which is what lets one promotion criterion serve both cadences. That is the
2390
+ * relation `scripts/check-package-promotion-satisfiable.ts` enforces.
2391
+ *
2369
2392
  * Pure logic with an injected clock (every method takes `now`), mirroring
2370
2393
  * `FrameGovernor` / `recomputeGovernor(now)` — no timers, no `Date.now()`
2371
2394
  * inside, fully unit-testable. Per-device state lives in a Map; the caller
@@ -2384,14 +2407,37 @@ function summarizeRunnerHardware(hardware, cpuCores = os.cpus().length) {
2384
2407
  */
2385
2408
  /** The step id (`PipelineStepInput.addonId`) the governor gates. */
2386
2409
  var PACKAGE_DETECTION_STEP_ID = "package-detection";
2410
+ /**
2411
+ * Burst: 5 runs, 5s apart — 0s, 5s, 10s, 15s, 20s after the settle edge.
2412
+ *
2413
+ * Sized against the two downstream numbers it has to satisfy, both pinned by
2414
+ * `scripts/check-package-promotion-satisfiable.ts`:
2415
+ *
2416
+ * - `PACKAGE_STILLNESS_ROUND_GAP_MS` (5 000) — the spacing must be ≥ this or
2417
+ * the runs collapse into one round;
2418
+ * - `packageDropDwellSec` (15s default) — the burst SPAN
2419
+ * (`(burstCount - 1) × burstIntervalMs` = 20s) must clear the dwell, or the
2420
+ * burst ends before the parcel has been still long enough to count and the
2421
+ * delivery falls back to the poll. 20s over 15s is one whole run of margin,
2422
+ * which is what absorbs a dropped analyzer frame.
2423
+ *
2424
+ * Cost: 5 extra inferences per motion episode, on cameras with an active
2425
+ * package zone only (the caller consults this governor only while the
2426
+ * `package-detection` step is in the camera's tree, and the resolver appends
2427
+ * that step only for an enabled `package`-stage zone rule).
2428
+ */
2387
2429
  var DEFAULT_MOTION_SETTLE_CONFIG = {
2388
2430
  settleMs: 3e3,
2389
- pollIntervalMs: 45e3
2431
+ pollIntervalMs: 45e3,
2432
+ burstCount: 5,
2433
+ burstIntervalMs: 5e3
2390
2434
  };
2391
2435
  var INITIAL_STATE = {
2392
2436
  lastMotionAt: null,
2393
2437
  armed: false,
2394
- lastRunAt: Number.NEGATIVE_INFINITY
2438
+ lastRunAt: Number.NEGATIVE_INFINITY,
2439
+ burstRemaining: 0,
2440
+ burstNextAt: 0
2395
2441
  };
2396
2442
  var NO_RUN = {
2397
2443
  run: false,
@@ -2417,18 +2463,35 @@ var MotionSettleGovernor = class {
2417
2463
  this.state.set(sample.deviceId, {
2418
2464
  lastMotionAt,
2419
2465
  armed: false,
2420
- lastRunAt: sample.now
2466
+ lastRunAt: sample.now,
2467
+ burstRemaining: Math.max(0, this.config.burstCount - 1),
2468
+ burstNextAt: sample.now + this.config.burstIntervalMs
2421
2469
  });
2422
2470
  return {
2423
2471
  run: true,
2424
2472
  reason: "settle"
2425
2473
  };
2426
2474
  }
2475
+ if (prev.burstRemaining > 0 && sample.now >= prev.burstNextAt) {
2476
+ this.state.set(sample.deviceId, {
2477
+ lastMotionAt,
2478
+ armed,
2479
+ lastRunAt: sample.now,
2480
+ burstRemaining: prev.burstRemaining - 1,
2481
+ burstNextAt: sample.now + this.config.burstIntervalMs
2482
+ });
2483
+ return {
2484
+ run: true,
2485
+ reason: "burst"
2486
+ };
2487
+ }
2427
2488
  if (pollReady) {
2428
2489
  this.state.set(sample.deviceId, {
2429
2490
  lastMotionAt,
2430
2491
  armed,
2431
- lastRunAt: sample.now
2492
+ lastRunAt: sample.now,
2493
+ burstRemaining: prev.burstRemaining,
2494
+ burstNextAt: prev.burstNextAt
2432
2495
  });
2433
2496
  return {
2434
2497
  run: true,
@@ -2438,7 +2501,9 @@ var MotionSettleGovernor = class {
2438
2501
  this.state.set(sample.deviceId, {
2439
2502
  lastMotionAt,
2440
2503
  armed,
2441
- lastRunAt: prev.lastRunAt
2504
+ lastRunAt: prev.lastRunAt,
2505
+ burstRemaining: prev.burstRemaining,
2506
+ burstNextAt: prev.burstNextAt
2442
2507
  });
2443
2508
  return NO_RUN;
2444
2509
  }
@@ -2452,6 +2517,90 @@ var MotionSettleGovernor = class {
2452
2517
  }
2453
2518
  };
2454
2519
  //#endregion
2520
+ //#region src/pipeline-runner/package-zone-proximity.ts
2521
+ /**
2522
+ * How much the zone-union box grows on EVERY side, as a fraction of its own
2523
+ * width / height.
2524
+ *
2525
+ * 0.5 means a doormat 271px wide on a 640px frame is approached through a
2526
+ * ~135px apron on each side — about one adult's width at that distance, which
2527
+ * is the thing being detected: a person standing next to the mat, not on it.
2528
+ * Larger and a passer-by on the pavement opens a burst; smaller and the
2529
+ * courier who never steps on the mat does not.
2530
+ */
2531
+ var PACKAGE_ZONE_DILATION = .5;
2532
+ /** Grow a box by {@link PACKAGE_ZONE_DILATION}, clamped to the frame. */
2533
+ function dilateZoneBox(box, dilation = PACKAGE_ZONE_DILATION) {
2534
+ const dx = box.w * dilation;
2535
+ const dy = box.h * dilation;
2536
+ const x = Math.max(0, box.x - dx);
2537
+ const y = Math.max(0, box.y - dy);
2538
+ return {
2539
+ x,
2540
+ y,
2541
+ w: Math.min(1, box.x + box.w + dx) - x,
2542
+ h: Math.min(1, box.y + box.h + dy) - y
2543
+ };
2544
+ }
2545
+ function intersects(a, b) {
2546
+ 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;
2547
+ }
2548
+ /**
2549
+ * Does any motion region reach the dilated package zone?
2550
+ *
2551
+ * **Fail-open on unknown geometry.** `zoneUnion === undefined` means the caller
2552
+ * has not (or could not) resolve the zones — a cold cache, a failed cap read.
2553
+ * It answers "yes, if there is any motion at all", which degrades to exactly
2554
+ * the whole-frame behaviour this replaced. The alternative — answering "no" —
2555
+ * would let one failed read silence package detection on a camera for as long
2556
+ * as the read kept failing, and be indistinguishable from a quiet doorstep.
2557
+ * That is the shape of D49, and it has cost this repo real deliveries.
2558
+ */
2559
+ function motionNearPackageZone(input) {
2560
+ if (input.regions.length === 0) return false;
2561
+ if (input.zoneUnion === void 0) return true;
2562
+ if (input.frameWidth <= 0 || input.frameHeight <= 0) return true;
2563
+ const target = dilateZoneBox(input.zoneUnion);
2564
+ for (const region of input.regions) if (intersects({
2565
+ x: region.bbox.x / input.frameWidth,
2566
+ y: region.bbox.y / input.frameHeight,
2567
+ w: region.bbox.w / input.frameWidth,
2568
+ h: region.bbox.h / input.frameHeight
2569
+ }, target)) return true;
2570
+ return false;
2571
+ }
2572
+ /**
2573
+ * The bounding box of a set of normalised polygons — the package zone union.
2574
+ * Returns `undefined` when there is no usable geometry, which the caller must
2575
+ * pass through as "unknown" rather than as an empty zone.
2576
+ */
2577
+ function packageZoneUnion(polygons) {
2578
+ let minX = Number.POSITIVE_INFINITY;
2579
+ let minY = Number.POSITIVE_INFINITY;
2580
+ let maxX = Number.NEGATIVE_INFINITY;
2581
+ let maxY = Number.NEGATIVE_INFINITY;
2582
+ let sawPoint = false;
2583
+ for (const polygon of polygons) for (const point of polygon) {
2584
+ sawPoint = true;
2585
+ if (point.x < minX) minX = point.x;
2586
+ if (point.y < minY) minY = point.y;
2587
+ if (point.x > maxX) maxX = point.x;
2588
+ if (point.y > maxY) maxY = point.y;
2589
+ }
2590
+ if (!sawPoint) return void 0;
2591
+ const x = Math.max(0, Math.min(1, minX));
2592
+ const y = Math.max(0, Math.min(1, minY));
2593
+ const w = Math.max(0, Math.min(1, maxX)) - x;
2594
+ const h = Math.max(0, Math.min(1, maxY)) - y;
2595
+ if (w <= 0 || h <= 0) return void 0;
2596
+ return {
2597
+ x,
2598
+ y,
2599
+ w,
2600
+ h
2601
+ };
2602
+ }
2603
+ //#endregion
2455
2604
  //#region src/pipeline-runner/native-crop-tier.ts
2456
2605
  /**
2457
2606
  * Pure tier decision for `getNativeCrop`. A native HIT always resolves to the
@@ -3829,6 +3978,13 @@ function isUsable(bbox) {
3829
3978
  //#endregion
3830
3979
  //#region src/pipeline-runner/index.ts
3831
3980
  /**
3981
+ * How often a device's package zone-union geometry is re-read. An operator
3982
+ * redrawing a package zone takes effect within one interval; the read is a
3983
+ * cross-addon cap call reached from the per-frame package gate, so it must be
3984
+ * throttled to something far slower than the frame rate.
3985
+ */
3986
+ var PACKAGE_ZONE_UNION_TTL_MS = 6e4;
3987
+ /**
3832
3988
  * Bound on the frames whose cutter is bound but whose result has not come back.
3833
3989
  * A few per camera in flight; the ceiling only catches results that never
3834
3990
  * arrive (shed, inference timeout, detach mid-flight).
@@ -4199,6 +4355,36 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4199
4355
  */
4200
4356
  packageGovernor = new MotionSettleGovernor();
4201
4357
  /**
4358
+ * Frames on which the governor DROPPED `package-detection` since that
4359
+ * device last ran it. The drop is the single most consequential silent
4360
+ * branch in the package chain — it decides the cadence that the whole
4361
+ * downstream promotion criterion has to be satisfiable at — and it logged
4362
+ * nothing at all, so "the parcel was never detected" and "the step never
4363
+ * ran" were indistinguishable in the logs. Counted here and reported on the
4364
+ * transition (first skip) and on every run, never per frame.
4365
+ */
4366
+ packageGateSkips = /* @__PURE__ */ new Map();
4367
+ /**
4368
+ * Per-device package zone-union box (normalised), the target the motion
4369
+ * proximity test measures against.
4370
+ *
4371
+ * A device MISSING from this map is "unknown", not "no zones" — and unknown
4372
+ * FAILS OPEN in `motionNearPackageZone`, degrading to the whole-frame motion
4373
+ * signal. Resolved off the frame path (on attach, and refreshed on the
4374
+ * package-step gate's own cadence), never inside `runMotionAnalysis`.
4375
+ */
4376
+ packageZoneUnionByDevice = /* @__PURE__ */ new Map();
4377
+ /** In-flight/last zone-union resolution time per device, so the refresh does
4378
+ * not stampede the zone caps from the motion path. */
4379
+ packageZoneUnionAt = /* @__PURE__ */ new Map();
4380
+ /**
4381
+ * Whether the last analyzer frame saw motion NEAR the device's package
4382
+ * zones — the boolean the settle-burst governor consumes. Distinct from
4383
+ * `lastAnalyzerDetected` (whole-frame), which is what the phase machine
4384
+ * uses and which must not change meaning.
4385
+ */
4386
+ lastMotionNearPackageZone = /* @__PURE__ */ new Map();
4387
+ /**
4202
4388
  * Last positive motion timestamp per device — preserved across the
4203
4389
  * OFF transition so the motion runtime-state slice keeps a stable
4204
4390
  * `lastDetectedAt` after the cooldown closes the phase. Cleared on
@@ -4399,6 +4585,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4399
4585
  }
4400
4586
  this.lastAnalyzerDetected.clear();
4401
4587
  this.packageGovernor.reset();
4588
+ this.packageGateSkips.clear();
4589
+ this.packageZoneUnionByDevice.clear();
4590
+ this.packageZoneUnionAt.clear();
4591
+ this.lastMotionNearPackageZone.clear();
4402
4592
  for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) this.clearOnboardAnalyzer(deviceId);
4403
4593
  if (this.runner) {
4404
4594
  this.runner.stop();
@@ -5661,6 +5851,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
5661
5851
  this.lastMotionFrameAt.delete(deviceId);
5662
5852
  this.lastDecodeUnexpectedEndAt.delete(deviceId);
5663
5853
  this.packageGovernor.forget(deviceId);
5854
+ this.packageGateSkips.delete(deviceId);
5855
+ this.packageZoneUnionByDevice.delete(deviceId);
5856
+ this.packageZoneUnionAt.delete(deviceId);
5857
+ this.lastMotionNearPackageZone.delete(deviceId);
5664
5858
  const warnPrefix = `${deviceId}:`;
5665
5859
  for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
5666
5860
  this.attached.delete(deviceId);
@@ -6237,13 +6431,31 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6237
6431
  if (steps.length === 0) return null;
6238
6432
  let dispatchSteps = steps;
6239
6433
  if (steps.some((s) => s.addonId === "package-detection" && s.enabled !== false)) {
6240
- if (!this.packageGovernor.evaluate({
6434
+ this.refreshPackageZoneUnion(deviceId);
6435
+ const motionInZone = this.lastMotionNearPackageZone.get(deviceId) ?? this.lastAnalyzerDetected.get(deviceId) ?? false;
6436
+ const decision = this.packageGovernor.evaluate({
6241
6437
  deviceId,
6242
- motionInZone: this.lastAnalyzerDetected.get(deviceId) ?? false,
6438
+ motionInZone,
6243
6439
  now: Date.now()
6244
- }).run) {
6440
+ });
6441
+ const skipped = this.packageGateSkips.get(deviceId) ?? 0;
6442
+ if (!decision.run) {
6245
6443
  dispatchSteps = steps.filter((s) => s.addonId !== PACKAGE_DETECTION_STEP_ID);
6444
+ if (skipped === 0) log.info("package-detection gated off by the motion-settle governor", {
6445
+ tags: { deviceId },
6446
+ meta: { motionInZone: this.lastAnalyzerDetected.get(deviceId) ?? false }
6447
+ });
6448
+ this.packageGateSkips.set(deviceId, skipped + 1);
6246
6449
  if (dispatchSteps.length === 0) return null;
6450
+ } else {
6451
+ this.packageGateSkips.set(deviceId, 0);
6452
+ log.info("package-detection dispatched by the motion-settle governor", {
6453
+ tags: { deviceId },
6454
+ meta: {
6455
+ reason: decision.reason,
6456
+ framesSkippedSinceLastRun: skipped
6457
+ }
6458
+ });
6247
6459
  }
6248
6460
  }
6249
6461
  let image;
@@ -6292,6 +6504,51 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6292
6504
  }
6293
6505
  return false;
6294
6506
  }
6507
+ /**
6508
+ * Refresh a device's package zone-union box, off the caller's path.
6509
+ *
6510
+ * Cross-addon reads go through `ctx.api` (the only cross-addon layer), and
6511
+ * they are RATE-LIMITED per device: this is reached from the per-frame
6512
+ * package gate, and two cap calls per frame would be a self-inflicted load
6513
+ * problem on the exact cameras the feature exists for.
6514
+ *
6515
+ * A FAILED read leaves the mirror untouched (D49): the last-known-good
6516
+ * geometry keeps being used, and a device that has never been read stays
6517
+ * absent from the map, which `motionNearPackageZone` treats as unknown and
6518
+ * fails OPEN on. Nothing here can make package detection quieter.
6519
+ */
6520
+ refreshPackageZoneUnion(deviceId) {
6521
+ const now = Date.now();
6522
+ if (now - (this.packageZoneUnionAt.get(deviceId) ?? 0) < PACKAGE_ZONE_UNION_TTL_MS) return;
6523
+ this.packageZoneUnionAt.set(deviceId, now);
6524
+ const api = this.ctx.api;
6525
+ if (!api) return;
6526
+ (async () => {
6527
+ try {
6528
+ const [zones, rules] = await Promise.all([api.zones.listZones.query({ deviceId }), api.zoneRules.listRules.query({
6529
+ deviceId,
6530
+ stage: "package"
6531
+ })]);
6532
+ const zoneIds = new Set(rules.filter((r) => r.enabled !== false).flatMap((r) => r.zoneIds));
6533
+ const polygons = zones.filter((z) => zoneIds.has(z.id)).map((z) => z.polygon);
6534
+ const union = packageZoneUnion(polygons);
6535
+ const previous = this.packageZoneUnionByDevice.get(deviceId);
6536
+ this.packageZoneUnionByDevice.set(deviceId, union);
6537
+ if (previous === void 0 && union !== void 0) this.ctx.logger.info("package zone geometry resolved — motion proximity armed", {
6538
+ tags: { deviceId },
6539
+ meta: {
6540
+ zoneUnion: union,
6541
+ zones: polygons.length
6542
+ }
6543
+ });
6544
+ } catch (err) {
6545
+ this.ctx.logger.warn("package zone geometry read failed — motion proximity fails open", {
6546
+ tags: { deviceId },
6547
+ meta: { error: err instanceof Error ? err.message : String(err) }
6548
+ });
6549
+ }
6550
+ })();
6551
+ }
6295
6552
  async runMotionAnalysis(deviceId, frame, handle) {
6296
6553
  const ctx = this.ctx;
6297
6554
  const runner = this.runner;
@@ -6312,6 +6569,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6312
6569
  });
6313
6570
  if (!result) return;
6314
6571
  const detected = result.regions.length > 0;
6572
+ this.lastMotionNearPackageZone.set(deviceId, motionNearPackageZone({
6573
+ regions: result.regions,
6574
+ frameWidth: result.frameWidth,
6575
+ frameHeight: result.frameHeight,
6576
+ zoneUnion: this.packageZoneUnionByDevice.get(deviceId)
6577
+ }));
6315
6578
  if (detected !== (this.lastAnalyzerDetected.get(deviceId) ?? false)) {
6316
6579
  this.lastAnalyzerDetected.set(deviceId, detected);
6317
6580
  if (this.ctx.eventBus) this.ctx.eventBus.emit(createEvent(EventCategory.MotionOnMotionChanged, {