@camstack/addon-pipeline 1.2.317 → 1.2.321

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 (35) 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-CILrD3sC.mjs → default-detection-model-DBqAX4cL.mjs} +853 -14
  4. package/dist/{default-detection-model-DdIJSdQB.js → default-detection-model-SkL7rw1J.js} +864 -13
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-3isAnyIX.mjs → dist-CTLBxswm.mjs} +3189 -2634
  8. package/dist/{dist-DXU3Fu7j.js → dist-DMwFmudw.js} +3189 -2634
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-PY3N7_ym.mjs → node-BO3NIRH9.mjs} +1 -1
  12. package/dist/{node-DEpHS48Q.js → node-CJqa8iTR.js} +1 -1
  13. package/dist/pipeline-runner/index.js +13 -821
  14. package/dist/pipeline-runner/index.mjs +12 -820
  15. package/dist/{process-memory-DAGs0yZp.js → process-memory-BLd-PX9x.js} +1 -1
  16. package/dist/{process-memory-DE0M2AiK.mjs → process-memory-qCTMcKBo.mjs} +1 -1
  17. package/dist/recorder/index.js +15 -8
  18. package/dist/recorder/index.mjs +15 -8
  19. package/dist/{segment-demux-js-k3D5kt3W.mjs → segment-demux-js-6HsaPjoo.mjs} +1 -1
  20. package/dist/{segment-demux-js-BZhn3qOl.js → segment-demux-js-DXHU1xgc.js} +1 -1
  21. package/dist/session-decode/decode-worker-main.js +194 -13
  22. package/dist/session-decode/decode-worker-main.mjs +194 -13
  23. package/dist/stream-broker/_stub.js +2 -2
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DqP6Ev3L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C1yya1ze.mjs} +2 -2
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-gPsQTvgB.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DGvtO8aA.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-BRiacIOY.mjs → hostInit-BHlXLPez.mjs} +2 -2
  30. package/dist/stream-broker/index.js +46 -12
  31. package/dist/stream-broker/index.mjs +46 -12
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/package.json +1 -1
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CVT3OmxC.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-DHNf76xj.mjs +0 -26
@@ -1,4 +1,5 @@
1
- import { G as audioKindId, Yt as stepModelRunsOnFormat, _t as hfModelUrl, f as COCO_80_LABELS, i as AUDIO_MACRO_LABELS, p as COCO_TO_MACRO, vt as inferModelProvider, y as DEVICE_BACKEND_TO_FORMAT } from "./dist-3isAnyIX.mjs";
1
+ import { G as audioKindId, Yt as stepModelRunsOnFormat, _t as hfModelUrl, f as COCO_80_LABELS, i as AUDIO_MACRO_LABELS, p as COCO_TO_MACRO, vt as inferModelProvider, y as DEVICE_BACKEND_TO_FORMAT } from "./dist-CTLBxswm.mjs";
2
+ import { t as isWorkerReply } from "./worker-protocol-D_NzPcnh.mjs";
2
3
  import { randomUUID } from "node:crypto";
3
4
  //#region src/detection-pipeline/registry/effective-confidence.ts
4
5
  /** The default in force: the model's own floor, else the step's. */
@@ -2239,6 +2240,836 @@ function resolveFrameViewGeometry(sourceWidthInput, sourceHeightInput, spec) {
2239
2240
  };
2240
2241
  }
2241
2242
  //#endregion
