@camstack/addon-pipeline 1.2.90 → 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 (30) hide show
  1. package/dist/{addon-utils-yc-chXuC.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-n4Nbnxx5.js → dist-BbRv3bM2.js} +139 -22
  7. package/dist/{dist-BJYwWEZ9.mjs → dist-XbrYiMV3.mjs} +134 -23
  8. package/dist/{event-loop-stall-monitor-BiPffk0D.mjs → event-loop-stall-monitor-CFWrOZ6G.mjs} +1 -1
  9. package/dist/{event-loop-stall-monitor-DG7QY00k.js → event-loop-stall-monitor-D9hqbc68.js} +1 -1
  10. package/dist/{lazy-sharp-EoSOHgoj.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 +655 -84
  16. package/dist/recorder/index.mjs +654 -83
  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 +2 -2
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CsrMZYLB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BNOPhQ-y.mjs} +2 -2
  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/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DgivrPsH.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DsIi4G5Q.mjs} +1 -1
  23. package/dist/stream-broker/{hostInit-DoFovQmc.mjs → hostInit-DCV2-mSi.mjs} +2 -2
  24. package/dist/stream-broker/index.js +194 -30
  25. package/dist/stream-broker/index.mjs +194 -30
  26. package/dist/stream-broker/remoteEntry.js +1 -1
  27. package/dist/{worker-protocol-DjuxhGo3.js → worker-protocol-CNC-ZcU3.js} +1 -1
  28. package/dist/{worker-protocol-XZ1e36rM.mjs → worker-protocol-D0ewr67U.mjs} +1 -1
  29. package/package.json +3 -2
  30. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DNSELPGi.mjs +0 -26
