@camstack/addon-pipeline 1.2.93 → 1.2.94

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 (29) hide show
  1. package/dist/{addon-utils-CTRpEiSd.js → addon-utils-DnUCCZVx.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-D9To_r_d.js → dist-BbRv3bM2.js} +43 -19
  7. package/dist/{dist-6POVIYmC.mjs → dist-XbrYiMV3.mjs} +43 -19
  8. package/dist/{event-loop-stall-monitor-C-VvWOxS.mjs → event-loop-stall-monitor-CFWrOZ6G.mjs} +1 -1
  9. package/dist/{event-loop-stall-monitor-CGacbzXq.js → event-loop-stall-monitor-D9hqbc68.js} +1 -1
  10. package/dist/{lazy-sharp-C3zOnmiU.js → lazy-sharp-U0EtN7_C.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 +195 -14
  14. package/dist/pipeline-runner/index.mjs +194 -13
  15. package/dist/recorder/index.js +523 -65
  16. package/dist/recorder/index.mjs +522 -64
  17. package/dist/session-decode/decode-worker-child.js +4 -4
  18. package/dist/session-decode/decode-worker-child.mjs +3 -3
  19. package/dist/stream-broker/_stub.js +1 -1
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2woB6bY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BNOPhQ-y.mjs} +1 -1
  21. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DMyOrQHq.mjs +26 -0
  22. package/dist/stream-broker/{hostInit-VCURcZLt.mjs → hostInit-DCV2-mSi.mjs} +1 -1
  23. package/dist/stream-broker/index.js +1 -1
  24. package/dist/stream-broker/index.mjs +1 -1
  25. package/dist/stream-broker/remoteEntry.js +1 -1
  26. package/dist/{worker-protocol-Bq28qobd.js → worker-protocol-CNC-ZcU3.js} +1 -1
  27. package/dist/{worker-protocol-PzAKg8mC.mjs → worker-protocol-D0ewr67U.mjs} +1 -1
  28. package/package.json +3 -2
  29. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Dut56EeQ.mjs +0 -26
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-D9To_r_d.js");
1
+ const require_dist = require("./dist-BbRv3bM2.js");
2
2
  require("node:crypto");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_dist.__toESM(node_path, 1);
@@ -2,9 +2,9 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-D9To_r_d.js");
5
+ const require_dist = require("../dist-BbRv3bM2.js");
6
6
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
7
- const require_addon_utils = require("../addon-utils-CTRpEiSd.js");
7
+ const require_addon_utils = require("../addon-utils-DnUCCZVx.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_dist.__toESM(node_path);
10
10
  let node_fs = require("node:fs");
@@ -1,5 +1,5 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { D as audioAnalyzerCapability, E as audioAnalysisCapability, U as mapAudioLabelToMacro, at as errMsg, dt as BaseAddon, gt as hydrateSchema, h as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, s as DEFAULT_AUDIO_ANALYZER_CONFIG } from "../dist-6POVIYmC.mjs";
2
+ import { D as audioAnalyzerCapability, E as audioAnalysisCapability, U as mapAudioLabelToMacro, at as errMsg, dt as BaseAddon, gt as hydrateSchema, h as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, s as DEFAULT_AUDIO_ANALYZER_CONFIG } from "../dist-XbrYiMV3.mjs";
3
3
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
4
4
  import { i as downloadFile } from "../addon-utils-A2S9D7pu.mjs";
5
5
  import * as path$1 from "node:path";
@@ -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-D9To_r_d.js");
6
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-CGacbzXq.js");
7
- const require_lazy_sharp = require("../lazy-sharp-C3zOnmiU.js");
5
+ const require_dist = require("../dist-BbRv3bM2.js");
6
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-D9hqbc68.js");
7
+ const require_lazy_sharp = require("../lazy-sharp-U0EtN7_C.js");
8
8
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
9
- const require_addon_utils = require("../addon-utils-CTRpEiSd.js");
9
+ const require_addon_utils = require("../addon-utils-DnUCCZVx.js");
10
10
  let sharp = require("sharp");
11
11
  sharp = require_dist.__toESM(sharp);
12
12
  let node_child_process = require("node:child_process");
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { B as enumerateInferenceDevices, Et as array, Ft as union, It as EventCategory, J as pipelineExecutorCapability, L as detectionPipelineCapability, M as defaultDeviceFor$1, Mt as object, Pt as string, V as evaluateZoneRules, at as errMsg, bt as nodePin, d as DEVICE_BACKEND_TO_FORMAT, dt as BaseAddon, et as runtimeDevices$1, gt as hydrateSchema, ht as createEvent, rt as supportedRuntimes$1, t as APPLE_SA_TO_MACRO, w as YAMNET_TO_MACRO, wt as sleep, xt as parseJsonUnknown } from "../dist-6POVIYmC.mjs";
3
- import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-C-VvWOxS.mjs";
2
+ import { B as enumerateInferenceDevices, Et as array, Ft as union, It as EventCategory, J as pipelineExecutorCapability, L as detectionPipelineCapability, M as defaultDeviceFor$1, Mt as object, Pt as string, V as evaluateZoneRules, at as errMsg, bt as nodePin, d as DEVICE_BACKEND_TO_FORMAT, dt as BaseAddon, et as runtimeDevices$1, gt as hydrateSchema, ht as createEvent, rt as supportedRuntimes$1, t as APPLE_SA_TO_MACRO, w as YAMNET_TO_MACRO, wt as sleep, xt as parseJsonUnknown } from "../dist-XbrYiMV3.mjs";
3
+ import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-CFWrOZ6G.mjs";
4
4
  import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
5
5
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
6
6
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-A2S9D7pu.mjs";
@@ -21996,20 +21996,6 @@ var VectorStatsResultSchema = object({
21996
21996
  exact: boolean()
21997
21997
  });
21998
21998
  method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }), method(VectorQueryInputSchema, VectorQueryResultSchema), method(VectorGetInputSchema, VectorGetResultSchema), method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorStatsInputSchema, VectorStatsResultSchema);