2243
+ //#region src/session-decode/session-decode-coordinator.ts
2244
+ /** How long to wait after `kill()` before escalating to `kill('SIGKILL')`. */
2245
+ var SIGKILL_FALLBACK_MS = 1e3;
2246
+ /**
2247
+ * The longest a planned teardown keeps a session's worker alive so the frames
2248
+ * it already handed out can be read (D652).
2249
+ *
2250
+ * What is being waited for is the REST of a frame's trip through the runner —
2251
+ * semaphore wait + inference + result — for frames that were admitted before
2252
+ * the detach. Measured on the live hub 2026-09-26 over one hour of
2253
+ * `pipeline stats` (2 254 per-camera 10 s windows): the WORST enqueue→emit in a
2254
+ * window was ≤1 000 ms in 96.7% of windows and ≤1 500 ms in 98.0%; going to
2255
+ * 5 000 ms buys only another 0.9%, and that tail is the inference-pool GPU hang,
2256
+ * which no wait here can rescue. The cost of waiting is one idle worker (its
2257
+ * decode already stopped by `drain`) for at most this long, per detach.
2258
+ *
2259
+ * A frame still held when this expires is not dropped silently: the
2260
+ * coordinator logs the count, tagged with the camera, and then kills.
2261
+ */
2262
+ var SESSION_DECODE_DRAIN_BOUND_MS = 1500;
2263
+ /**
2264
+ * The miss reason for a native crop requested after its session's teardown.
2265
+ * Only crops already SENT when the drain began are waited for (D652); a later
2266
+ * one would ask a worker that is about to be killed.
2267
+ */
2268
+ var NATIVE_CROP_REFUSED_DRAINING = "session draining — crop requested after teardown";
2269
+ /**
2270
+ * The refusal for a `toBuffer` asked for once the worker has been told to exit
2271
+ * (D656). A read during a DRAIN is served — that is what the drain is for — but
2272
+ * after the kill no reply can come, and a raw rejection read as a worker fault.
2273
+ */
2274
+ var TO_BUFFER_REFUSED_KILLED = "session killed — read requested after the worker was told to exit";
2275
+ /** The refusal for a `toBuffer` the worker is no longer there to answer. */
2276
+ var TO_BUFFER_REFUSED_EXITED = "worker already exited";
2277
+ /** A read the session declined to send, by name — never a worker fault. */
2278
+ var SessionDecodeReadRefusedError = class extends Error {
2279
+ reason;
2280
+ frameId;
2281
+ constructor(reason, frameId) {
2282
+ super(`session-decode-coordinator: ${reason} (frameId=${frameId})`);
2283
+ this.reason = reason;
2284
+ this.frameId = frameId;
2285
+ this.name = "SessionDecodeReadRefusedError";
2286
+ }
2287
+ };
2288
+ /**
2289
+ * How often the cold-start wait REPORTS that it is still waiting. Task 0
2290
+ * live-measured cold-start (restream dial + hwaccel context + keyframe wait) at
2291
+ * 5-8s, so this must tolerate a slow-but-healthy start.
2292
+ *
2293
+ * It is no longer the point at which the session is abandoned — see
2294
+ * {@link DEFAULT_FIRST_FRAME_MAX_WAIT_MS}.
2295
+ */
2296
+ var DEFAULT_FIRST_FRAME_TIMEOUT_MS = 2e4;
2297
+ /**
2298
+ * Hard ceiling on the TOTAL wait for the FIRST frame of a dial.
2299
+ *
2300
+ * The first decodable frame is not a fixed cost: it is one IDR interval plus
2301
+ * every GOP the decoder has to discard. Measured on device 618 (2560x1440
2302
+ * h264, `Invalid NAL unit` / `corrupt decoded frame` in its bitstream), dial →
2303
+ * first decoded frame over the broker's own muted restream was 1.8 / 6.5 / 7.0
2304
+ * / 7.2 / 9.2 / 10.0 / 10.8 / 12.9 / 14.9 / 20.0 / 30.3 / >40 s across 12
2305
+ * trials — against a healthy 587's 2.0 s ± 0.02.
2306
+ *
2307
+ * **The "8.0 s IDR interval" this used to attribute 618's spread to was wrong,
2308
+ * and is corrected here rather than deleted** (2026-08-28): a stated number
2309
+ * nobody re-derived reads as verification, and this one was used to argue that
2310
+ * 618 was an encoder outlier. The fleet runs 100-frame GOPs at 25 fps — a
2311
+ * **4 000 ms** IDR interval — and two independent readings of 6 h of live logs
2312
+ * agree with the operator rather than with this docblock. First, the join-burst
2313
+ * span logged on `restream prime (lat-probe)` is the time since the last IDR,
2314
+ * uniform in `[0, IDR]` and censored at `PRIME_MAX_SPAN_MS`: its median is
2315
+ * ~1 000 ms on eleven cameras, which is what `uniform[0, 4000]` conditioned on
2316
+ * `≤2000` predicts. Second, `detectionIntentServed` over detection dials puts
2317
+ * the SERVED fraction at 44-61 % on the bulk of the fleet, i.e. the ~50 %
2318
+ * `P(span > 2000)` a 4 000 ms interval implies. 618 measures 51 %, which is the
2319
+ * fleet norm. The genuine long-GOP outlier is **637** at 30 % served (⇒ ~6.7 s).
2320
+ *
2321
+ * So the spread is not this camera's encoder. It is D57's fixed 2 000 ms bound
2322
+ * withholding the join burst WHOLE on roughly half of every 4 s-GOP camera's
2323
+ * dials — see `join-burst-bound.ts`, which states the arithmetic, and
2324
+ * `docs/architecture/load-ledger.md` (2026-08-28) for the measurement.
2325
+ *
2326
+ * Abandoning at 20 s was therefore not a timeout, it was a coin flip, and the
2327
+ * recovery made it worse: ending the session RE-DIALS, which throws the
2328
+ * accumulated wait away and re-pays the RTSP setup + probe from zero, and two
2329
+ * ends inside 60 s DETACH the camera. That loop cost 618 25.9% of its assigned
2330
+ * decode airtime in one day (325 of 1257 ten-second windows delivered nothing).
2331
+ *
2332
+ * The wait is bounded instead of deadlined: the worker keeps the outstanding
2333
+ * `pull` latched, so re-arming costs nothing but the wait, and the ceiling is
2334
+ * what stops a genuinely dead input from holding a spawn permit forever.
2335
+ */
2336
+ var DEFAULT_FIRST_FRAME_MAX_WAIT_MS = 45e3;
2337
+ /** Default timeout for every `pull` round-trip AFTER the first frame arrives. */
2338
+ var DEFAULT_FRAME_TIMEOUT_MS = 1e4;
2339
+ /** Narrow an 'exit' payload to {@link WorkerExitInfo} (fakes may emit undefined). */
2340
+ function toWorkerExitInfo(v) {
2341
+ const isInfo = (x) => typeof x === "object" && x !== null && "code" in x && "signal" in x;
2342
+ return isInfo(v) ? v : void 0;
2343
+ }
2344
+ function toError(err) {
2345
+ return err instanceof Error ? err : new Error(String(err));
2346
+ }
2347
+ /**
2348
+ * Per-session mutable state + the message-handling logic that adapts the
2349
+ * child's `WorkerReply` stream into resolved/rejected waiter promises. One
2350
+ * instance is created per `generateVideoFrames()` call (i.e. per forked
2351
+ * child) — never shared across sessions.
2352
+ */
2353
+ var SessionDecodeSession = class {
2354
+ child;
2355
+ logger;
2356
+ deviceId;
2357
+ firstFrameTimeoutMs;
2358
+ frameTimeoutMs;
2359
+ firstFrameMaxWaitMs;
2360
+ hooks;
2361
+ pendingPull = null;
2362
+ pendingToBuffers = /* @__PURE__ */ new Map();
2363
+ /** Outstanding `nativeCrop` requests, keyed by the monotonic `requestId`. */
2364
+ pendingNativeCrops = /* @__PURE__ */ new Map();
2365
+ nextNativeCropId = 1;
2366
+ exited = false;
2367
+ /** Set by teardown() — the session is ending, whether or not it drains first. */
2368
+ stopRequested = false;
2369
+ /** Set when `kill()` is sent, so handleExit can tell an intentional stop from a death. */
2370
+ killSent = false;
2371
+ /**
2372
+ * A SIGKILL already went out — an `'immediate'` teardown from the pump
2373
+ * (D666) and the generator's later `finally` both escalate; one kill is enough.
2374
+ */
2375
+ sigkillSent = false;
2376
+ /**
2377
+ * Images yielded and not yet closed that a CONSUMER still holds — queued,
2378
+ * in flight, or being read. Each dies with the worker and somebody was going
2379
+ * to read it first. This is what a drain waits on.
2380
+ */
2381
+ consumerImages = 0;
2382
+ /**
2383
+ * Images yielded and not yet closed that only a RETENTION holder keeps —
2384
+ * parked for a read that may come later (the on-demand JPEG fallback, D656).
2385
+ * They die with the worker too, but nobody reads them inside any bound worth
2386
+ * holding a worker for, so a drain does NOT wait on them.
2387
+ */
2388
+ retainedImages = 0;
2389
+ /** Armed while a drain waits; its expiry kills with frames still held. */
2390
+ drainTimer = null;
2391
+ sigkillTimer = null;
2392
+ pullTimeoutTimer = null;
2393
+ firstFrameReceived = false;
2394
+ /** `Date.now()` of the FIRST `pull` — the origin of the cold-start ceiling. */
2395
+ firstPullAt = 0;
2396
+ constructor(child, logger, deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs, hooks) {
2397
+ this.child = child;
2398
+ this.logger = logger;
2399
+ this.deviceId = deviceId;
2400
+ this.firstFrameTimeoutMs = firstFrameTimeoutMs;
2401
+ this.frameTimeoutMs = frameTimeoutMs;
2402
+ this.firstFrameMaxWaitMs = firstFrameMaxWaitMs;
2403
+ this.hooks = hooks;
2404
+ this.child.on("message", (m) => this.handleMessage(m));
2405
+ this.child.on("exit", (m) => this.handleExit(toWorkerExitInfo(m)));
2406
+ }
2407
+ start(source, opts, lease) {
2408
+ this.trySend({
2409
+ kind: "start",
2410
+ source,
2411
+ opts,
2412
+ lease
2413
+ });
2414
+ }
2415
+ /**
2416
+ * Send `pull` and resolve with the next `frame` or `ended` reply. Arms a
2417
+ * bounded liveness timeout for the round-trip: if the child stops
2418
+ * producing frames WITHOUT exiting, the timeout settles this pull as
2419
+ * `ended` so the caller's async generator resumes, runs its `finally`,
2420
+ * and tears the child down — see {@link handlePullTimeout}.
2421
+ */
2422
+ pullNext() {
2423
+ if (this.exited) return Promise.resolve({ kind: "ended" });
2424
+ if (this.firstPullAt === 0) this.firstPullAt = Date.now();
2425
+ return new Promise((resolve, reject) => {
2426
+ this.pendingPull = {
2427
+ resolve,
2428
+ reject
2429
+ };
2430
+ this.armPullTimeout();
2431
+ try {
2432
+ this.child.send({ kind: "pull" });
2433
+ } catch (err) {
2434
+ this.clearPullTimeout();
2435
+ this.pendingPull = null;
2436
+ reject(toError(err));
2437
+ }
2438
+ });
2439
+ }
2440
+ /** Send `toBuffer` for `frameId` and resolve with the matching `buffer` reply's bytes. */
2441
+ requestBuffer(frameId, opts) {
2442
+ if (this.exited) return Promise.reject(new SessionDecodeReadRefusedError(TO_BUFFER_REFUSED_EXITED, frameId));
2443
+ if (this.killSent) return Promise.reject(new SessionDecodeReadRefusedError(TO_BUFFER_REFUSED_KILLED, frameId));
2444
+ return new Promise((resolve, reject) => {
2445
+ const waiter = {
2446
+ resolve,
2447
+ reject
2448
+ };
2449
+ this.queueToBufferWaiter(frameId, waiter);
2450
+ try {
2451
+ this.child.send({
2452
+ kind: "toBuffer",
2453
+ frameId,
2454
+ opts
2455
+ });
2456
+ } catch (err) {
2457
+ this.removeToBufferWaiter(frameId, waiter);
2458
+ reject(toError(err));
2459
+ }
2460
+ });
2461
+ }
2462
+ /**
2463
+ * Send `nativeCrop` for `frameId` and resolve with the crop result, or `null`
2464
+ * on any miss (frame aged out, worker error, or worker already exited). Never
2465
+ * rejects — native crops are strictly best-effort with a free fallback.
2466
+ */
2467
+ requestNativeCrop(frameId, bbox, maxWidth) {
2468
+ if (this.exited) return Promise.resolve({ kind: "miss" });
2469
+ if (this.stopRequested) return Promise.resolve({
2470
+ kind: "miss",
2471
+ workerReason: NATIVE_CROP_REFUSED_DRAINING
2472
+ });
2473
+ const requestId = this.nextNativeCropId++;
2474
+ return new Promise((resolve) => {
2475
+ this.pendingNativeCrops.set(requestId, { resolve });
2476
+ try {
2477
+ this.child.send({
2478
+ kind: "nativeCrop",
2479
+ requestId,
2480
+ frameId,
2481
+ bbox,
2482
+ ...maxWidth !== void 0 ? { maxWidth } : {}
2483
+ });
2484
+ } catch {
2485
+ this.pendingNativeCrops.delete(requestId);
2486
+ resolve({ kind: "miss" });
2487
+ this.settleDrainIfDone();
2488
+ }
2489
+ });
2490
+ }
2491
+ /**
2492
+ * The runner has this frame's detections: cut one native subject tile per box
2493
+ * and END the hold on `frameId`. Fire and forget (no reply) so the encode can
2494
+ * never delay the runner; a dropped message costs only the tiles.
2495
+ *
2496
+ * An EMPTY box list is still sent — it is how a frame that detected nothing
2497
+ * releases its hold, and that is the common case.
2498
+ */
2499
+ cutTiles(frameId, bboxes) {
2500
+ if (this.exited) return;
2501
+ this.trySend({
2502
+ kind: "cutTiles",
2503
+ frameId,
2504
+ bboxes
2505
+ });
2506
+ }
2507
+ /**
2508
+ * Prompt-release the worker's RAM native-frame hold for `frameId`. Fire and
2509
+ * forget (no reply): the hold is also bounded by a frame count, so a dropped
2510
+ * release only defers reclaim. No-op once the worker has exited.
2511
+ */
2512
+ releaseLease(frameId) {
2513
+ if (this.exited) return;
2514
+ this.trySend({
2515
+ kind: "releaseNativeLease",
2516
+ frameId
2517
+ });
2518
+ }
2519
+ /**
2520
+ * Tell the worker `frameId` reached inference, so its native lease is worth
2521
+ * keeping. Fire and forget, no reply. Ordering matters and is free here: the
2522
+ * caller runs in the same turn that consumed the frame, and the pull that
2523
+ * decides this frame's admission is issued after that turn returns.
2524
+ */
2525
+ markInferred(frameId) {
2526
+ if (this.exited) return;
2527
+ this.trySend({
2528
+ kind: "markInferred",
2529
+ frameId
2530
+ });
2531
+ }
2532
+ /** A yielded image the consumer now holds — see {@link consumerImages}. */
2533
+ noteImageOpened() {
2534
+ this.consumerImages += 1;
2535
+ }
2536
+ /**
2537
+ * An open image moved between a consumer and a retention-only holder
2538
+ * (D656). Losing its last consumer may complete a waiting drain.
2539
+ */
2540
+ noteImageHolder(pendingConsumer) {
2541
+ if (pendingConsumer) {
2542
+ this.retainedImages = Math.max(0, this.retainedImages - 1);
2543
+ this.consumerImages += 1;
2544
+ return;
2545
+ }
2546
+ this.consumerImages = Math.max(0, this.consumerImages - 1);
2547
+ this.retainedImages += 1;
2548
+ this.settleDrainIfDone();
2549
+ }
2550
+ /** A yielded image was closed; a waiting drain may now be done. */
2551
+ noteImageClosed(pendingConsumer) {
2552
+ if (pendingConsumer) this.consumerImages = Math.max(0, this.consumerImages - 1);
2553
+ else this.retainedImages = Math.max(0, this.retainedImages - 1);
2554
+ this.settleDrainIfDone();
2555
+ }
2556
+ /**
2557
+ * End the session.
2558
+ *
2559
+ * `'drain'` with frames still held: tell the worker to stop decoding (it
2560
+ * keeps every frame it holds), and kill only once every yielded image has
2561
+ * been closed and every read answered — or when
2562
+ * {@link SESSION_DECODE_DRAIN_BOUND_MS} expires, which is logged. Nothing
2563
+ * held: `stop` + SIGTERM now. `'immediate'`: SIGKILL now.
2564
+ *
2565
+ * A SECOND call that says `'immediate'` while a drain is in progress
2566
+ * ESCALATES (D652): addon shutdown lands on sessions already draining from
2567
+ * a detach, and the runner exits right after — a drain it does not wait for
2568
+ * is an orphaned worker holding its GPU pool. Any other repeat is ignored.
2569
+ */
2570
+ teardown(mode) {
2571
+ if (this.stopRequested) {
2572
+ if (mode === "immediate") this.escalateToImmediate();
2573
+ return;
2574
+ }
2575
+ this.stopRequested = true;
2576
+ this.clearPullTimeout();
2577
+ if (this.exited) return;
2578
+ if (mode === "drain" && this.unsettledCount() > 0) {
2579
+ this.trySend({ kind: "drain" });
2580
+ this.drainTimer = setTimeout(() => this.handleDrainBoundExpired(), SESSION_DECODE_DRAIN_BOUND_MS);
2581
+ this.drainTimer.unref?.();
2582
+ return;
2583
+ }
2584
+ this.killNow(mode);
2585
+ }
2586
+ /** The drain (or a SIGTERM still waiting on its fallback) gives way to SIGKILL now. */
2587
+ escalateToImmediate() {
2588
+ if (this.exited || this.sigkillSent) return;
2589
+ if (this.drainTimer !== null) {
2590
+ clearTimeout(this.drainTimer);
2591
+ this.drainTimer = null;
2592
+ }
2593
+ if (!this.killSent) {
2594
+ this.killNow("immediate");
2595
+ return;
2596
+ }
2597
+ if (this.sigkillTimer !== null) {
2598
+ clearTimeout(this.sigkillTimer);
2599
+ this.sigkillTimer = null;
2600
+ }
2601
+ this.tryKill("SIGKILL");
2602
+ }
2603
+ /**
2604
+ * Frames a CONSUMER still holds, reads still waiting on the worker, and
2605
+ * native crops already REQUESTED. A crop asked for before the teardown
2606
+ * request is waited for; one asked for after it is refused by name — post-analysis's
2607
+ * ordinary crop arrives 2.2–9.9 s after the frame, past any bound worth
2608
+ * holding a worker for, and is served from subject tiles that die with the
2609
+ * worker either way.
2610
+ *
2611
+ * A frame only PARKED for such a late read ({@link retainedImages}) is not
2612
+ * counted, for the same reason (D656): counting it made nearly every
2613
+ * teardown wait the full bound for frames nobody was going to read. Its read,
2614
+ * once STARTED, is a pending read like any other and is waited for.
2615
+ */
2616
+ unsettledCount() {
2617
+ return this.consumerImages + this.pendingReadCount() + this.pendingNativeCrops.size;
2618
+ }
2619
+ /** What the session still holds, per category — every drain line carries it. */
2620
+ heldCounts() {
2621
+ return {
2622
+ consumerFrames: this.consumerImages,
2623
+ retainedFrames: this.retainedImages,
2624
+ pendingReads: this.pendingReadCount(),
2625
+ pendingCrops: this.pendingNativeCrops.size
2626
+ };
2627
+ }
2628
+ pendingReadCount() {
2629
+ let n = 0;
2630
+ for (const waiters of this.pendingToBuffers.values()) n += waiters.length;
2631
+ return n;
2632
+ }
2633
+ settleDrainIfDone() {
2634
+ if (this.drainTimer === null || this.unsettledCount() > 0) return;
2635
+ clearTimeout(this.drainTimer);
2636
+ this.drainTimer = null;
2637
+ this.killNow("drain");
2638
+ }
2639
+ /**
2640
+ * The drain ran out of time with frames still held. Those frames die with
2641
+ * the worker; that is a branch that DROPS work (D391), so it says how many,
2642
+ * on which camera.
2643
+ */
2644
+ handleDrainBoundExpired() {
2645
+ this.drainTimer = null;
2646
+ if (this.exited) return;
2647
+ this.logger.warn("session-decode-coordinator: drain bound expired — killing the worker with frames still held", {
2648
+ tags: { deviceId: this.deviceId },
2649
+ meta: {
2650
+ ...this.heldCounts(),
2651
+ boundMs: SESSION_DECODE_DRAIN_BOUND_MS
2652
+ }
2653
+ });
2654
+ this.killNow("drain");
2655
+ }
2656
+ /**
2657
+ * `stop`, then the kill — unless already exited.
2658
+ *
2659
+ * `'drain'`: SIGTERM, so the child flushes its last lines (`bounded-exit.ts`),
2660
+ * and a SIGKILL fallback. `'immediate'`: SIGKILL outright. Since D652 gave the
2661
+ * child a SIGTERM handler, SIGTERM is no longer instant death: a worker
2662
+ * blocked in a native call (a VAAPI hang) never runs the handler, and the
2663
+ * fallback was an `unref`'d timer the exiting parent did not wait for — so
2664
+ * on shutdown that worker was orphaned with its GPU pool. The fallback timer
2665
+ * is now ref'd: it exists only for a drain while the parent is alive (a
2666
+ * shutdown escalates to SIGKILL and clears it), and a timer that keeps the
2667
+ * process up for at most one second is the price of a kill that lands.
2668
+ */
2669
+ killNow(mode) {
2670
+ if (this.killSent) return;
2671
+ this.killSent = true;
2672
+ if (this.retainedImages > 0) this.logger.debug("session-decode-coordinator: retained frames die with the worker", {
2673
+ tags: { deviceId: this.deviceId },
2674
+ meta: {
2675
+ mode,
2676
+ ...this.heldCounts()
2677
+ }
2678
+ });
2679
+ this.trySend({ kind: "stop" });
2680
+ if (mode === "immediate") {
2681
+ this.tryKill("SIGKILL");
2682
+ return;
2683
+ }
2684
+ this.tryKill(void 0);
2685
+ if (this.exited) return;
2686
+ this.sigkillTimer = setTimeout(() => {
2687
+ this.sigkillTimer = null;
2688
+ if (this.exited) return;
2689
+ this.logger.warn("session-decode-coordinator: child did not exit within SIGKILL fallback window — forcing", {
2690
+ tags: { deviceId: this.deviceId },
2691
+ meta: { fallbackMs: SIGKILL_FALLBACK_MS }
2692
+ });
2693
+ this.tryKill("SIGKILL");
2694
+ }, SIGKILL_FALLBACK_MS);
2695
+ }
2696
+ handleMessage(raw) {
2697
+ if (!isWorkerReply(raw)) {
2698
+ this.logger.warn("session-decode-coordinator: dropping malformed worker reply", { meta: { raw } });
2699
+ return;
2700
+ }
2701
+ switch (raw.kind) {
2702
+ case "frame":
2703
+ this.settlePendingPull({
2704
+ kind: "frame",
2705
+ reply: raw
2706
+ });
2707
+ return;
2708
+ case "ended":
2709
+ this.settlePendingPull({ kind: "ended" });
2710
+ return;
2711
+ case "buffer":
2712
+ this.settleToBuffer(raw.frameId, raw.bytes);
2713
+ return;
2714
+ case "nativeCropResult":
2715
+ this.settleNativeCrop(raw.requestId, {
2716
+ kind: "crop",
2717
+ crop: {
2718
+ bytes: raw.bytes,
2719
+ width: raw.width,
2720
+ height: raw.height,
2721
+ ...raw.source ? { source: raw.source } : {}
2722
+ }
2723
+ });
2724
+ return;
2725
+ case "nativeCropMiss":
2726
+ if (raw.reason !== void 0) this.logger.debug("session-decode: native crop miss", { meta: {
2727
+ requestId: raw.requestId,
2728
+ reason: raw.reason
2729
+ } });
2730
+ this.settleNativeCrop(raw.requestId, {
2731
+ kind: "miss",
2732
+ ...raw.reason !== void 0 ? { workerReason: raw.reason } : {}
2733
+ });
2734
+ return;
2735
+ case "ready": return;
2736
+ case "error":
2737
+ this.settleError(raw.message, raw.frameId);
2738
+ return;
2739
+ case "log":
2740
+ this.logger[raw.level](raw.message);
2741
+ return;
2742
+ case "dialTiming":
2743
+ this.hooks.onDialTiming?.({
2744
+ hwContextMs: raw.hwContextMs,
2745
+ demuxerOpenMs: raw.demuxerOpenMs,
2746
+ decoderCreateMs: raw.decoderCreateMs,
2747
+ firstDecodeMs: raw.firstDecodeMs,
2748
+ streamInfoProbed: raw.streamInfoProbed
2749
+ });
2750
+ return;
2751
+ case "preRollSettled":
2752
+ this.hooks.onPreRollSettled?.({
2753
+ read: raw.read,
2754
+ released: raw.released,
2755
+ lostByReason: raw.lostByReason,
2756
+ unsettled: raw.unsettled
2757
+ });
2758
+ return;
2759
+ case "preRollReport":
2760
+ this.hooks.onPreRollReport?.({
2761
+ offered: raw.offered,
2762
+ inferred: raw.inferred,
2763
+ dropped: raw.dropped,
2764
+ droppedByReason: raw.droppedByReason,
2765
+ strideMs: raw.strideMs,
2766
+ subsampled: raw.subsampled,
2767
+ spanMs: raw.spanMs,
2768
+ budgetFrames: raw.budgetFrames
2769
+ });
2770
+ return;
2771
+ }
2772
+ }
2773
+ handleExit(info) {
2774
+ if (!this.exited) this.hooks.onWorkerExit?.();
2775
+ if (!this.killSent && !this.exited && info?.killedByRunner === true) this.logger.debug("session-decode worker ended by the runner shutdown kill", {
2776
+ tags: { deviceId: this.deviceId },
2777
+ meta: {
2778
+ code: info.code,
2779
+ signal: info.signal
2780
+ }
2781
+ });
2782
+ else if (!this.killSent && !this.exited) this.logger.warn(this.stopRequested ? "session-decode worker exited unexpectedly while draining" : "session-decode worker exited unexpectedly", {
2783
+ tags: { deviceId: this.deviceId },
2784
+ meta: {
2785
+ code: info?.code ?? null,
2786
+ signal: info?.signal ?? null,
2787
+ ...this.stopRequested ? this.heldCounts() : {}
2788
+ }
2789
+ });
2790
+ this.exited = true;
2791
+ if (this.drainTimer) {
2792
+ clearTimeout(this.drainTimer);
2793
+ this.drainTimer = null;
2794
+ }
2795
+ if (this.sigkillTimer) {
2796
+ clearTimeout(this.sigkillTimer);
2797
+ this.sigkillTimer = null;
2798
+ }
2799
+ this.clearPullTimeout();
2800
+ if (this.pendingPull) {
2801
+ const waiter = this.pendingPull;
2802
+ this.pendingPull = null;
2803
+ waiter.resolve({ kind: "ended" });
2804
+ }
2805
+ for (const waiters of this.pendingToBuffers.values()) for (const waiter of waiters) waiter.reject(/* @__PURE__ */ new Error("session-decode-coordinator: worker exited before toBuffer reply"));
2806
+ this.pendingToBuffers.clear();
2807
+ for (const waiter of this.pendingNativeCrops.values()) waiter.resolve({ kind: "miss" });
2808
+ this.pendingNativeCrops.clear();
2809
+ }
2810
+ /** Settle the `nativeCrop` waiter for `requestId` (dropping unknown/stale ids). */
2811
+ settleNativeCrop(requestId, attempt) {
2812
+ const waiter = this.pendingNativeCrops.get(requestId);
2813
+ if (!waiter) return;
2814
+ this.pendingNativeCrops.delete(requestId);
2815
+ waiter.resolve(attempt);
2816
+ this.settleDrainIfDone();
2817
+ }
2818
+ settlePendingPull(outcome) {
2819
+ const waiter = this.pendingPull;
2820
+ if (!waiter) {
2821
+ this.logger.debug("session-decode-coordinator: dropping unsolicited pull-shaped reply", { meta: { outcomeKind: outcome.kind } });
2822
+ return;
2823
+ }
2824
+ this.pendingPull = null;
2825
+ this.clearPullTimeout();
2826
+ if (outcome.kind === "frame") this.firstFrameReceived = true;
2827
+ waiter.resolve(outcome);
2828
+ }
2829
+ /**
2830
+ * Fires when a `pull` round-trip outlives its liveness timeout — the child
2831
+ * has not produced a frame and has not exited.
2832
+ *
2833
+ * BEFORE the first frame this is not yet a failure. The worker latches the
2834
+ * outstanding `pull` (`decode-worker-child.handlePull` sets `pendingPull` and
2835
+ * delivers on the next decoded frame), so re-arming the timer costs nothing
2836
+ * but the wait and the session keeps the dial, the RTSP setup and the probe
2837
+ * it has already paid for. Only the {@link firstFrameMaxWaitMs} ceiling ends
2838
+ * it — see that constant for the measurement that set it.
2839
+ *
2840
+ * AFTER the first frame the deadline is unchanged: a steady stream that stops
2841
+ * is a hung decode, and ending settles the outstanding pull as `ended` (not a
2842
+ * throw) so the consumer's async generator resumes cleanly, runs its
2843
+ * `finally`, and tears the child down via {@link teardown}.
2844
+ *
2845
+ * Every line here carries `tags: { deviceId }` and a `reason`, because
2846
+ * "detaching" a camera is the loudest dropped work in this path and an
2847
+ * operator has to be able to answer "why did 618 stop" from the logs alone.
2848
+ */
2849
+ handlePullTimeout(timeoutMs) {
2850
+ this.pullTimeoutTimer = null;
2851
+ const waiter = this.pendingPull;
2852
+ if (!waiter) return;
2853
+ const waitedMs = this.firstPullAt === 0 ? timeoutMs : Date.now() - this.firstPullAt;
2854
+ if (!this.firstFrameReceived && waitedMs < this.firstFrameMaxWaitMs) {
2855
+ this.logger.warn("session-decode-coordinator: still waiting for its first decodable frame — holding the dial", {
2856
+ tags: { deviceId: this.deviceId },
2857
+ meta: {
2858
+ waitedMs,
2859
+ reportEveryMs: timeoutMs,
2860
+ maxWaitMs: this.firstFrameMaxWaitMs,
2861
+ hint: "long IDR interval or a discarded GOP looks exactly like this"
2862
+ }
2863
+ });
2864
+ this.armPullTimeout();
2865
+ return;
2866
+ }
2867
+ this.pendingPull = null;
2868
+ const reason = this.firstFrameReceived ? "stalled-after-first-frame" : "no-first-frame";
2869
+ this.logger.warn(this.firstFrameReceived ? "session-decode-coordinator: gave up waiting for the next frame — ending session" : "session-decode-coordinator: gave up waiting for the first decodable frame — ending session", {
2870
+ tags: { deviceId: this.deviceId },
2871
+ meta: {
2872
+ reason,
2873
+ waitedMs,
2874
+ timeoutMs,
2875
+ ...this.firstFrameReceived ? {} : { maxWaitMs: this.firstFrameMaxWaitMs }
2876
+ }
2877
+ });
2878
+ waiter.resolve({ kind: "ended" });
2879
+ }
2880
+ /**
2881
+ * Arm the next round-trip deadline. Before the first frame the timer is
2882
+ * clamped to what is LEFT of the ceiling, so the give-up lands exactly on
2883
+ * {@link firstFrameMaxWaitMs} instead of on the next report boundary past it
2884
+ * — otherwise a 45 s ceiling reported every 20 s would really give up at 60 s.
2885
+ */
2886
+ armPullTimeout() {
2887
+ const timeoutMs = this.firstFrameReceived ? this.frameTimeoutMs : Math.max(1, Math.min(this.firstFrameTimeoutMs, this.firstFrameMaxWaitMs - (this.firstPullAt === 0 ? 0 : Date.now() - this.firstPullAt)));
2888
+ this.pullTimeoutTimer = setTimeout(() => this.handlePullTimeout(timeoutMs), timeoutMs);
2889
+ this.pullTimeoutTimer.unref?.();
2890
+ }
2891
+ clearPullTimeout() {
2892
+ if (this.pullTimeoutTimer) {
2893
+ clearTimeout(this.pullTimeoutTimer);
2894
+ this.pullTimeoutTimer = null;
2895
+ }
2896
+ }
2897
+ /**
2898
+ * Correlate by `frameId`: a `buffer` reply only ever settles waiters queued
2899
+ * under the SAME frameId — a late reply for a stale/superseded frame that
2900
+ * nobody is (still) awaiting is dropped, never mistakenly resolves a
2901
+ * different, newer frame's waiter.
2902
+ */
2903
+ settleToBuffer(frameId, bytes) {
2904
+ const queue = this.pendingToBuffers.get(frameId);
2905
+ if (!queue || queue.length === 0) {
2906
+ this.logger.debug("session-decode-coordinator: dropping stale/unknown buffer reply", { meta: { frameId } });
2907
+ return;
2908
+ }
2909
+ const waiter = queue.shift();
2910
+ if (queue.length === 0) this.pendingToBuffers.delete(frameId);
2911
+ waiter?.resolve(bytes);
2912
+ this.settleDrainIfDone();
2913
+ }
2914
+ /**
2915
+ * `error` replies MAY carry a `frameId` (a `toBuffer`-scoped miss, e.g. a
2916
+ * stale/unknown frameId or a scale failure in `decode-worker-child.ts`) —
2917
+ * those fail ONLY that frame's `toBuffer` waiter(s) so the pull stays alive
2918
+ * and the generator keeps running (P2/I1: a single dropped frame must not
2919
+ * tear the whole session down). An error WITHOUT a `frameId` is a
2920
+ * session-level failure (decode-loop crash, dial error) and keeps the
2921
+ * original fatal behavior: fail the outstanding `pull` first (the more
2922
+ * common source), else fail every outstanding `toBuffer` waiter rather than
2923
+ * silently dropping the error and leaving a caller hanging forever.
2924
+ */
2925
+ settleError(message, frameId) {
2926
+ const err = /* @__PURE__ */ new Error(`session-decode-coordinator: worker error — ${message}`);
2927
+ if (frameId !== void 0) {
2928
+ const queue = this.pendingToBuffers.get(frameId);
2929
+ if (queue && queue.length > 0) {
2930
+ for (const waiter of queue) waiter.reject(err);
2931
+ this.pendingToBuffers.delete(frameId);
2932
+ this.settleDrainIfDone();
2933
+ return;
2934
+ }
2935
+ this.logger.debug("session-decode-coordinator: dropping toBuffer error for unknown frameId", { meta: { frameId } });
2936
+ return;
2937
+ }
2938
+ if (this.pendingPull) {
2939
+ const waiter = this.pendingPull;
2940
+ this.pendingPull = null;
2941
+ this.clearPullTimeout();
2942
+ waiter.reject(err);
2943
+ return;
2944
+ }
2945
+ for (const waiters of this.pendingToBuffers.values()) for (const waiter of waiters) waiter.reject(err);
2946
+ this.pendingToBuffers.clear();
2947
+ this.settleDrainIfDone();
2948
+ }
2949
+ queueToBufferWaiter(frameId, waiter) {
2950
+ const queue = this.pendingToBuffers.get(frameId);
2951
+ if (queue) queue.push(waiter);
2952
+ else this.pendingToBuffers.set(frameId, [waiter]);
2953
+ }
2954
+ removeToBufferWaiter(frameId, waiter) {
2955
+ const queue = this.pendingToBuffers.get(frameId);
2956
+ if (!queue) return;
2957
+ const idx = queue.indexOf(waiter);
2958
+ if (idx >= 0) queue.splice(idx, 1);
2959
+ if (queue.length === 0) this.pendingToBuffers.delete(frameId);
2960
+ }
2961
+ trySend(msg) {
2962
+ try {
2963
+ this.child.send(msg);
2964
+ } catch (err) {
2965
+ this.logger.warn("session-decode-coordinator: send to child failed", { meta: {
2966
+ requestKind: msg.kind,
2967
+ err: toError(err).message
2968
+ } });
2969
+ }
2970
+ }
2971
+ tryKill(signal) {
2972
+ try {
2973
+ this.child.kill(signal);
2974
+ if (signal === "SIGKILL") this.sigkillSent = true;
2975
+ } catch (err) {
2976
+ this.logger.warn("session-decode-coordinator: kill() on child failed", {
2977
+ tags: { deviceId: this.deviceId },
2978
+ meta: {
2979
+ signal,
2980
+ err: toError(err).message
2981
+ }
2982
+ });
2983
+ }
2984
+ }
2985
+ };
2986
+ /** Build the {@link FrameImage} adapter for one `frame` reply. */
2987
+ function buildFrameImage(session, reply) {
2988
+ let closed = false;
2989
+ let pendingConsumer = true;
2990
+ session.noteImageOpened();
2991
+ return {
2992
+ width: reply.width,
2993
+ height: reply.height,
2994
+ format: reply.format,
2995
+ frameId: reply.frameId,
2996
+ toBuffer: async (opts) => {
2997
+ if (closed) throw new Error(`session-decode-coordinator: toBuffer() called on a closed frame (frameId=${reply.frameId})`);
2998
+ return session.requestBuffer(reply.frameId, opts);
2999
+ },
3000
+ nativeCrop: (bbox, maxWidth) => session.requestNativeCrop(reply.frameId, bbox, maxWidth),
3001
+ cutTiles: (bboxes) => session.cutTiles(reply.frameId, bboxes),
3002
+ releaseNativeLease: () => session.releaseLease(reply.frameId),
3003
+ markInferred: () => session.markInferred(reply.frameId),
3004
+ setPendingConsumer: (pending) => {
3005
+ if (closed || pending === pendingConsumer) return;
3006
+ pendingConsumer = pending;
3007
+ session.noteImageHolder(pending);
3008
+ },
3009
+ close: () => {
3010
+ if (closed) return;
3011
+ closed = true;
3012
+ session.noteImageClosed(pendingConsumer);
3013
+ }
3014
+ };
3015
+ }
3016
+ /**
3017
+ * Create the `VideoFrameGenerator` adapter: forks one decode-worker child per
3018
+ * `generateVideoFrames()` call, drives it with `pull`/`toBuffer` requests
3019
+ * one at a time (no pre-fetch — back-pressure is the async generator's
3020
+ * natural suspend-on-yield), and guarantees the child is torn down
3021
+ * (bounded drain, then `stop` + `kill()` + SIGKILL fallback) in every exit
3022
+ * path via `finally`.
3023
+ */
3024
+ function createSessionDecodeCoordinator(deps) {
3025
+ const firstFrameTimeoutMs = deps.firstFrameTimeoutMs ?? DEFAULT_FIRST_FRAME_TIMEOUT_MS;
3026
+ const frameTimeoutMs = deps.frameTimeoutMs ?? DEFAULT_FRAME_TIMEOUT_MS;
3027
+ const firstFrameMaxWaitMs = Math.max(deps.firstFrameMaxWaitMs ?? DEFAULT_FIRST_FRAME_MAX_WAIT_MS, firstFrameTimeoutMs);
3028
+ return { async *generateVideoFrames(source, opts) {
3029
+ const session = new SessionDecodeSession(deps.fork(source), deps.logger, source.deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs, {
3030
+ ...deps.onDialTiming ? { onDialTiming: deps.onDialTiming } : {},
3031
+ ...deps.onPreRollReport ? { onPreRollReport: deps.onPreRollReport } : {},
3032
+ ...deps.onPreRollSettled ? { onPreRollSettled: deps.onPreRollSettled } : {},
3033
+ ...deps.onWorkerExit ? { onWorkerExit: deps.onWorkerExit } : {}
3034
+ });
3035
+ session.start(source, opts, deps.lease());
3036
+ deps.onSessionTeardown?.((mode) => session.teardown(mode));
3037
+ let failed = false;
3038
+ try {
3039
+ while (true) {
3040
+ const outcome = await session.pullNext();
3041
+ if (outcome.kind === "ended") return;
3042
+ const { reply } = outcome;
3043
+ yield {
3044
+ timestamp: reply.timestamp,
3045
+ image: buildFrameImage(session, reply),
3046
+ ...reply.capturedAt !== void 0 ? { capturedAt: reply.capturedAt } : {},
3047
+ ...reply.preRoll === true ? { preRoll: true } : {}
3048
+ };
3049
+ }
3050
+ } catch (err) {
3051
+ failed = true;
3052
+ throw err;
3053
+ } finally {
3054
+ session.teardown(failed ? "immediate" : deps.teardownMode?.() ?? "drain");
3055
+ }
3056
+ } };
3057
+ }
3058
+ //#endregion
3059
+ //#region src/session-decode/session-decode-read-gone.ts
3060
+ /**
3061
+ * A session-decode `toBuffer` the worker will never answer (D656).
3062
+ *
3063
+ * Named refusals after teardown (`SessionDecodeReadRefusedError`) and the
3064
+ * in-flight waiter rejection when the child exits mid-read. Live Mac bursts
3065
+ * (frameId 1–8 at session end) used to surface these as `runInference failed`.
3066
+ */
3067
+ function isSessionDecodeReadGone(err) {
3068
+ if (err instanceof SessionDecodeReadRefusedError) return true;
3069
+ const msg = err instanceof Error ? err.message : String(err);
3070
+ return msg.includes("worker already exited") || msg.includes("session killed — read requested after the worker was told to exit") || msg.includes("worker exited before toBuffer reply") || msg.includes("local frameRef miss:");
3071
+ }
3072
+ //#endregion
2242
3073
  //#region src/session-decode/local-frame-registry.ts
