@camstack/addon-pipeline-orchestrator 1.2.295 → 1.2.296

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.
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- //#region ../types/dist/event-category-BGmz_oxW.mjs
5
+ //#region ../types/dist/event-category-B_0XEcLF.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -503,15 +503,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
503
503
  EventCategory["AgentWsDisconnected"] = "agent.ws.disconnected";
504
504
  EventCategory["AgentBackupActivated"] = "agent.backup.activated";
505
505
  EventCategory["OrchestrationSettingsUpdated"] = "orchestration.settings-updated";
506
- /**
507
- * Per-agent hwaccel preference changed (user override set, cleared, or
508
- * re-probed). Observability event — decoders pull the current pref at
509
- * `createSession`, so running sessions keep their current backend until
510
- * they rotate naturally (camera add/remove, stream restart). Future
511
- * work can wire a listener in stream-broker that force-rotates live
512
- * sessions; for now this event powers logs + admin-UI toast feedback.
513
- */
514
- EventCategory["PipelineAgentHwaccelChanged"] = "pipeline.agent-hwaccel-changed";
515
506
  EventCategory["MotionAnalysis"] = "detection.motion-analysis";
516
507
  /** All raw motion zones from CCL before minArea filter — for UI debug overlay. */
517
508
  EventCategory["MotionZonesRaw"] = "detection.motion-zones-raw";