21999
- /**
22000
- * `videoclips` — the unified, navigable-clip surface for a camera.
22001
- *
22002
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
22003
- * provider per device, substitutable. The DEFAULT provider (registered by
22004
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
22005
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
22006
- * is a time-WINDOW over existing footage, never a separate file. A camera that
22007
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
22008
- * wrapper provider for its device and serve its own clip catalog + URLs.
22009
- *
22010
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
22011
- * temporal overlap, so the API never decides `continuous` vs `events`.
22012
- */
22013
21999
  var ClipSchema = object({
22014
22000
  /** Opaque, provider-namespaced id. The default provider encodes the time
22015
22001
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -22035,12 +22021,41 @@ var ClipSchema = object({
22035
22021
  * of the event that owns `kind` (object > motion > audio). Do not extract
22036
22022
  * a keyframe from the recorded segments. Other providers (onboard, HKSV)
22037
22023
  * mint their own stills.
22024
+ *
22025
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
22026
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
22027
+ * Absent is meaningful — "this visit has no event still" — never "we did not
22028
+ * look". Stamping it from a URL template made 35% of one camera's clips point
22029
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
22030
+ * A read that FAILS drops the claim; it never invents it.
22038
22031
  */
22039
22032
  thumbnail: string().optional(),
22040
- /** Analytics event ids that overlap this visit. Empty on footage-only clips.
22041
- * The default provider's visit grain puts many motion heartbeats on one clip
22042
- * instead of minting one clip per marker. */
22033
+ /**
22034
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
22035
+ * can be decoded. Present whenever the visit came from recorded availability;
22036
+ * absent on a per-event padded window (there is no footage to promise).
22037
+ *
22038
+ * This is not a thumbnail and not a second byte path: it is the argument to
22039
+ * the recorder's existing still route. The surface — never the provider —
22040
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
22041
+ * contiguous range, not of the visit: a visit spans its holes by
22042
+ * construction, so a naive midpoint lands in dead air.
22043
+ */
22044
+ stillAtMs: number().optional(),
22045
+ /**
22046
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
22047
+ * first within kind (object → motion → audio), capped at
22048
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
22049
+ *
22050
+ * Bounded because it is not a payload the surface pages through: one visit on
22051
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
22052
+ * camera-day. Read {@link eventCount} for the true total.
22053
+ */
22043
22054
  eventIds: array(string()).optional(),
22055
+ /** How many analytics events actually overlap this visit. Differs from
22056
+ * `eventIds.length` exactly when the sample was capped — so a truncated
22057
+ * list is never mistaken for a quiet visit. */
22058
+ eventCount: number().int().nonnegative().optional(),
22044
22059
  /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
22045
22060
  * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
22046
22061
  * bar keeps showing them via `recording.getAvailability`. */
@@ -22591,7 +22606,14 @@ var SearchResultSchema = object({
22591
22606
  });
22592
22607
  var AutoUpdateSettingsSchema = object({
22593
22608
  channel: ChannelSchema,
22594
- intervalSeconds: number()
22609
+ intervalSeconds: number(),
22610
+ /**
22611
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
22612
+ * `channel`: the poller runs while auto-apply is `off`, because being told
22613
+ * about a publish and installing it are different decisions. Clamped
22614
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
22615
+ */
22616
+ updateCheckIntervalSeconds: number()
22595
22617
  });
22596
22618
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
22597
22619
  var RestartAddonResultSchema = unknown();
@@ -22732,7 +22754,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
22732
22754
  auth: "admin"
22733
22755
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
22734
22756
  channel: ChannelSchema,
22735
- intervalSeconds: number().min(300).max(86400).optional()
22757
+ intervalSeconds: number().min(300).max(86400).optional(),
22758
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
22759
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
22736
22760
  }), unknown(), {
22737
22761
  kind: "mutation",
22738
22762
  auth: "admin"
@@ -21973,20 +21973,6 @@ var VectorStatsResultSchema = object({
21973
21973
  exact: boolean()
21974
21974
  });
21975
21975
  method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }), method(VectorQueryInputSchema, VectorQueryResultSchema), method(VectorGetInputSchema, VectorGetResultSchema), method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }), method(VectorStatsInputSchema, VectorStatsResultSchema);