2243
3074
  var DEFAULT_MAX_FRAMES = 24;
2244
3075
  var DEFAULT_MAX_BYTES = 512 * 1024 * 1024;
@@ -2392,18 +3223,26 @@ var LocalFrameRegistry = class {
2392
3223
  };
2393
3224
  this.touch(entry);
2394
3225
  const geometry = resolveFrameViewGeometry(entry.image.width, entry.image.height, spec);
2395
- return {
2396
- kind: "view",
2397
- data: await entry.image.toBuffer({
2398
- crop: geometry.sourceCrop,
2399
- resize: geometry.output,
2400
- format: spec.format
2401
- }),
2402
- width: geometry.output.width,
2403
- height: geometry.output.height,
2404
- format: spec.format,
2405
- geometry
2406
- };
3226
+ try {
3227
+ return {
3228
+ kind: "view",
3229
+ data: await entry.image.toBuffer({
3230
+ crop: geometry.sourceCrop,
3231
+ resize: geometry.output,
3232
+ format: spec.format
3233
+ }),
3234
+ width: geometry.output.width,
3235
+ height: geometry.output.height,
3236
+ format: spec.format,
3237
+ geometry
3238
+ };
3239
+ } catch (err) {
3240
+ if (isSessionDecodeReadGone(err)) return {
3241
+ kind: "miss",
3242
+ reason: "stale-ref"
3243
+ };
3244
+ throw err;
3245
+ }
2407
3246
  }