@@ -1,6 +1,6 @@
1
- import { At as number, Et as boolean, F as deriveDetailCropRect, Ft as EventCategory, K as pickDetailCropConvention, N as defineCustomActions, Nt as string, Ot as lazy, Tt as array, Y as pipelineRunnerCapability, c as DEFAULT_DETAIL_CROP_CONVENTION, d as DEVICE_BACKEND_TO_FORMAT, it as errMsg, j as customAction, jt as object, kt as literal, mt as createEvent, q as pickNativeLeaseOverride, ut as BaseAddon, wt as _enum, yt as nodePin } from "../dist-BJYwWEZ9.mjs";
2
- import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-BiPffk0D.mjs";
3
- import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-XZ1e36rM.mjs";
1
+ import { At as literal, Dt as boolean, Et as array, F as deriveDetailCropRect, It as EventCategory, K as pickDetailCropConvention, Mt as object, N as defineCustomActions, Pt as string, Tt as _enum, Y as pipelineRunnerCapability, at as errMsg, bt as nodePin, c as DEFAULT_DETAIL_CROP_CONVENTION, d as DEVICE_BACKEND_TO_FORMAT, dt as BaseAddon, ht as createEvent, j as customAction, jt as number, kt as lazy, q as pickNativeLeaseOverride } from "../dist-XbrYiMV3.mjs";
2
+ import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-CFWrOZ6G.mjs";
3
+ import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-D0ewr67U.mjs";
4
4
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
6
6
  import { i as withDetectionIntent, n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-Ci7RXNGb.mjs";
@@ -128,6 +128,8 @@ function startGuardedSessionDecode(deps) {
128
128
  let stopPump = null;
129
129
  let release = null;
130
130
  let retryTimer = null;
131
+ /** The live-worker slot held by the RUNNING session, released on teardown. */
132
+ let workerLease = null;
131
133
  const delays = deps.acquireRetryDelaysMs && deps.acquireRetryDelaysMs.length > 0 ? deps.acquireRetryDelaysMs : DEFAULT_ACQUIRE_RETRY_DELAYS_MS;
132
134
  /** Resolves after the backoff for `attempt`, or immediately once aborted. */
133
135
  const waitBackoff = (attempt) => new Promise((resolve) => {
@@ -140,10 +142,19 @@ function startGuardedSessionDecode(deps) {
140
142
  });
141
143
  (async () => {
142
144
  for (let attempt = 0; !aborted; attempt++) {
145
+ let lease = null;
146
+ if (deps.admitWorker) {
147
+ lease = deps.admitWorker();
148
+ if (!lease) {
149
+ await waitBackoff(attempt);
150
+ continue;
151
+ }
152
+ }
143
153
  let acquired = null;
144
154
  try {
145
155
  acquired = await deps.acquire();
146
156
  } catch (err) {
157
+ lease?.release();
147
158
  (attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: acquire failed — retrying", { meta: {
148
159
  attempt: attempt + 1,
149
160
  error: errMsg(err)
@@ -152,16 +163,19 @@ function startGuardedSessionDecode(deps) {
152
163
  continue;
153
164
  }
154
165
  if (aborted) {
166
+ lease?.release();
155
167
  if (acquired) acquired.release();
156
168
  return;
157
169
  }
158
170
  if (!acquired) {
171
+ lease?.release();
159
172
  (attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: no broker profile bound yet — retrying", { meta: { attempt: attempt + 1 } });
160
173
  await waitBackoff(attempt);
161
174
  continue;
162
175
  }
163
176
  if (attempt > 0) deps.logger.info("session-decode: acquire succeeded after retry", { meta: { attempts: attempt + 1 } });
164
177
  release = acquired.release;
178
+ workerLease = lease;
165
179
  stopPump = deps.startPump(acquired.source);
166
180
  return;
167
181
  }
@@ -175,6 +189,8 @@ function startGuardedSessionDecode(deps) {
175
189
  retryTimer = null;
176
190
  }
177
191
  stopPump?.();
192
+ workerLease?.release();
193
+ workerLease = null;
178
194
  if (release) release();
179
195
  };
180
196
  }
@@ -1387,7 +1403,9 @@ var RunSyntheticBenchInputSchema = object({
1387
1403
  sessionId: string().optional(),
1388
1404
  simulatePipeline: boolean().optional(),
1389
1405
  nodeId: string().optional(),
1390
- engine: BenchEngineChoiceSchema.optional()
1406
+ engine: BenchEngineChoiceSchema.optional(),
1407
+ deviceKey: string().optional(),
1408
+ plane: _enum(["full", "frame"]).optional()
1391
1409
  });
1392
1410
  var TimingSplitSchema = object({
1393
1411
  mean: number(),
@@ -1442,6 +1460,48 @@ var pipelineRunnerBenchActions = defineCustomActions({
1442
1460
  abortCapacityRamp: customAction(AbortCapacityRampInputSchema, AbortCapacityRampResultSchema, { kind: "mutation" })
1443
1461
  });
1444
1462
  //#endregion
1463
+ //#region src/pipeline-runner/bench-dispatch.ts
1464
+ /**
1465
+ * Why the cached fast path is unavailable, or `null` if nothing blocks it.
1466
+ *
1467
+ * The fast path is `cacheFrameInPool` once + `inferCached` per call, and
1468
+ * NEITHER cap method takes a `deviceKey` or a `plane` — only `runPipeline`
1469
+ * does. Forwarding a device selector into it would scope the WARMUP
1470
+ * `runPipeline` to the chosen accelerator and leave every TIMED call on the
1471
+ * node's default pool: a measurement of the wrong device, reported as the
1472
+ * right one. Refusing the fast path costs a slower bench and buys a true
1473
+ * number.
1474
+ */
1475
+ function fastPathRefusalReason(input) {
1476
+ 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`;
1477
+ if (input.plane) return `plane '${input.plane}' requested — inferCached has no plane selector; using the runPipeline path instead`;
1478
+ return null;
1479
+ }
1480
+ /**
1481
+ * The cached fast path is only for a plain, single-step, default-device,
1482
+ * default-plane inference bench.
1483
+ */
1484
+ function canUseCachedFastPath(isSingleStep, input) {
1485
+ return isSingleStep && !input.simulatePipeline && fastPathRefusalReason(input) === null;
1486
+ }
1487
+ /**
1488
+ * Build the `pipelineExecutor.runPipeline` argument for one bench call.
1489
+ *
1490
+ * Every optional is ABSENT when unset rather than present-and-undefined: an
1491
+ * omitted `deviceKey` means "the runner's default device" and an omitted
1492
+ * `plane` means the cap's `'full'` default, and the two are not the same as
1493
+ * an explicit `undefined` on a `.loose()` router input.
1494
+ */
1495
+ function buildBenchRunPipelineInput(req) {
1496
+ return {
1497
+ steps: [...req.steps],
1498
+ frame: req.frame,
1499
+ ...req.engine ? { engine: req.engine } : {},
1500
+ ...req.deviceKey ? { deviceKey: req.deviceKey } : {},
1501
+ ...req.plane ? { plane: req.plane } : {}
1502
+ };
1503
+ }
1504
+ //#endregion
1445
1505
  //#region src/pipeline-runner/capacity/capacity-runner.ts
1446
1506
  /**
1447
1507
  * Run ONE ramp step: `cameras` independent open-loop pacers for
@@ -3863,6 +3923,76 @@ var PipelineRunner = class {
3863
3923
  }
3864
3924
  };
3865
3925
  //#endregion
3926
+ //#region src/session-decode/live-worker-admission.ts
3927
+ /**
3928
+ * The node's live-decode-worker budget.
3929
+ *
3930
+ * One instance per runner addon. Not a semaphore: a semaphore's contract is
3931
+ * "wait", and waiting is the thing this deliberately does not do — see
3932
+ * {@link DEFAULT_MAX_LIVE_DECODE_WORKERS}.
3933
+ */
3934
+ var LiveDecodeWorkerAdmission = class {
3935
+ limit;
3936
+ logger;
3937
+ live = 0;
3938
+ constructor(options) {
3939
+ this.limit = Math.max(1, options.limit);
3940
+ this.logger = options.logger;
3941
+ }
3942
+ /** Workers currently holding a slot. */
3943
+ get inFlight() {
3944
+ return this.live;
3945
+ }
3946
+ get ceiling() {
3947
+ return this.limit;
3948
+ }
3949
+ /**
3950
+ * Apply an operator-changed ceiling.
3951
+ *
3952
+ * Lowering it never kills a running session — a decode worker torn down
3953
+ * mid-motion loses the event it was started for. It only stops the next
3954
+ * admission, so the count drains to the new ceiling as sessions end.
3955
+ */
3956
+ resize(limit) {
3957
+ this.limit = Math.max(1, limit);
3958
+ }
3959
+ /**
3960
+ * Take a slot, or refuse.
3961
+ *
3962
+ * A refusal is a WARN and carries `tags.deviceId`, because the only useful
3963
+ * form of this question is per-camera ("why did 617 not run when 615 did?").
3964
+ * A branch that drops work silently reads as "it never happened".
3965
+ */
3966
+ admit(deviceId, label) {
3967
+ if (this.live >= this.limit) {
3968
+ this.logger.warn("session-decode: REFUSED — node live decode-worker ceiling reached, will retry", {
3969
+ tags: { deviceId },
3970
+ meta: {
3971
+ label,
3972
+ live: this.live,
3973
+ ceiling: this.limit
3974
+ }
3975
+ });
3976
+ return null;
3977
+ }
3978
+ this.live += 1;
3979
+ this.logger.debug("session-decode: live decode-worker slot taken", {
3980
+ tags: { deviceId },
3981
+ meta: {
3982
+ label,
3983
+ live: this.live,
3984
+ ceiling: this.limit
3985
+ }
3986
+ });
3987
+ let released = false;
3988
+ return { release: () => {
3989
+ if (released) return;
3990
+ released = true;
3991
+ this.live -= 1;
3992
+ } };
3993
+ }
3994
+ };
3995
+ //#endregion
3866
3996
  //#region src/pipeline-runner/stateless-step.ts
3867
3997
  function modelServability(step, modelId, format) {
3868
3998
  const entry = step.models.find((m) => m.id === modelId);
@@ -4001,7 +4131,8 @@ var DEFAULT_CONFIG = {
4001
4131
  maxConcurrentInferences: 16,
4002
4132
  targetLoadPercent: 80,
4003
4133
  minThrottledFps: 1,
4004
- maxConcurrentDecodeSpawns: 3
4134
+ maxConcurrentDecodeSpawns: 3,
4135
+ maxLiveDecodeWorkers: 8
4005
4136
  };
4006
4137
  /**
4007
4138
  * Camera attachment record kept by the runner addon. Holds the original
@@ -4304,6 +4435,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4304
4435
  */
4305
4436
  spawnGate = null;
4306
4437
  /**
4438
+ * Per-node ceiling on decode workers ALIVE at once, sized to
4439
+ * `config.maxLiveDecodeWorkers`. The spawn gate above bounds how fast workers
4440
+ * START; this bounds how many EXIST — measured live at 1 -> 8 workers and a
4441
+ * ~4 GiB move in 8 seconds, with nothing stopping the 9th. `resize()`d live in
4442
+ * `onConfigChanged`. Null until `onInitialize`.
4443
+ */
4444
+ liveWorkers = null;
4445
+ /**
4307
4446
  * Node-level map from a retention-ring `FrameHandle` to the decode worker
4308
4447
  * still holding that frame's NATIVE surface — backs the `getNativeCrop` cap
4309
4448
  * so post-analysis can cut native-res crops (best-effort; a miss falls back
@@ -4527,6 +4666,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4527
4666
  }
4528
4667
  });
4529
4668
  this.spawnGate = new Semaphore(Math.max(1, this.config.maxConcurrentDecodeSpawns));
4669
+ this.liveWorkers = new LiveDecodeWorkerAdmission({
4670
+ limit: this.config.maxLiveDecodeWorkers,
4671
+ logger: this.ctx.logger
4672
+ });
4530
4673
  this.runner.timingSampler.setLogger(this.ctx.logger.child("timing"));
4531
4674
  this.runner.onDetectionStreamChange((deviceId, action) => {
4532
4675
  this.handleDetectionStreamChange(deviceId, action);
@@ -4687,8 +4830,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4687
4830
  children: s.children ?? []
4688
4831
  }));
4689
4832
  const enabledSteps = stepsToRun.filter((s) => s.enabled);
4690
- const useFastPath = enabledSteps.length === 1 && (!enabledSteps[0].children || enabledSteps[0].children.filter((c) => c.enabled).length === 0) && !input.simulatePipeline;
4833
+ const isSingleStep = enabledSteps.length === 1 && (!enabledSteps[0].children || enabledSteps[0].children.filter((c) => c.enabled).length === 0);
4834
+ const useFastPath = canUseCachedFastPath(isSingleStep, input);
4691
4835
  const rootStep = enabledSteps[0];
4836
+ const fastPathRefusal = isSingleStep ? fastPathRefusalReason(input) : null;
4837
+ if (fastPathRefusal) ctx.logger.info("synthetic bench: cached fast path declined", { meta: {
4838
+ reason: fastPathRefusal,
4839
+ node: benchNode
4840
+ } });
4692
4841
  const sharedFrame = {
4693
4842
  data: cached.data,
4694
4843
  format: cached.format,
@@ -4708,11 +4857,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4708
4857
  height: cached.height,
4709
4858
  format: cached.format
4710
4859
  }, pin)).frameId;
4711
- await api.pipelineExecutor.runPipeline.mutate({
4860
+ await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
4712
4861
  steps: stepsToRun,
4713
4862
  frame: sharedFrame,
4714
4863
  ...input.engine ? { engine: input.engine } : {}
4715
- }, pin);
4864
+ }) }, pin);
4716
4865
  const warmupCount = input.warmup ?? 1;
4717
4866
  for (let w = 0; w < warmupCount; w++) await api.pipelineExecutor.inferCached.mutate({
4718
4867
  stepId: rootStep.addonId,
@@ -4805,16 +4954,20 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
4805
4954
  }
4806
4955
  ctx.logger.info("synthetic bench: using full runPipeline path", { meta: {
4807
4956
  steps: enabledSteps.length,
4808
- simulatePipeline: !!input.simulatePipeline
4957
+ simulatePipeline: !!input.simulatePipeline,
4958
+ deviceKey: input.deviceKey ?? null,
4959
+ plane: input.plane ?? null
4809
4960
  } });
4810
4961
  let _callCount = 0;
4811
4962
  const callOnce = async () => {
4812
4963
  const t0 = performance.now();
4813
- const result = await api.pipelineExecutor.runPipeline.mutate({
4964
+ const result = await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
4814
4965
  steps: stepsToRun,
4815
4966
  frame: sharedFrame,
4816
- ...input.engine ? { engine: input.engine } : {}
4817
- }, pin);
4967
+ ...input.engine ? { engine: input.engine } : {},
4968
+ ...input.deviceKey ? { deviceKey: input.deviceKey } : {},
4969
+ ...input.plane ? { plane: input.plane } : {}
4970
+ }) }, pin);
4818
4971
  const wallMs = performance.now() - t0;
4819
4972
  const n = ++_callCount;
4820
4973
  if (n <= 20) ctx.logger.info("bench call trace", { meta: {
@@ -6191,6 +6344,18 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6191
6344
  * blocked by a missing gate. `label` distinguishes detect vs motion forks in
6192
6345
  * the logs; the device id is already tagged on `log`.
6193
6346
  */
6347
+ /**
6348
+ * The node's live-decode-worker admission, bound to one camera.
6349
+ *
6350
+ * `undefined` when the ceiling is not configured (pre-`onInitialize`), which
6351
+ * `startGuardedSessionDecode` reads as "no ceiling" — the historical
6352
+ * behaviour. Never a queue: see {@link LiveDecodeWorkerAdmission}.
6353
+ */
6354
+ buildLiveWorkerAdmit(deviceId, label) {
6355
+ const admission = this.liveWorkers;
6356
+ if (!admission) return void 0;
6357
+ return () => admission.admit(deviceId, label);
6358
+ }
6194
6359
  buildDecodeSpawnAcquire(log, label) {
6195
6360
  return async () => {
6196
6361
  const gate = this.spawnGate;
@@ -6218,6 +6383,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6218
6383
  const requestedAt = Date.now();
6219
6384
  const teardown = startGuardedSessionDecode({
6220
6385
  logger: log,
6386
+ admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "detect"),
6221
6387
  acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config), true),
6222
6388
  startPump: (source) => {
6223
6389
  const dialMs = Date.now() - requestedAt;
@@ -6290,6 +6456,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6290
6456
  if (!runner) return () => {};
6291
6457
  return startGuardedSessionDecode({
6292
6458
  logger: log,
6459
+ admitWorker: this.buildLiveWorkerAdmit(config.deviceId, "motion"),
6293
6460
  acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
6294
6461
  startPump: (source) => {
6295
6462
  log.info("session-decode: motion routed through per-session decode worker (gray)", { meta: {
@@ -6846,6 +7013,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6846
7013
  }, OCCUPANCY_BURST_SAFETY_TIMEOUT_MS);
6847
7014
  teardown = startGuardedSessionDecode({
6848
7015
  logger: log,
7016
+ admitWorker: this.buildLiveWorkerAdmit(deviceId, "occupancy"),
6849
7017
  acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:occupancy:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
6850
7018
  startPump: (source) => startSessionDecodePump({
6851
7019
  fork: this.forkDecodeWorkerWithLease,
@@ -7019,6 +7187,17 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
7019
7187
  step: 1,
7020
7188
  default: DEFAULT_CONFIG.maxConcurrentDecodeSpawns,
7021
7189
  showValue: true
7190
+ },
7191
+ {
7192
+ type: "slider",
7193
+ key: "maxLiveDecodeWorkers",
7194
+ label: "Max live decode workers",
7195
+ 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.",
7196
+ min: 1,
7197
+ max: 32,
7198
+ step: 1,
7199
+ default: DEFAULT_CONFIG.maxLiveDecodeWorkers,
7200
+ showValue: true
7022
7201
  }
7023
7202
  ]
7024
7203
  }] });
@@ -7026,12 +7205,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
7026
7205
  async onConfigChanged() {
7027
7206
  this.runner?.updateLimits(this.config);
7028
7207
  this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
7208
+ this.liveWorkers?.resize(this.config.maxLiveDecodeWorkers);
7029
7209
  this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
7030
7210
  maxQueueDepth: this.config.maxQueueDepth,
7031
7211
  maxConcurrentInferences: this.config.maxConcurrentInferences,
7032
7212
  targetLoadPercent: this.config.targetLoadPercent,
7033
7213
  minThrottledFps: this.config.minThrottledFps,
7034
- maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns
7214
+ maxConcurrentDecodeSpawns: this.config.maxConcurrentDecodeSpawns,
7215
+ maxLiveDecodeWorkers: this.config.maxLiveDecodeWorkers
7035
7216
  } });
7036
7217
  }
7037
7218
  };