@@ -5382,7 +5373,7 @@ var ZodIssueCode = {
5382
5373
  var ZodFirstPartyTypeKind;
5383
5374
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5384
5375
  //#endregion
5385
- //#region ../types/dist/sleep-DAJ9Er57.mjs
5376
+ //#region ../types/dist/sleep-BNeXenvr.mjs
5386
5377
  /**
5387
5378
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5388
5379
  * window to float samples (D455).
@@ -6781,32 +6772,12 @@ object({
6781
6772
  audioCodec: string().optional(),
6782
6773
  metadata: record(string(), unknown()).readonly().optional()
6783
6774
  });
6784
- var EncodedPacketSchema = object({
6785
- type: _enum(["video", "audio"]),
6786
- data: _instanceof(Uint8Array),
6787
- pts: number(),
6788
- dts: number(),
6789
- keyframe: boolean(),
6790
- codec: string()
6791
- });
6792
- var DecodedFrameSchema = object({
6793
- data: _instanceof(Uint8Array),
6794
- width: number(),
6795
- height: number(),
6796
- format: _enum([
6797
- "jpeg",
6798
- "rgb",
6799
- "bgr",
6800
- "yuv420",
6801
- "gray"
6802
- ]),
6803
- timestamp: number()
6804
- });
6805
6775
  /**
6806
6776
  * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
6807
6777
  * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
6808
- * interface in `interfaces/frame-handle.ts` field-for-field so a decoder cap
6809
- * method (`pullHandles`) can carry handles over tRPC / Moleculer.
6778
+ * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
6779
+ * (the runner's native-crop and pipeline-executor inputs) can carry handles
6780
+ * over tRPC / Moleculer.
6810
6781
  *
6811
6782
  * The `satisfies` assertion below pins this schema to the interface: if a
6812
6783
  * field is added to / removed from `FrameHandle` without a matching schema
@@ -6832,50 +6803,6 @@ var FrameHandleSchema = object({
6832
6803
  slotCount: number().int().positive()
6833
6804
  });
6834
6805
  /**
6835
- * Pixel format a frame-handle subscriber can request from the broker
6836
- * (Phase 5 / D9). The packed, raster subset of `FrameFormat` — the formats a
6837
- * `frameSink: 'shm'` decoder session can write into a `FrameRing` slot.
6838
- * `jpeg` is excluded: a variable-length stream is not a fixed-stride ring
6839
- * payload, and no decoded-frame consumer requests it over the shm plane.
6840
- */
6841
- var FrameHandleFormatSchema = _enum([
6842
- "rgb",
6843
- "bgr",
6844
- "yuv420",
6845
- "gray"
6846
- ]);
6847
- /**
6848
- * Input for `stream-broker.subscribeFrames` (Phase 5 / D9). A consumer asks the
6849
- * broker for a `FrameHandle` stream of one `format`; the broker maintains one
6850
- * shared-memory ring per `(brokerId, format)` actually requested, so a `gray`
6851
- * subscriber (motion) and an `rgb` subscriber (detection) each read the format
6852
- * they asked for with no broker-side conversion.
6853
- */
6854
- var SubscribeFramesInputSchema = object({
6855
- brokerId: string(),
6856
- format: FrameHandleFormatSchema,
6857
- /**
6858
- * Optional reader-side cadence hint in frames per second. The broker does
6859
- * NOT throttle — latest-wins ring reads drop frames implicitly for a slow
6860
- * consumer. The value is echoed back in `SubscribeFramesResult.maxFps` so
6861
- * the consumer can pace its own `pullFrameHandles` polling.
6862
- */
6863
- maxFps: number().positive().optional(),
6864
- /** Short caller-identity tag (`motion`, `detection`, …) for diagnostics. */
6865
- tag: string().optional()
6866
- });
6867
- /**
6868
- * Result of `stream-broker.subscribeFrames`. The consumer then polls
6869
- * `pullFrameHandles({ subscriptionId, maxCount })` and feeds each returned
6870
- * `FrameHandle` to a `FrameRingReader`.
6871
- */
6872
- var SubscribeFramesResultSchema = object({
6873
- /** Opaque id the consumer passes to `pullFrameHandles` / `unsubscribeFrames`. */
6874
- subscriptionId: string(),
6875
- /** Reader-side cadence hint (frames/s) — echoes `SubscribeFramesInput.maxFps`. */
6876
- maxFps: number().nonnegative()
6877
- });
6878
- /**
6879
6806
  * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
6880
6807
  * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
6881
6808
  * sample bytes plus the track parameters a consumer needs to interpret them.
@@ -6883,8 +6810,8 @@ var SubscribeFramesResultSchema = object({
6883
6810
  * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
6884
6811
  * Float32 PCM), so unlike video frames they ship their bytes INLINE over
6885
6812
  * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
6886
- * (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
6887
- * / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
6813
+ * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
6814
+ * it.
6888
6815
  */
6889
6816
  var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
6890
6817
  var DecodedAudioChunkSchema = object({
@@ -6902,11 +6829,10 @@ var DecodedAudioChunkSchema = object({
6902
6829
  format: AudioChunkFormatSchema.optional()
6903
6830
  });
6904
6831
  /**
6905
- * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
6906
- * handle-free, ship-bytes-inline analogue of `subscribeFrames`: a consumer
6907
- * asks the broker for the decoded audio-chunk stream of one broker; the
6908
- * broker keeps a per-subscription bounded FIFO queue the consumer drains via
6909
- * `pullAudioChunks`.
6832
+ * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
6833
+ * its bytes inline: a consumer asks the broker for the decoded audio-chunk
6834
+ * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
6835
+ * the consumer drains via `pullAudioChunks`.
6910
6836
  */
6911
6837
  var SubscribeAudioChunksInputSchema = object({
6912
6838
  brokerId: string(),
@@ -6937,9 +6863,7 @@ var BrokerStatsSchema = object({
6937
6863
  "stopped"
6938
6864
  ]),
6939
6865
  inputFps: number(),
6940
- decodeFps: number(),
6941
6866
  encodedSubscribers: number(),
6942
- decodedSubscribers: number(),
6943
6867
  uptimeMs: number(),
6944
6868
  bitrateKbps: number(),
6945
6869
  idrIntervalMs: number(),
@@ -6952,14 +6876,6 @@ var BrokerStatsSchema = object({
6952
6876
  preBufferMs: number(),
6953
6877
  preBufferPackets: number(),
6954
6878
  /**
6955
- * Moleculer node id of the decoder provider currently servicing this
6956
- * stream's decoded subscribers. `null` until the deferred decoder is
6957
- * created (no decoded clients yet, or codec not detected). Surfaces the
6958
- * runtime decoder placement so the UI can show "Decoder: <agent>" without
6959
- * a separate cap call per broker.
6960
- */
6961
- decoderNodeId: string().nullable(),
6962
- /**
6963
6879
  * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
6964
6880
  * when the stream has no audio track or the broker is in cold start.
6965
6881
  * `supported = false` means the codec was detected but the local
@@ -11297,70 +11213,6 @@ var StorageLocationDeclarationSchema = object({
11297
11213
  "backup"
11298
11214
  ]).optional()
11299
11215
  });
11300
- var DecoderStatsSchema = object({
11301
- inputFps: number(),
11302
- outputFps: number(),
11303
- avgDecodeTimeMs: number(),
11304
- droppedFrames: number(),
11305
- /**
11306
- * Pull-mode adaptive-fps telemetry (optional — only pull sessions run the
11307
- * lag-driven controller; push sessions omit these). `lagMs` is the EWMA of
11308
- * the decoder's real-time drift (rising = falling behind live); `adaptiveFps`
11309
- * is the current lag-throttled emit rate (≤ `effectiveFps` ceiling).
11310
- */
11311
- lagMs: number().optional(),
11312
- effectiveFps: number().optional(),
11313
- adaptiveFps: number().optional()
11314
- });
11315
- var DecoderSessionConfigSchema = object({
11316
- codec: string(),
11317
- maxFps: number().default(0),
11318
- outputFormat: _enum([
11319
- "jpeg",
11320
- "rgb",
11321
- "bgr",
11322
- "yuv420",
11323
- "gray"
11324
- ]).default("jpeg"),
11325
- scale: number().default(1),
11326
- width: number().optional(),
11327
- height: number().optional(),
11328
- /**
11329
- * Identifier of the camera this decoder session serves. Optional
11330
- * because the cap is generic (any caller could request decode), but
11331
- * stream-broker passes it so decoder logs include `deviceId` for
11332
- * per-camera filtering when diagnosing failures (e.g. node-av
11333
- * sendPacket errors on a single hung camera).
11334
- */
11335
- deviceId: number().int().nonnegative().optional(),
11336
- /**
11337
- * Free-form tag for log scoping. Stream-broker uses
11338
- * `broker:<deviceId>/<profile>`. Decoder session logger surfaces it
11339
- * on every line so `grep tag=broker:5/high` filters one camera
11340
- * profile cleanly.
11341
- */
11342
- tag: string().optional(),
11343
- /**
11344
- * Where the session delivers decoded frames (Phase 5 / D9):
11345
- *
11346
- * - `'callback'` (default) — the legacy pixel path: decoded frames are
11347
- * buffered as `DecodedFrame`s and drained via `pullFrames`.
11348
- * - `'shm'` — the shared-memory frame plane: decoded frames are written
11349
- * into an OS shared-memory ring and drained as zero-pixel
11350
- * `FrameHandle`s via `pullHandles`. A session is one mode or the
11351
- * other — `pullFrames` returns nothing for an `'shm'` session and
11352
- * `pullHandles` returns nothing for a `'callback'` session.
11353
- */
11354
- frameSink: _enum(["callback", "shm"]).default("callback"),
11355
- /**
11356
- * Per-camera decoder DEBUG facility. When `true`, a pull-mode session emits
11357
- * a throttled (~1Hz) structured `decoder debug` line (effective/adaptive fps,
11358
- * real-time lag, dropped-frame delta, avg decode time, hwaccel). Mirrors the
11359
- * stream-broker's `streamingDebug` gate — off by default so production logs
11360
- * stay quiet and the emit path pays zero per-frame cost when disabled.
11361
- */
11362
- debug: boolean().optional()
11363
- });
11364
11216
  /**
11365
11217
  * Per-component log CHANNELS — the gate a hot path consults, and the registry
11366
11218
  * an addon declares its channels in.
@@ -13564,13 +13416,6 @@ var BrokerRtspClientSchema = object({
13564
13416
  lastRtpAt: number(),
13565
13417
  bytesSent: number()
13566
13418
  });
13567
- var BrokerDecodedClientSchema = object({
13568
- tag: string(),
13569
- subscribedAt: number(),
13570
- maxFps: number(),
13571
- framesDelivered: number(),
13572
- framesDropped: number()
13573
- });
13574
13419
  var BrokerAudioClientSchema = object({
13575
13420
  tag: string(),
13576
13421
  subscribedAt: number(),
@@ -13662,7 +13507,6 @@ var BrokerEncodedClientSchema = object({
13662
13507
  });
13663
13508
  var BrokerClientsSchema = object({
13664
13509
  rtsp: array(BrokerRtspClientSchema).readonly(),
13665
- decoded: array(BrokerDecodedClientSchema).readonly(),
13666
13510
  audio: array(BrokerAudioClientSchema).readonly(),
13667
13511
  encoded: array(BrokerEncodedClientSchema).readonly(),
13668
13512
  pipeClients: number(),
@@ -14031,11 +13875,7 @@ method(object({
14031
13875
  auth: "admin"
14032
13876
  }), method(object({ brokerId: string() }), BrokerClientsSchema), method(object({
14033
13877
  brokerId: string(),
14034
- channel: _enum([
14035
- "rtsp",
14036
- "decoded",
14037
- "audio"
14038
- ]),
13878
+ channel: _enum(["rtsp", "audio"]),
14039
13879
  handle: string()
14040
13880
  }), object({ killed: boolean() }), {
14041
13881
  kind: "mutation",
@@ -14070,10 +13910,7 @@ method(object({
14070
13910
  }), method(SubscribeAudioChunksInputSchema, SubscribeAudioChunksResultSchema, { kind: "mutation" }), method(object({
14071
13911
  subscriptionId: string(),
14072
13912
  maxCount: number().int().positive().default(8)
14073
- }), array(DecodedAudioChunkSchema).readonly()), method(object({ subscriptionId: string() }), object({ released: boolean() }), { kind: "mutation" }), method(SubscribeFramesInputSchema, SubscribeFramesResultSchema, { kind: "mutation" }), method(object({
14074
- subscriptionId: string(),
14075
- maxCount: number().int().positive().default(4)
14076
- }), array(FrameHandleSchema).readonly()), method(object({ subscriptionId: string() }), object({ released: boolean() }), { kind: "mutation" }), method(object({
13913
+ }), array(DecodedAudioChunkSchema).readonly()), method(object({ subscriptionId: string() }), object({ released: boolean() }), { kind: "mutation" }), method(object({
14077
13914
  brokerId: string(),
14078
13915
  seconds: number().min(0).max(30)
14079
13916
  }), _void(), {
@@ -15230,54 +15067,12 @@ id: union([string(), number()]) }), {
15230
15067
  kind: "mutation",
15231
15068
  auth: "admin"
15232
15069
  });
15233
- /**
15234
- * shm ring usage stats for a `frameSink: 'shm'` decoder session —
15235
- * exposed via `decoder.getShmStats` so downstream consumers can
15236
- * observe ring pressure (slot count, byte budget, hit/miss ratio).
15237
- */
15238
- var ShmRingStatsSchema = object({
15239
- sessionId: string(),
15240
- slotCount: number().int(),
15241
- slotByteLength: number().int(),
15242
- segmentBytes: number().int(),
15243
- budgetMb: number().int(),
15244
- framesWritten: number().int(),
15245
- getFrameHits: number().int(),
15246
- getFrameMisses: number().int()
15247
- });
15248
- method(object({ codec: string() }), boolean()), method(_void(), object({
15070
+ method(_void(), object({
15249
15071
  id: string(),
15250
15072
  name: string(),
15251
- isPullMode: boolean().optional(),
15252
- priority: number().optional(),
15253
15073
  hwaccel: string().optional(),
15254
15074
  probedBestHwaccel: string().optional()
15255
- })), method(DecoderSessionConfigSchema, object({
15256
- sessionId: string(),
15257
- nodeId: string()
15258
- })), method(object({ sessionId: string() }), _void()), method(object({
15259
- sessionId: string(),
15260
- packet: EncodedPacketSchema
15261
- }), _void()), method(object({
15262
- sessionId: string(),
15263
- url: string()
15264
- }), _void()), method(object({
15265
- sessionId: string(),
15266
- maxCount: number().default(1),
15267
- waitMs: number().optional()
15268
- }), array(DecodedFrameSchema)), method(object({
15269
- sessionId: string(),
15270
- maxCount: number().default(1),
15271
- waitMs: number().optional()
15272
- }), array(FrameHandleSchema)), method(object({ handle: FrameHandleSchema }), DecodedFrameSchema.nullable()), method(object({ sessionId: string() }), ShmRingStatsSchema.nullable()), method(object({
15273
- sessionId: string(),
15274
- config: DecoderSessionConfigSchema.partial()
15275
- }), _void()), method(object({ sessionId: string() }), DecoderStatsSchema), method(_void(), array(object({
15276
- sessionId: string(),
15277
- codec: string(),
15278
- outputFormat: string(),
15279
- createdAtMs: number()
15280
- })).readonly()), method(_void(), object({ backend: string() }), {
15075
+ })), method(_void(), object({ backend: string() }), {
15281
15076
  kind: "mutation",
15282
15077
  auth: "admin"
15283
15078
  });
@@ -22347,8 +22142,8 @@ function interpretStoredClusterModel(raw) {
22347
22142
  var CLUSTER_STEP_SETTING_FIELDS = [{
22348
22143
  stepId: "face-embedding",
22349
22144
  key: "minLandmarkFaceSize",
22350
- label: "Min face size for recognition (detection px)",
22351
- description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
22145
+ label: "Min face size to embed (detection-frame px)",
22146
+ description: "Faces smaller than this in the small frame the detector sees are not embedded, so they can never be recognised. Below it the face cannot be aligned reliably and a bad embedding matches everyone. Same value on every node. 0 = no limit.",
22352
22147
  type: "slider",
22353
22148
  min: 0,
22354
22149
  max: 64,
@@ -26480,14 +26275,15 @@ var RunnerLocalLoadSchema = object({
26480
26275
  * Per-inference-device live load (multi-device C4). One entry per deviceKey
26481
26276
  * this runner currently has attached cameras on, so the orchestrator's second
26482
26277
  * `balance()` pass (over a node's devices) weights on real per-pool session
26483
- * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
26484
- * per device is 0 until the pool backlog gets a public accessor (follow-up).
26278
+ * counts. Empty on single-device / pre-multi-device runners. There is no
26279
+ * per-device queue depth: the inference pool's backlog has no accessor, and
26280
+ * a field that always read 0 was removed rather than shown as a measurement
26281
+ * (D762).
26485
26282
  */
26486
26283
  devices: array(object({
26487
26284
  deviceKey: string(),
26488
26285
  backend: string(),
26489
- attachedCameras: number(),
26490
- queueDepthTotal: number()
26286
+ attachedCameras: number()
26491
26287
  })).default([]),
26492
26288
  /** Hardware capability flags reported by this node. */
26493
26289
  hardware: object({
@@ -26810,7 +26606,10 @@ var PipelineAssignmentSchema = object({
26810
26606
  });
26811
26607
  /**
26812
26608
  * Per-agent load summary surfaced to the load balancer + dashboards.
26813
- * Aggregated from each runner's `getLocalLoad` cap call.
26609
+ * Aggregated from each runner's `getLocalLoad` cap call. In the
26610
+ * `getAgentLoad` report, a single-device node's default-pool sessions (no
26611
+ * `deviceKey`) are credited to its one eligible device in `load.devices` —
26612
+ * the default pool IS that device (D745); the balancer's census stays raw.
26814
26613
  */
26815
26614
  var AgentLoadSummarySchema = object({
26816
26615
  nodeId: string(),
@@ -26899,8 +26698,14 @@ var CameraBrokerStatusSchema = object({
26899
26698
  width: number(),
26900
26699
  height: number(),
26901
26700
  subscribers: number(),
26902
- inFps: number(),
26903
- outFps: number()
26701
+ /**
26702
+ * Encoded packets/s the broker receives from the camera. null when the
26703
+ * broker's stats read failed — unknown, never 0 (a 0 here reads as a stall).
26704
+ * There is no output rate: decoding runs in the session decode workers, not
26705
+ * in the broker, so the broker's own decoded-frame rate is 0 on every live
26706
+ * camera and is not served (D758).
26707
+ */
26708
+ inFps: number().nullable()
26904
26709
  })).readonly(),
26905
26710
  webrtcSessions: number(),
26906
26711
  rtspRestream: boolean()
@@ -29989,8 +29794,6 @@ var WebrtcStreamChoiceSchema = object({
29989
29794
  ]).nullable(),
29990
29795
  /** Input FPS from the camera (null for adaptive). */
29991
29796
  inputFps: number().nullable(),
29992
- /** Decoded FPS (null for adaptive). */
29993
- decodeFps: number().nullable(),
29994
29797
  /** Bitrate in kbps (null for adaptive). */
29995
29798
  bitrateKbps: number().nullable()
29996
29799
  });
@@ -40236,90 +40039,18 @@ Object.freeze({
40236
40039
  addonId: null,
40237
40040
  access: "create"
40238
40041
  },
40239
- "decoder.createSession": {
40240
- capName: "decoder",
40241
- capScope: "system",
40242
- addonId: null,
40243
- access: "view"
40244
- },
40245
- "decoder.destroySession": {
40246
- capName: "decoder",
40247
- capScope: "system",
40248
- addonId: null,
40249
- access: "delete"
40250
- },
40251
- "decoder.getFrame": {
40252
- capName: "decoder",
40253
- capScope: "system",
40254
- addonId: null,
40255
- access: "view"
40256
- },
40257
40042
  "decoder.getInfo": {
40258
40043
  capName: "decoder",
40259
40044
  capScope: "system",
40260
40045
  addonId: null,
40261
40046
  access: "view"
40262
40047
  },
40263
- "decoder.getShmStats": {
40264
- capName: "decoder",
40265
- capScope: "system",
40266
- addonId: null,
40267
- access: "view"
40268
- },
40269
- "decoder.getStats": {
40270
- capName: "decoder",
40271
- capScope: "system",
40272
- addonId: null,
40273
- access: "view"
40274
- },
40275
- "decoder.listActiveSessions": {
40276
- capName: "decoder",
40277
- capScope: "system",
40278
- addonId: null,
40279
- access: "view"
40280
- },
40281
- "decoder.openStream": {
40282
- capName: "decoder",
40283
- capScope: "system",
40284
- addonId: null,
40285
- access: "view"
40286
- },
40287
- "decoder.pullFrames": {
40288
- capName: "decoder",
40289
- capScope: "system",
40290
- addonId: null,
40291
- access: "view"
40292
- },
40293
- "decoder.pullHandles": {
40294
- capName: "decoder",
40295
- capScope: "system",
40296
- addonId: null,
40297
- access: "view"
40298
- },
40299
- "decoder.pushPacket": {
40300
- capName: "decoder",
40301
- capScope: "system",
40302
- addonId: null,
40303
- access: "view"
40304
- },
40305
40048
  "decoder.reprobeHwaccel": {
40306
40049
  capName: "decoder",
40307
40050
  capScope: "system",
40308
40051
  addonId: null,
40309
40052
  access: "create"
40310
40053
  },
40311
- "decoder.supportsCodec": {
40312
- capName: "decoder",
40313
- capScope: "system",
40314
- addonId: null,
40315
- access: "view"
40316
- },
40317
- "decoder.updateConfig": {
40318
- capName: "decoder",
40319
- capScope: "system",
40320
- addonId: null,
40321
- access: "view"
40322
- },
40323
40054
  "detectionPipeline.applyDeviceSettingsPatch": {
40324
40055
  capName: "detection-pipeline",
40325
40056
  capScope: "device",
@@ -45366,12 +45097,6 @@ Object.freeze({
45366
45097
  addonId: null,
45367
45098
  access: "view"
45368
45099
  },
45369
- "streamBroker.pullFrameHandles": {
45370
- capName: "stream-broker",
45371
- capScope: "system",
45372
- addonId: null,
45373
- access: "view"
45374
- },
45375
45100
  "streamBroker.regenerateRtspToken": {
45376
45101
  capName: "stream-broker",
45377
45102
  capScope: "system",
@@ -45438,12 +45163,6 @@ Object.freeze({
45438
45163
  addonId: null,
45439
45164
  access: "create"
45440
45165
  },
45441
- "streamBroker.subscribeFrames": {
45442
- capName: "stream-broker",
45443
- capScope: "system",
45444
- addonId: null,
45445
- access: "create"
45446
- },
45447
45166
  "streamBroker.unassignProfile": {
45448
45167
  capName: "stream-broker",
45449
45168
  capScope: "system",
@@ -45456,12 +45175,6 @@ Object.freeze({
45456
45175
  addonId: null,
45457
45176
  access: "create"
45458
45177
  },
45459
- "streamBroker.unsubscribeFrames": {
45460
- capName: "stream-broker",
45461
- capScope: "system",
45462
- addonId: null,
45463
- access: "create"
45464
- },
45465
45178
  "streamCatalog.getCatalog": {
45466
45179
  capName: "stream-catalog",
45467
45180
  capScope: "device",
@@ -48003,11 +47716,6 @@ Object.freeze({
48003
47716
  form: "single",
48004
47717
  optional: false
48005
47718
  }],
48006
- "decoder.createSession": [{
48007
- name: "deviceId",
48008
- form: "single",
48009
- optional: true
48010
- }],
48011
47719
  "detectionPipeline.applyDeviceSettingsPatch": [{
48012
47720
  name: "deviceId",
48013
47721
  form: "single",
@@ -54724,6 +54432,40 @@ var LoadShedController = class LoadShedController {
54724
54432
  }
54725
54433
  };
54726
54434
  //#endregion
54435
+ //#region src/default-pool-attribution.ts
54436
+ /** The backend prefix of a `<backend>:<device>` key (bare `cpu` → `cpu`). */
54437
+ function backendOf(deviceKey) {
54438
+ const colon = deviceKey.indexOf(":");
54439
+ return colon >= 0 ? deviceKey.slice(0, colon) : deviceKey;
54440
+ }
54441
+ /**
54442
+ * `load` with its unattributed sessions credited to `eligibleKeys[0]` when that
54443
+ * is the node's ONLY eligible device; `load` itself (same reference) otherwise.
54444
+ * `eligibleKeys` null = the eligible set is not known.
54445
+ */
54446
+ function attributeDefaultPoolSessions(load, eligibleKeys) {
54447
+ if (eligibleKeys === null || eligibleKeys.length !== 1) return load;
54448
+ const onlyKey = eligibleKeys[0];
54449
+ if (onlyKey === void 0) return load;
54450
+ const attributed = load.devices.reduce((sum, d) => sum + d.attachedCameras, 0);
54451
+ const unattributed = load.attachedCameras - attributed;
54452
+ if (unattributed <= 0) return load;
54453
+ const existing = load.devices.find((d) => d.deviceKey === onlyKey);
54454
+ const credited = existing ? {
54455
+ ...existing,
54456
+ attachedCameras: existing.attachedCameras + unattributed
54457
+ } : {
54458
+ deviceKey: onlyKey,
54459
+ backend: backendOf(onlyKey),
54460
+ attachedCameras: unattributed
54461
+ };
54462
+ const devices = [...load.devices.filter((d) => d.deviceKey !== onlyKey), credited].toSorted((a, b) => a.deviceKey.localeCompare(b.deviceKey));
54463
+ return {
54464
+ ...load,
54465
+ devices
54466
+ };
54467
+ }
54468
+ //#endregion
54727
54469
  //#region src/dispatch-timing.ts
54728
54470
  /**
54729
54471
  * How long the placement census waits for ONE runner's `getLocalCameras`
@@ -54790,6 +54532,25 @@ var AgentLoadService = class AgentLoadService {
54790
54532
  getAgentLoadSummaries() {
54791
54533
  return [...this.cachedAgentLoad.values()];
54792
54534
  }
54535
+ /**
54536
+ * The cached summaries as the load REPORT (`getAgentLoad`) carries them: a
54537
+ * single-device node's default-pool sessions credited to its one device
54538
+ * (`default-pool-attribution.ts`). The balancer's census
54539
+ * ({@link collectAgentLoad}) stays the runner's raw answer.
54540
+ */
54541
+ async reportedAgentLoad() {
54542
+ const resolve = this.deps.eligibleDeviceKeys;
54543
+ const summaries = this.getAgentLoadSummaries();
54544
+ if (resolve === void 0) return summaries;
54545
+ return Promise.all(summaries.map(async (summary) => {
54546
+ const eligible = await resolve(summary.nodeId).catch(() => null);
54547
+ const load = attributeDefaultPoolSessions(summary.load, eligible);
54548
+ return load === summary.load ? summary : {
54549
+ ...summary,
54550
+ load
54551
+ };
54552
+ }));
54553
+ }
54793
54554
  cachedAgentLoadNodeIds() {
54794
54555
  return [...this.cachedAgentLoad.keys()];
54795
54556
  }
@@ -57237,8 +56998,7 @@ function mapBroker(brokerResult) {
57237
56998
  width: p.width,
57238
56999
  height: p.height,
57239
57000
  subscribers: p.subscribers,
57240
- inFps: p.inFps,
57241
- outFps: p.outFps
57001
+ inFps: p.inFps
57242
57002
  })),
57243
57003
  webrtcSessions: brokerResult.webrtcSessions,
57244
57004
  rtspRestream: brokerResult.rtspRestream
@@ -57625,11 +57385,9 @@ var CameraStatusService = class {
57625
57385
  * `allSlotsFetch` (fetched once by the caller) to avoid a second
57626
57386
  * round-trip. Per-slot `getBrokerStats` + `listClients` are fetched in
57627
57387
  * parallel. WebRTC sessions are counted by filtering `encoded[]` entries
57628
- * whose `attribution.kind` is one of the `WEBRTC_KINDS`. Also captures the
57629
- * broker's actual decode-session node into `liveDecoder` (T6) — the first
57630
- * slot that reports one wins.
57388
+ * whose `attribution.kind` is one of the `WEBRTC_KINDS`.
57631
57389
  */
57632
- buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, sink) {
57390
+ buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, sink) {
57633
57391
  if (!api || !allSlotsFetch) return Promise.resolve(null);
57634
57392
  return this.boundedStage(allSlotsFetch.then(async (slots) => {
57635
57393
  const deviceSlots = slots.filter((s) => s.deviceId === deviceId);
@@ -57646,19 +57404,16 @@ var CameraStatusService = class {
57646
57404
  clients
57647
57405
  };
57648
57406
  })), pass.rtspEntries(api, sourceNodeId).catch(() => null)]);
57649
- const profileDetails = statsAndClients.map(({ slot, stats, clients }) => ({
57650
- profile: slot.profile,
57651
- status: slot.status,
57652
- codec: stats?.codec ?? slot.codec ?? "",
57653
- width: slot.resolution?.width ?? 0,
57654
- height: slot.resolution?.height ?? 0,
57655
- subscribers: clients?.encodedSubscribers ?? 0,
57656
- inFps: stats?.inputFps ?? 0,
57657
- outFps: stats?.decodeFps ?? 0
57658
- }));
57659
- for (const { stats } of statsAndClients) if (liveDecoder.nodeId === null && typeof stats?.decoderNodeId === "string") liveDecoder.nodeId = stats.decoderNodeId;
57660
57407
  return {
57661
- profiles: profileDetails,
57408
+ profiles: statsAndClients.map(({ slot, stats, clients }) => ({
57409
+ profile: slot.profile,
57410
+ status: slot.status,
57411
+ codec: stats?.codec ?? slot.codec ?? "",
57412
+ width: slot.resolution?.width ?? 0,
57413
+ height: slot.resolution?.height ?? 0,
57414
+ subscribers: clients?.encodedSubscribers ?? 0,
57415
+ inFps: stats?.inputFps ?? null
57416
+ })),
57662
57417
  webrtcSessions: statsAndClients.reduce((total, { clients }) => {
57663
57418
  if (!clients) return total;
57664
57419
  return total + clients.encoded.filter((c) => WEBRTC_KINDS.has(c.attribution.kind)).length;
@@ -57852,10 +57607,9 @@ var CameraStatusService = class {
57852
57607
  const api = this.deps.api();
57853
57608
  const degradations = { entries: [] };
57854
57609
  const { detectionNodeId, sourceNodeId, pinned, detectionReason, audioNodeId, audioPinned } = this.buildAssignmentContext(deviceId);
57855
- const liveDecoder = { nodeId: null };
57856
57610
  const allSlotsFetch = api ? pass.profileSlots(api, sourceNodeId) : null;
57857
57611
  const sourceFetch = this.buildSourceStage(api, allSlotsFetch, deviceId, degradations);
57858
- const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, degradations);
57612
+ const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, degradations);
57859
57613
  const decoderFetch = this.buildDecoderStage(detectionNodeId);
57860
57614
  const motionResult = this.buildMotionStage(deviceId);
57861
57615
  const detectionFetch = this.buildDetectionStage(api, detectionNodeId, deviceId, pass, degradations);
@@ -57870,11 +57624,10 @@ var CameraStatusService = class {
57870
57624
  switchesFetch
57871
57625
  ]);
57872
57626
  const audioResult = this.buildAudioStage(audioNodeId, switchedOff);
57873
- const liveDecoderNodeId = brokerResult !== null ? liveDecoder.nodeId : null;
57874
- const decoderNodeId = liveDecoderNodeId ?? detectionNodeId;
57627
+ const decoderNodeId = detectionNodeId;
57875
57628
  const reasons = {
57876
57629
  detection: detectionReason,
57877
- decoder: liveDecoderNodeId !== null ? "session" : decoderNodeId !== null ? "co-located" : void 0,
57630
+ decoder: decoderNodeId !== null ? "co-located" : void 0,
57878
57631
  audio: audioPinned ? "manual" : void 0
57879
57632
  };
57880
57633
  return composeCameraStatus({
@@ -60111,7 +59864,7 @@ function deviceAsLoad(deviceKey, load) {
60111
59864
  activeCameras: 0,
60112
59865
  avgInferenceFps: 0,
60113
59866
  avgInferenceTimeMs: 0,
60114
- queueDepthTotal: load?.queueDepthTotal ?? 0,
59867
+ queueDepthTotal: 0,
60115
59868
  devices: [],
60116
59869
  hardware: { hasGpu: false }
60117
59870
  };
@@ -61091,8 +60844,7 @@ function tallyLoad(load, nodeDelta, perDevice) {
61091
60844
  }), ...[...perDevice ?? /* @__PURE__ */ new Map()].filter(([deviceKey, delta]) => !known.has(deviceKey) && delta > 0).map(([deviceKey, delta]) => ({
61092
60845
  deviceKey,
61093
60846
  backend: deviceKey.split(":")[0] ?? deviceKey,
61094
- attachedCameras: delta,
61095
- queueDepthTotal: 0
60847
+ attachedCameras: delta
61096
60848
  }))];
61097
60849
  return {
61098
60850
  ...load,
@@ -69080,6 +68832,10 @@ async function buildOrchestratorControllers(deps) {
69080
68832
  onCensus: (entries) => {
69081
68833
  motionSliceGate?.noteCensus(entries);
69082
68834
  providerSliceGate?.refreshIfStale("runner-census");
68835
+ },
68836
+ eligibleDeviceKeys: async (nodeId) => {
68837
+ const read = await deps.getEligibleInferenceDevicesIfCached(nodeId);
68838
+ return read.kind === "eligible" ? Object.keys(read.eligible) : null;
69083
68839
  }
69084
68840
  });
69085
68841
  /**
@@ -69938,12 +69694,12 @@ function buildGlobalSettingsSections(options) {
69938
69694
  id: CLUSTER_MODEL_SECTION_ID,
69939
69695
  title: "Enrichment models",
69940
69696
  tab: "pipeline",
69941
- description: "The encoders that turn a crop into a VECTOR, and the admission knobs those encoders share. One value each for the whole cluster, because each writes into a single shared index. Changing a model does not corrupt what is already stored (every vector carries the model that produced it) but it does STRAND it until a re-embed pass has run. Step knobs such as the face-size floor apply to every node for the same reason.",
69697
+ description: "The face and search encoders, one each for the whole cluster: each fills a single shared index, and vectors from two models cannot be compared. Changing one keeps what is stored but hides it from matching and search until it is re-embedded.",
69942
69698
  fields: [...CLUSTER_MODEL_SCOPED_STEPS.map((step) => ({
69943
69699
  key: clusterModelSettingKey(step.stepId),
69944
69700
  type: "select",
69945
69701
  label: step.label,
69946
- description: `Applies to every node. A node whose engine ships no build of the chosen model REFUSES this step and says so in its log, rather than quietly running a different encoder into ${step.indexName}. Changing this strands every vector already in ${step.indexName} until it is re-embedded.`,
69702
+ description: `Used on every node. A node that has no build of this model skips the step (and logs it) rather than use another model. Changing it hides what is already in ${step.indexName} until it is re-embedded.`,
69947
69703
  options: step.options.map((o) => ({
69948
69704
  value: o.id,
69949
69705
  label: o.label
@@ -69965,12 +69721,12 @@ function buildGlobalSettingsSections(options) {
69965
69721
  id: DETAIL_CROP_SECTION_ID,
69966
69722
  title: "Enrichment crop",
69967
69723
  tab: "pipeline",
69968
- description: "How the crop fed to enrichment steps (semantic-search embedding, face, plate) is cut out of the frame. ONE value for the whole cluster: the semantic-search index is a single feature space, and vectors cut with different conventions cannot be compared, so a per-node value would silently corrupt search results. Changing either knob invalidates the stored index — follow it with a rebuild (Camera → Semantic search index → Rebuild), which now cuts through this same setting.",
69724
+ description: "How the crop handed to the search, face and plate models is cut from the frame. One value for the whole cluster, because search results are only comparable when every crop is cut the same way. After changing it, rebuild the search index (Camera → Semantic search index → Rebuild).",
69969
69725
  fields: [{
69970
69726
  key: DETAIL_CROP_PADDING_KEY,
69971
69727
  type: "slider",
69972
69728
  label: "Crop margin",
69973
- description: "Fraction of the detection box added on EACH side before cutting. 0 is the pixel-tight box; 0.15 is the shipped default; 0.2 gives a 1.4x window. CLIP is trained on natural images WITH surroundings, so a tight crop removes exactly the context it is strongest on — but the right value is empirical, which is why it is a knob.",
69729
+ description: "Extra margin added on each side of the detection box, as a fraction of its size. 0 = tight box, 0.15 = default. Search works better with some surroundings in the crop.",
69974
69730
  min: DETAIL_CROP_PADDING_FIELD.min,
69975
69731
  max: DETAIL_CROP_PADDING_FIELD.max,
69976
69732
  step: DETAIL_CROP_PADDING_FIELD.step,
@@ -69980,7 +69736,7 @@ function buildGlobalSettingsSections(options) {
69980
69736
  key: DETAIL_CROP_SQUARE_KEY,
69981
69737
  type: "boolean",
69982
69738
  label: "Square the crop",
69983
- description: "Grow the shorter side so the crop is square before it reaches the model. CLIP's input is square, so a tall subject resized straight to NxN is squashed into a shape the model never saw in training. Off by default because the stored index was built unsquared.",
69739
+ description: "Widen the crop to a square so a tall subject is not squashed when the model resizes it. Off by default because the existing index was built without it.",
69984
69740
  default: DEFAULT_DETAIL_CROP_CONVENTION.square
69985
69741
  }]
69986
69742
  },
@@ -69988,13 +69744,13 @@ function buildGlobalSettingsSections(options) {
69988
69744
  id: NATIVE_LEASE_SECTION_ID,
69989
69745
  title: "Native frame lease",
69990
69746
  tab: "pipeline",
69991
- description: "How a decode worker keeps native pixels available for a LATE crop (post-analysis snapshot, face/plate detail) instead of falling back to the ≤640 detection frame. Two things are kept and they cost very differently: a HELD FRAME is a full native raster (~24.9 MB at 4K on the default pinned-RGB24 path, ~6.2 MB at 1080p) and lives only until its own detection result arrives; a TILE is one subject cut from that frame at native resolution and JPEG-encoded (~60-120 KB at 4K), and lives for tens of seconds. A frame on which nothing was detected produces no tiles and costs nothing. Takes effect on the NEXT decode session for a camera, not on sessions already running.",
69747
+ description: "How each camera keeps full-resolution pixels so a crop taken seconds later (snapshot, face, plate) is sharp instead of cut from the small detection frame. A held frame is a whole picture (~25 MB at 4K) kept until its detections arrive; a tile is one subject, or one whole-scene copy, saved as JPEG for tens of seconds. Applies from the next session of each camera.",
69992
69748
  fields: [
69993
69749
  {
69994
69750
  key: NATIVE_LEASE_HOLD_KEY,
69995
69751
  type: "slider",
69996
69752
  label: "Frames held at once",
69997
- description: "How many delivered frames a worker keeps alive while waiting for their detection results. A frame is freed as soon as its own result comes back and its subject tiles have been cut, so the steady state is inference latency × delivered fps — 1 to 4 frames in practice. This number is only the bound above which the OLDEST held frame is dropped, which is what stops a runner that has stopped answering from pinning RAM. Raising it does not improve crop hit rate; it buys tolerance for a slow runner, and holdOverflow on the metrics line is what tells you that you need it.",
69753
+ description: "Most frames kept per camera while their detections are pending; past it the oldest is dropped. 1 to 4 are in use normally. Raise it only if a slow node drops frames (holdOverflow in the logs) — it does not make crops sharper.",
69998
69754
  min: NATIVE_LEASE_HOLD_FIELD.min,
69999
69755
  max: NATIVE_LEASE_HOLD_FIELD.max,
70000
69756
  step: NATIVE_LEASE_HOLD_FIELD.step,
@@ -70006,7 +69762,7 @@ function buildGlobalSettingsSections(options) {
70006
69762
  key: NATIVE_LEASE_TILE_BUDGET_KEY,
70007
69763
  type: "slider",
70008
69764
  label: "Subject tile RAM",
70009
- description: "RAM per decode worker for the compressed SUBJECT TILES — the native-resolution crops taken at the moment a frame's detections are known, and kept long after the frame itself is gone. This is what serves a crop that arrives seconds late, which measurement says is the ordinary case (2 to 10 seconds on this cluster). At ~60-120 KB a tile, 64 MB is many hundreds of subjects. 0 turns tiles OFF and restores the old behaviour, where a late crop had nothing to fall back to but the ≤640 detection frame.",
69765
+ description: "RAM per camera for subject tiles: full-resolution crops of each detected subject, kept so a crop asked for seconds later is still sharp. A tile is ~60-120 KB. 0 = off (late crops come from the small detection frame).",
70010
69766
  min: NATIVE_LEASE_TILE_BUDGET_FIELD.min,
70011
69767
  max: NATIVE_LEASE_TILE_BUDGET_FIELD.max,
70012
69768
  step: NATIVE_LEASE_TILE_BUDGET_FIELD.step,
@@ -70018,7 +69774,7 @@ function buildGlobalSettingsSections(options) {
70018
69774
  key: NATIVE_LEASE_SCENE_BUDGET_KEY,
70019
69775
  type: "slider",
70020
69776
  label: "Scene tile RAM",
70021
- description: "RAM per decode worker for the SCENE TILES — one JPEG copy of the WHOLE native frame, taken at the same instant as the subject tiles above, and only on frames that detected something. A subject tile cannot answer a request for the whole frame (containment is strict, so that a crop never quietly comes from a different part of the scene), which left the native key frame, the detail plane's full-frame rung and the gallery fallback with nothing to fall back to once the held raster was released — measured at 23.3% of key-frame captures lost, 95.7% of them for exactly that reason. A scene tile is ~1.5-2.5 MB at 4K against ~24 MB for the raster it came from, so this budget buys tens of seconds of coverage where holding the rasters for ten would cost around half a gigabyte per camera. 0 turns scene tiles OFF and restores that gap.",
69777
+ description: "RAM per camera for scene tiles: a full-resolution JPEG of the whole picture (~2 MB at 4K) on frames where something was detected, for key frames and whole-frame crops. 0 = off (those then fall back to the small detection frame).",
70022
69778
  min: NATIVE_LEASE_SCENE_BUDGET_FIELD.min,
70023
69779
  max: NATIVE_LEASE_SCENE_BUDGET_FIELD.max,
70024
69780
  step: NATIVE_LEASE_SCENE_BUDGET_FIELD.step,
@@ -70030,7 +69786,7 @@ function buildGlobalSettingsSections(options) {
70030
69786
  key: NATIVE_LEASE_BUDGET_KEY,
70031
69787
  type: "slider",
70032
69788
  label: "Lease RAM ceiling",
70033
- description: "Hard RAM ceiling for held frames, PER decode worker (one worker per camera per plane). This is a safety ceiling, not the working size — the hold count above is what reclaims frames now, and the ceiling is the number above which something is wrong. Lower it on a small host to bound the worst case. 0 DISABLES the lease entirely and falls the worker back to the tiny GPU surface ring, which misses roughly 85% of late crops — that is the behaviour the lease exists to replace, so 0 is a diagnostic setting, not a tuning one.",
69789
+ description: "Safety ceiling on the RAM held frames may use per camera; the frame count above is what normally limits it. Lower it on a small host. 0 turns full-resolution crops off almost entirely — for diagnosis only.",
70034
69790
  min: NATIVE_LEASE_BUDGET_FIELD.min,
70035
69791
  max: NATIVE_LEASE_BUDGET_FIELD.max,
70036
69792
  step: NATIVE_LEASE_BUDGET_FIELD.step,
@@ -70042,7 +69798,7 @@ function buildGlobalSettingsSections(options) {
70042
69798
  key: NATIVE_LEASE_ACTIVITY_KEY,
70043
69799
  type: "slider",
70044
69800
  label: "Demand window",
70045
- description: "Retention only runs within this long of the last native-crop request (or of the stream dialling). A camera nothing is cropping therefore retains nothing and pays no GPU→RAM download at all; the first crop after the window closes misses once, falls back, and re-opens the window for the retry. 0 means ALWAYS ON — it turns the gate off, it does not turn retention off — which is the legacy behaviour that saturated an N100 with 24 native-4K downloads per second on a camera with zero crop demand. Leave it non-zero unless you are reproducing that.",
69801
+ description: "Keep full-resolution pixels only within this long of the last crop request, so a camera nobody crops costs nothing; the first crop after a quiet spell falls back once. 0 = always keep (heavy on small hosts).",
70046
69802
  min: NATIVE_LEASE_ACTIVITY_FIELD.min,
70047
69803
  max: NATIVE_LEASE_ACTIVITY_FIELD.max,
70048
69804
  step: NATIVE_LEASE_ACTIVITY_FIELD.step,
@@ -70055,7 +69811,7 @@ function buildGlobalSettingsSections(options) {
70055
69811
  key: NATIVE_LEASE_ADMISSION_KEY,
70056
69812
  type: "select",
70057
69813
  label: "What to retain",
70058
- description: "WHICH delivered frames get a native copy. A late crop always names one specific frame, and the only way a frame id leaves the runner is on an inference result — so 'Only frames sent to inference' keeps exactly the set a crop can ever ask for and drops the rest, which at the governor's usual 5-in-25 admit rate is around 80% less retained RAM and 80% less GPU→RAM download. 'Every delivered frame' is the older, unconditional behaviour; choose it if a crop ever has to reach a frame the pipeline never inferred. The decode worker counts any crop that names a non-inferred frame on its metrics line (leaseUnmarkedCrops), so a wrong choice here is visible rather than silent.",
69814
+ description: "Which frames get a full-resolution copy. 'Only frames sent to inference' keeps the frames a crop can actually ask for (about 80% less RAM); 'Every delivered frame' keeps all of them.",
70059
69815
  options: NATIVE_LEASE_ADMISSION_FIELD.options,
70060
69816
  default: NATIVE_LEASE_ADMISSION_FIELD.default
70061
69817
  }
@@ -70068,7 +69824,8 @@ function buildGlobalSettingsSections(options) {
70068
69824
  {
70069
69825
  key: "onDisconnect",
70070
69826
  type: "select",
70071
- label: "On runner disconnect",
69827
+ label: "When a node disconnects",
69828
+ description: "Move its unpinned cameras to other nodes, or leave them stopped.",
70072
69829
  default: DEFAULT_FAILOVER_POLICY.onDisconnect,
70073
69830
  options: [{
70074
69831
  value: "migrate",
@@ -70081,7 +69838,8 @@ function buildGlobalSettingsSections(options) {
70081
69838
  {
70082
69839
  key: "pinnedOnDisconnect",
70083
69840
  type: "select",
70084
- label: "Pinned cameras on disconnect",
69841
+ label: "Its pinned cameras",
69842
+ description: "Keep the pin and wait for the node, or drop the pin and move them.",
70085
69843
  default: DEFAULT_FAILOVER_POLICY.pinnedOnDisconnect,
70086
69844
  options: [{
70087
69845
  value: "leave-pinned",
@@ -70094,7 +69852,8 @@ function buildGlobalSettingsSections(options) {
70094
69852
  {
70095
69853
  key: "onReconnect",
70096
69854
  type: "select",
70097
- label: "On runner reconnect",
69855
+ label: "When it comes back",
69856
+ description: "Put its cameras back where they were, or rebalance the whole cluster.",
70098
69857
  default: DEFAULT_FAILOVER_POLICY.onReconnect,
70099
69858
  options: [{
70100
69859
  value: "restore",
@@ -72095,7 +71854,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
72095
71854
  async getAgentLoad() {
72096
71855
  await this.loadService.refreshDecodeHwaccels();
72097
71856
  await this.loadService.collectAgentLoad();
72098
- return this.loadService.getAgentLoadSummaries();
71857
+ return this.loadService.reportedAgentLoad();
72099
71858
  }
72100
71859
  async getGlobalMetrics() {
72101
71860
  const summaries = await this.getAgentLoad();