2408
3247
  /** Oldest owner-only / queued entry (`refs === 1`). Consumer leases (`refs > 1`) stay. */
2409
3248
  findOwnerOnlyVictim() {
@@ -2850,4 +3689,4 @@ function resolveDefaultDetectionModel(backend, device, format, getStepDef = getS
2850
3689
  return getDefaultModelForFormatFromDef(def, format);
2851
3690
  }
2852
3691
  //#endregion
2853
- export { resolveModelForFormat as _, buildSubtreeCanRun as a, resolveModelDefaultConfidence as b, macroGateVerdict as c, resolveFrameViewGeometry as d, ALL_PIPELINE_STEPS as f, getStepDefinition as g, getStep as h, mergeCustomModels as i, rootObjectGate as l, getDefaultModelForFormat as m, resolveDefaultDetectionModel as n, placeDetailStep as o, ALL_STEPS as p, CustomModelCatalog as r, stepsForElectedPool as s, OBJECT_DETECTION_STEP_ID as t, localFrameRegistry as u, RETIRED_MODEL_IDS as v, landmarkPrecisionVerdict as y };
3692
+ export { resolveModelDefaultConfidence as S, getStep as _, buildSubtreeCanRun as a, RETIRED_MODEL_IDS as b, macroGateVerdict as c, isSessionDecodeReadGone as d, createSessionDecodeCoordinator as f, getDefaultModelForFormat as g, ALL_STEPS as h, mergeCustomModels as i, rootObjectGate as l, ALL_PIPELINE_STEPS as m, resolveDefaultDetectionModel as n, placeDetailStep as o, resolveFrameViewGeometry as p, CustomModelCatalog as r, stepsForElectedPool as s, OBJECT_DETECTION_STEP_ID as t, localFrameRegistry as u, getStepDefinition as v, landmarkPrecisionVerdict as x, resolveModelForFormat as y };