21976
- /**
21977
- * `videoclips` — the unified, navigable-clip surface for a camera.
21978
- *
21979
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
21980
- * provider per device, substitutable. The DEFAULT provider (registered by
21981
- * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
21982
- * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
21983
- * is a time-WINDOW over existing footage, never a separate file. A camera that
21984
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
21985
- * wrapper provider for its device and serve its own clip catalog + URLs.
21986
- *
21987
- * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
21988
- * temporal overlap, so the API never decides `continuous` vs `events`.
21989
- */
21990
21976
  var ClipSchema = object({
21991
21977
  /** Opaque, provider-namespaced id. The default provider encodes the time
21992
21978
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -22012,12 +21998,41 @@ var ClipSchema = object({
22012
21998
  * of the event that owns `kind` (object > motion > audio). Do not extract
22013
21999
  * a keyframe from the recorded segments. Other providers (onboard, HKSV)
22014
22000
  * mint their own stills.
22001
+ *
22002
+ * **VOUCHED, never fabricated.** A provider emits this only for an event it
22003
+ * has CONFIRMED owns at least one media row (its own, or its owning track's).
22004
+ * Absent is meaningful — "this visit has no event still" — never "we did not
22005
+ * look". Stamping it from a URL template made 35% of one camera's clips point
22006
+ * at a 404 (device 3836, 2026-08-17: 64 of 179 clips dead, 63 of them motion).
22007
+ * A read that FAILS drops the claim; it never invents it.
22015
22008
  */
22016
22009
  thumbnail: string().optional(),
22017
- /** Analytics event ids that overlap this visit. Empty on footage-only clips.
22018
- * The default provider's visit grain puts many motion heartbeats on one clip
22019
- * instead of minting one clip per marker. */
22010
+ /**
22011
+ * An instant INSIDE this visit's footage, hole-safe, where a recorded still
22012
+ * can be decoded. Present whenever the visit came from recorded availability;
22013
+ * absent on a per-event padded window (there is no footage to promise).
22014
+ *
22015
+ * This is not a thumbnail and not a second byte path: it is the argument to
22016
+ * the recorder's existing still route. The surface — never the provider —
22017
+ * decides whether to use it. It is the midpoint of the visit's LONGEST
22018
+ * contiguous range, not of the visit: a visit spans its holes by
22019
+ * construction, so a naive midpoint lands in dead air.
22020
+ */
22021
+ stillAtMs: number().optional(),
22022
+ /**
22023
+ * Analytics event ids that overlap this visit — a BOUNDED sample, newest
22024
+ * first within kind (object → motion → audio), capped at
22025
+ * {@link MAX_CLIP_EVENT_IDS}. Empty on footage-only clips.
22026
+ *
22027
+ * Bounded because it is not a payload the surface pages through: one visit on
22028
+ * device 615 carried 2 345 ids, and `eventIds` was 99% of a 153 KB
22029
+ * camera-day. Read {@link eventCount} for the true total.
22030
+ */
22020
22031
  eventIds: array(string()).optional(),
22032
+ /** How many analytics events actually overlap this visit. Differs from
22033
+ * `eventIds.length` exactly when the sample was capped — so a truncated
22034
+ * list is never mistaken for a quiet visit. */
22035
+ eventCount: number().int().nonnegative().optional(),
22021
22036
  /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
22022
22037
  * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
22023
22038
  * bar keeps showing them via `recording.getAvailability`. */
@@ -22568,7 +22583,14 @@ var SearchResultSchema = object({
22568
22583
  });
22569
22584
  var AutoUpdateSettingsSchema = object({
22570
22585
  channel: ChannelSchema,
22571
- intervalSeconds: number()
22586
+ intervalSeconds: number(),
22587
+ /**
22588
+ * Cadence of the "an update exists" POLLER, in seconds. Independent of
22589
+ * `channel`: the poller runs while auto-apply is `off`, because being told
22590
+ * about a publish and installing it are different decisions. Clamped
22591
+ * server-side to 900 s … 604800 s; defaults to 21600 s (6 h).
22592
+ */
22593
+ updateCheckIntervalSeconds: number()
22572
22594
  });
22573
22595
  var AddonAutoUpdateSchema = ChannelWithInheritSchema;
22574
22596
  var RestartAddonResultSchema = unknown();
@@ -22709,7 +22731,9 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
22709
22731
  auth: "admin"
22710
22732
  }), method(_void(), AutoUpdateSettingsSchema, { auth: "admin" }), method(object({
22711
22733
  channel: ChannelSchema,
22712
- intervalSeconds: number().min(300).max(86400).optional()
22734
+ intervalSeconds: number().min(300).max(86400).optional(),
22735
+ /** Availability-poll cadence; see AutoUpdateSettingsSchema. */
22736
+ updateCheckIntervalSeconds: number().min(900).max(604800).optional()
22713
22737
  }), unknown(), {
22714
22738
  kind: "mutation",
22715
22739
  auth: "admin"
@@ -1,4 +1,4 @@
1
- import { H as hfModelUrl, a as COCO_80_LABELS, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-6POVIYmC.mjs";
1
+ import { H as hfModelUrl, a as COCO_80_LABELS, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-XbrYiMV3.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-D9To_r_d.js");
1
+ const require_dist = require("./dist-BbRv3bM2.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
- const require_dist = require("./dist-D9To_r_d.js");
1
+ const require_dist = require("./dist-BbRv3bM2.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-D9To_r_d.js");
6
- const require_lazy_sharp = require("../lazy-sharp-C3zOnmiU.js");
5
+ const require_dist = require("../dist-BbRv3bM2.js");
6
+ const require_lazy_sharp = require("../lazy-sharp-U0EtN7_C.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 { G as motionDetectionCapability, V as evaluateZoneRules, dt as BaseAddon, gt as hydrateSchema, mt as DeviceType } from "../dist-6POVIYmC.mjs";
1
+ import { G as motionDetectionCapability, V as evaluateZoneRules, dt as BaseAddon, gt as hydrateSchema, mt as DeviceType } from "../dist-XbrYiMV3.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-D9To_r_d.js");
6
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-CGacbzXq.js");
7
- const require_worker_protocol = require("../worker-protocol-Bq28qobd.js");
5
+ const require_dist = require("../dist-BbRv3bM2.js");
6
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-D9hqbc68.js");
7
+ const require_worker_protocol = require("../worker-protocol-CNC-ZcU3.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
- const require_lazy_sharp = require("../lazy-sharp-C3zOnmiU.js");
9
+ const require_lazy_sharp = require("../lazy-sharp-U0EtN7_C.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);
@@ -134,6 +134,8 @@ function startGuardedSessionDecode(deps) {
134
134
  let stopPump = null;
135
135
  let release = null;
136
136
  let retryTimer = null;
137
+ /** The live-worker slot held by the RUNNING session, released on teardown. */
138
+ let workerLease = null;
137
139
  const delays = deps.acquireRetryDelaysMs && deps.acquireRetryDelaysMs.length > 0 ? deps.acquireRetryDelaysMs : DEFAULT_ACQUIRE_RETRY_DELAYS_MS;
138
140
  /** Resolves after the backoff for `attempt`, or immediately once aborted. */
139
141
  const waitBackoff = (attempt) => new Promise((resolve) => {
@@ -146,10 +148,19 @@ function startGuardedSessionDecode(deps) {
146
148
  });
147
149
  (async () => {
148
150
  for (let attempt = 0; !aborted; attempt++) {
151
+ let lease = null;
152
+ if (deps.admitWorker) {
153
+ lease = deps.admitWorker();
154
+ if (!lease) {
155
+ await waitBackoff(attempt);
156
+ continue;
157
+ }
158
+ }
149
159
  let acquired = null;
150
160
  try {
151
161
  acquired = await deps.acquire();
152
162
  } catch (err) {
163
+ lease?.release();
153
164
  (attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: acquire failed — retrying", { meta: {
154
165
  attempt: attempt + 1,
155
166
  error: require_dist.errMsg(err)
@@ -158,16 +169,19 @@ function startGuardedSessionDecode(deps) {
158
169
  continue;
159
170
  }
160
171
  if (aborted) {
172
+ lease?.release();
161
173
  if (acquired) acquired.release();
162
174
  return;
163
175
  }
164
176
  if (!acquired) {
177
+ lease?.release();
165
178
  (attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: no broker profile bound yet — retrying", { meta: { attempt: attempt + 1 } });
166
179
  await waitBackoff(attempt);
167
180
  continue;
168
181
  }
169
182
  if (attempt > 0) deps.logger.info("session-decode: acquire succeeded after retry", { meta: { attempts: attempt + 1 } });
170
183
  release = acquired.release;
184
+ workerLease = lease;
171
185
  stopPump = deps.startPump(acquired.source);
172
186
  return;
173
187
  }
@@ -181,6 +195,8 @@ function startGuardedSessionDecode(deps) {
181
195
  retryTimer = null;
182
196
  }
183
197
  stopPump?.();
198
+ workerLease?.release();
199
+ workerLease = null;
184
200
  if (release) release();
185
201
  };
186
202
  }
@@ -1394,7 +1410,9 @@ var RunSyntheticBenchInputSchema = require_dist.object({
1394
1410
  sessionId: require_dist.string().optional(),
1395
1411
  simulatePipeline: require_dist.boolean().optional(),
1396
1412
  nodeId: require_dist.string().optional(),
1397
- engine: BenchEngineChoiceSchema.optional()
1413
+ engine: BenchEngineChoiceSchema.optional(),
1414
+ deviceKey: require_dist.string().optional(),
1415
+ plane: require_dist._enum(["full", "frame"]).optional()
1398
1416
  });
1399
1417
  var TimingSplitSchema = require_dist.object({
1400
1418
  mean: require_dist.number(),
@@ -1449,6 +1467,48 @@ var pipelineRunnerBenchActions = require_dist.defineCustomActions({
1449
1467
  abortCapacityRamp: require_dist.customAction(AbortCapacityRampInputSchema, AbortCapacityRampResultSchema, { kind: "mutation" })
1450
1468
  });
1451
1469
  //#endregion
1470
+ //#region src/pipeline-runner/bench-dispatch.ts
1471
+ /**
1472
+ * Why the cached fast path is unavailable, or `null` if nothing blocks it.
1473
+ *
1474
+ * The fast path is `cacheFrameInPool` once + `inferCached` per call, and
1475
+ * NEITHER cap method takes a `deviceKey` or a `plane` — only `runPipeline`
1476
+ * does. Forwarding a device selector into it would scope the WARMUP
1477
+ * `runPipeline` to the chosen accelerator and leave every TIMED call on the
1478
+ * node's default pool: a measurement of the wrong device, reported as the
1479
+ * right one. Refusing the fast path costs a slower bench and buys a true
1480
+ * number.
1481
+ */
1482
+ function fastPathRefusalReason(input) {
1483
+ if (input.deviceKey) return `deviceKey '${input.deviceKey}' requested — inferCached has no device selector, so the timed calls would land on the node's default pool; using the runPipeline path instead`;
1484
+ if (input.plane) return `plane '${input.plane}' requested — inferCached has no plane selector; using the runPipeline path instead`;
1485
+ return null;
1486
+ }
1487
+ /**
1488
+ * The cached fast path is only for a plain, single-step, default-device,
1489
+ * default-plane inference bench.
1490
+ */
1491
+ function canUseCachedFastPath(isSingleStep, input) {
1492
+ return isSingleStep && !input.simulatePipeline && fastPathRefusalReason(input) === null;
1493
+ }
1494
+ /**
1495
+ * Build the `pipelineExecutor.runPipeline` argument for one bench call.
1496
+ *
1497
+ * Every optional is ABSENT when unset rather than present-and-undefined: an
1498
+ * omitted `deviceKey` means "the runner's default device" and an omitted
1499
+ * `plane` means the cap's `'full'` default, and the two are not the same as
1500
+ * an explicit `undefined` on a `.loose()` router input.
1501
+ */
1502
+ function buildBenchRunPipelineInput(req) {
1503
+ return {
1504
+ steps: [...req.steps],
1505
+ frame: req.frame,
1506
+ ...req.engine ? { engine: req.engine } : {},
1507
+ ...req.deviceKey ? { deviceKey: req.deviceKey } : {},
1508
+ ...req.plane ? { plane: req.plane } : {}
1509
+ };
1510
+ }
1511
+ //#endregion
1452
1512
  //#region src/pipeline-runner/capacity/capacity-runner.ts
1453
1513
  /**
1454
1514
  * Run ONE ramp step: `cameras` independent open-loop pacers for
@@ -3870,6 +3930,76 @@ var PipelineRunner = class {
3870
3930
  }
3871
3931
  };
3872
3932
  //#endregion
3933
+ //#region src/session-decode/live-worker-admission.ts
3934
+ /**
3935
+ * The node's live-decode-worker budget.
3936
+ *
3937
+ * One instance per runner addon. Not a semaphore: a semaphore's contract is
3938
+ * "wait", and waiting is the thing this deliberately does not do — see
3939
+ * {@link DEFAULT_MAX_LIVE_DECODE_WORKERS}.
3940
+ */
3941
+ var LiveDecodeWorkerAdmission = class {
3942
+ limit;
3943
+ logger;
3944
+ live = 0;
3945
+ constructor(options) {
3946
+ this.limit = Math.max(1, options.limit);
3947
+ this.logger = options.logger;
3948
+ }
3949
+ /** Workers currently holding a slot. */
3950
+ get inFlight() {
3951
+ return this.live;
3952
+ }
3953
+ get ceiling() {
3954
+ return this.limit;
3955
+ }
3956
+ /**
3957
+ * Apply an operator-changed ceiling.
3958
+ *
3959
+ * Lowering it never kills a running session — a decode worker torn down
3960
+ * mid-motion loses the event it was started for. It only stops the next
3961
+ * admission, so the count drains to the new ceiling as sessions end.
3962
+ */
3963
+ resize(limit) {
3964
+ this.limit = Math.max(1, limit);
3965
+ }
3966
+ /**
3967
+ * Take a slot, or refuse.
3968
+ *
3969
+ * A refusal is a WARN and carries `tags.deviceId`, because the only useful
3970
+ * form of this question is per-camera ("why did 617 not run when 615 did?").
3971
+ * A branch that drops work silently reads as "it never happened".
3972
+ */
3973
+ admit(deviceId, label) {
3974
+ if (this.live >= this.limit) {
3975
+ this.logger.warn("session-decode: REFUSED — node live decode-worker ceiling reached, will retry", {
3976
+ tags: { deviceId },
3977
+ meta: {
3978
+ label,
3979
+ live: this.live,
3980
+ ceiling: this.limit
3981
+ }
3982
+ });
3983
+ return null;
3984
+ }
3985
+ this.live += 1;
3986
+ this.logger.debug("session-decode: live decode-worker slot taken", {
3987
+ tags: { deviceId },
3988
+ meta: {
3989
+ label,
3990
+ live: this.live,
3991
+ ceiling: this.limit
3992
+ }
3993
+ });
3994
+ let released = false;
3995
+ return { release: () => {
3996
+ if (released) return;
3997
+ released = true;
3998
+ this.live -= 1;
3999
+ } };
4000
+ }
4001
+ };
4002
+ //#endregion
3873
4003
  //#region src/pipeline-runner/stateless-step.ts
3874
4004
  function modelServability(step, modelId, format) {
3875
4005
  const entry = step.models.find((m) => m.id === modelId);
@@ -4008,7 +4138,8 @@ var DEFAULT_CONFIG = {
4008
4138
  maxConcurrentInferences: 16,
4009
4139
  targetLoadPercent: 80,
4010
4140
  minThrottledFps: 1,
4011
- maxConcurrentDecodeSpawns: 3
4141
+ maxConcurrentDecodeSpawns: 3,
4142
+ maxLiveDecodeWorkers: 8
4012
4143
  };
4013
4144
  /**
4014
4145
  * Camera attachment record kept by the runner addon. Holds the original
@@ -4311,6 +4442,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4311
4442
  */
4312
4443
  spawnGate = null;
4313
4444
  /**
4445
+ * Per-node ceiling on decode workers ALIVE at once, sized to
4446
+ * `config.maxLiveDecodeWorkers`. The spawn gate above bounds how fast workers
4447
+ * START; this bounds how many EXIST — measured live at 1 -> 8 workers and a
4448
+ * ~4 GiB move in 8 seconds, with nothing stopping the 9th. `resize()`d live in
4449
+ * `onConfigChanged`. Null until `onInitialize`.
4450
+ */
4451
+ liveWorkers = null;
4452
+ /**
4314
4453
  * Node-level map from a retention-ring `FrameHandle` to the decode worker
4315
4454
  * still holding that frame's NATIVE surface — backs the `getNativeCrop` cap
4316
4455
  * so post-analysis can cut native-res crops (best-effort; a miss falls back
@@ -4534,6 +4673,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4534
4673
  }
4535
4674
  });
4536
4675
  this.spawnGate = new Semaphore(Math.max(1, this.config.maxConcurrentDecodeSpawns));
4676
+ this.liveWorkers = new LiveDecodeWorkerAdmission({
4677
+ limit: this.config.maxLiveDecodeWorkers,
4678
+ logger: this.ctx.logger
4679
+ });
4537
4680
  this.runner.timingSampler.setLogger(this.ctx.logger.child("timing"));
4538
4681
  this.runner.onDetectionStreamChange((deviceId, action) => {
4539
4682
  this.handleDetectionStreamChange(deviceId, action);
@@ -4694,8 +4837,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4694
4837
  children: s.children ?? []
4695
4838
  }));
4696
4839
  const enabledSteps = stepsToRun.filter((s) => s.enabled);
4697
- const useFastPath = enabledSteps.length === 1 && (!enabledSteps[0].children || enabledSteps[0].children.filter((c) => c.enabled).length === 0) && !input.simulatePipeline;
4840
+ const isSingleStep = enabledSteps.length === 1 && (!enabledSteps[0].children || enabledSteps[0].children.filter((c) => c.enabled).length === 0);
4841
+ const useFastPath = canUseCachedFastPath(isSingleStep, input);
4698
4842
  const rootStep = enabledSteps[0];
4843
+ const fastPathRefusal = isSingleStep ? fastPathRefusalReason(input) : null;
4844
+ if (fastPathRefusal) ctx.logger.info("synthetic bench: cached fast path declined", { meta: {
4845
+ reason: fastPathRefusal,
4846
+ node: benchNode
4847
+ } });
4699
4848
  const sharedFrame = {
4700
4849
  data: cached.data,
4701
4850
  format: cached.format,
@@ -4715,11 +4864,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4715
4864
  height: cached.height,
4716
4865
  format: cached.format
4717
4866
  }, pin)).frameId;
4718
- await api.pipelineExecutor.runPipeline.mutate({
4867
+ await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
4719
4868
  steps: stepsToRun,
4720
4869
  frame: sharedFrame,
4721
4870
  ...input.engine ? { engine: input.engine } : {}
4722
- }, pin);
4871
+ }) }, pin);
4723
4872
  const warmupCount = input.warmup ?? 1;
4724
4873
  for (let w = 0; w < warmupCount; w++) await api.pipelineExecutor.inferCached.mutate({
4725
4874
  stepId: rootStep.addonId,
@@ -4812,16 +4961,20 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
4812
4961
  }
4813
4962
  ctx.logger.info("synthetic bench: using full runPipeline path", { meta: {
4814
4963
  steps: enabledSteps.length,
4815
- simulatePipeline: !!input.simulatePipeline
4964
+ simulatePipeline: !!input.simulatePipeline,
4965
+ deviceKey: input.deviceKey ?? null,
4966
+ plane: input.plane ?? null
4816
4967
  } });
4817
4968
  let _callCount = 0;
4818
4969
  const callOnce = async () => {
4819
4970
  const t0 = performance.now();
4820
- const result = await api.pipelineExecutor.runPipeline.mutate({
4971
+ const result = await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
4821
4972
  steps: stepsToRun,
4822
4973
  frame: sharedFrame,
4823
- ...input.engine ? { engine: input.engine } : {}
4824
- }, pin);
4974
+ ...input.engine ? { engine: input.engine } : {},
4975
+ ...input.deviceKey ? { deviceKey: input.deviceKey } : {},
4976
+ ...input.plane ? { plane: input.plane } : {}
4977
+ }) }, pin);
4825
4978
  const wallMs = performance.now() - t0;
4826
4979
  const n = ++_callCount;
4827
4980
  if (n <= 20) ctx.logger.info("bench call trace", { meta: {
@@ -6198,6 +6351,18 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6198
6351
  * blocked by a missing gate. `label` distinguishes detect vs motion forks in
6199
6352
  * the logs; the device id is already tagged on `log`.
6200
6353
  */
6354
+ /**
6355
+ * The node's live-decode-worker admission, bound to one camera.
6356
+ *
6357
+ * `undefined` when the ceiling is not configured (pre-`onInitialize`), which
6358
+ * `startGuardedSessionDecode` reads as "no ceiling" — the historical
6359
+ * behaviour. Never a queue: see {@link LiveDecodeWorkerAdmission}.
6360
+ */
6361
+ buildLiveWorkerAdmit(deviceId, label) {
6362
+ const admission = this.liveWorkers;
6363
+ if (!admission) return void 0;
6364
+ return () => admission.admit(deviceId, label);
6365
+ }
6201
6366
  buildDecodeSpawnAcquire(log, label) {
6202
6367
  return async () => {
6203
6368
  const gate = this.spawnGate;
@@ -6225,6 +6390,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6225
6390
  const requestedAt = Date.now();
6226
6391
  const teardown = startGuardedSessionDecode({
6227
6392
  logger: log,
6393
+ admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "detect"),
6228
6394
  acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config), true),
6229
6395
  startPump: (source) => {
6230
6396
  const dialMs = Date.now() - requestedAt;
@@ -6297,6 +6463,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6297
6463
  if (!runner) return () => {};
6298
6464
  return startGuardedSessionDecode({
6299
6465
  logger: log,
6466
+ admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "motion"),
6300
6467
  acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
6301
6468
  startPump: (source) => {
6302
6469
  log.info("session-decode: motion routed through per-session decode worker (gray)", { meta: {
@@ -6853,6 +7020,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6853
7020
  }, OCCUPANCY_BURST_SAFETY_TIMEOUT_MS);
6854
7021
  teardown = startGuardedSessionDecode({
6855
7022
  logger: log,
7023
+ admitWorker: this.buildLiveWorkerAdmit(deviceId, "occupancy"),
6856
7024
  acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:occupancy:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
6857
7025
  startPump: (source) => startSessionDecodePump({
6858
7026
  fork: this.forkDecodeWorkerWithLease,
@@ -7026,6 +7194,17 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
7026
7194
  step: 1,
7027
7195
  default: DEFAULT_CONFIG.maxConcurrentDecodeSpawns,
7028
7196
  showValue: true
7197
+ },
7198
+ {
7199
+ type: "slider",
7200
+ key: "maxLiveDecodeWorkers",
7201
+ label: "Max live decode workers",
7202
+ description: "Hard ceiling on decode workers ALIVE at once on this node (~320 MB of RAM each). Unlike the spawn gate above, which only limits how fast workers START, this limits how many EXIST. A camera that does not fit is REFUSED and logged, then retried automatically as soon as a slot frees — it is never queued, because a queued live session would decode a moment that has already passed. Raise it if the node has RAM to spare; lower it if the host is memory-constrained.",
7203
+ min: 1,
7204
+ max: 32,
7205
+ step: 1,
7206
+ default: DEFAULT_CONFIG.maxLiveDecodeWorkers,
7207
+ showValue: true
7029
7208
  }
7030
7209
  ]
7031
7210
  }] });
@@ -7033,12 +7212,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
7033
7212
  async onConfigChanged() {
7034
7213
  this.runner?.updateLimits(this.config);
7035
7214
  this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
7215
+ this.liveWorkers?.resize(this.config.maxLiveDecodeWorkers);
7036
7216
  this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
7037
7217
  maxQueueDepth: this.config.maxQueueDepth,
7038
7218
  maxConcurrentInferences: this.config.maxConcurrentInferences,
7039
7219
  targetLoadPercent: this.config.targetLoadPercent,
7040
7220
  minThrottledFps: this.config.minThrottledFps,
7041
- maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns
7221
+ maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns,
7222
+ maxLiveDecodeWorkers: this.config.maxLiveDecodeWorkers
7042
7223
  } });
7043
7224
  }
7044
7225
  };