channel-worker 2.5.2 → 2.5.3

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.
@@ -1336,52 +1336,28 @@ class CommandPoller {
1336
1336
  } catch (e) {
1337
1337
  if (this.config.verbose) console.warn('[scene-dispatch] getMyWorker failed, using default:', e.message);
1338
1338
  }
1339
- const stillOffline = renderers.filter(r => !runningRenderers.includes(r));
1340
- console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} (flowkit=${flowkitQ} dom=${domQ}) offline=${stillOffline.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1341
-
1342
- // 5b. Close excess profiles — when parallelLimit drops (e.g., after a DOM→FlowKit
1343
- // switch), idle profiles over the cap should shut down instead of staying open
1344
- // and getting throttled at /flowkit/claim-command.
1345
- if (runningRenderers.length > parallelLimit) {
1346
- const excess = runningRenderers.length - parallelLimit;
1347
- const idleRenderers = [];
1348
- for (const r of runningRenderers) {
1349
- // Only daemon-launched profiles are eligible skip manual launches
1350
- if (!this._profileLastActivity[r.nst_profile_id]) continue;
1351
- try {
1352
- const c = await this.api.rendererHasCommands(r.nst_profile_id);
1353
- if (c === 0) idleRenderers.push(r);
1354
- } catch {}
1355
- }
1356
- const toClose = idleRenderers.slice(0, excess);
1357
- for (const r of toClose) {
1358
- console.log(`[scene-dispatch] Closing excess profile ${r.name} (over cap ${parallelLimit})`);
1359
- try {
1360
- await this.nst.stopProfile(r.nst_profile_id);
1361
- delete this._profileLastActivity[r.nst_profile_id];
1362
- if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
1363
- const idx = runningRenderers.indexOf(r);
1364
- if (idx >= 0) runningRenderers.splice(idx, 1);
1365
- } catch (e) {
1366
- console.warn(`[scene-dispatch] Failed to close excess ${r.name}: ${e.message}`);
1367
- }
1368
- }
1369
- }
1370
-
1371
- // Count how many running profiles are actually free (no commands)
1372
- let freeRunning = 0;
1373
- for (const r of runningRenderers) {
1374
- try {
1375
- const c = await this.api.rendererHasCommands(r.nst_profile_id);
1376
- if (c === 0) freeRunning++;
1377
- } catch {}
1378
- }
1379
- // Only launch new profiles if queue has more work than free profiles
1380
- const needNew = Math.max(0, queueCount - freeRunning);
1381
- const neededLaunches = Math.min(parallelLimit - runningRenderers.length, stillOffline.length, needNew);
1382
- for (let li = 0; li < neededLaunches; li++) {
1383
- const toLaunch = stillOffline[li];
1384
- console.log(`[scene-dispatch] Launching ${toLaunch.name} (running: ${runningRenderers.length + li}/${parallelLimit})`);
1339
+ // Treat 'paused' renderers as NOT eligible to launch — operator clicked
1340
+ // Pause on the Renderers tab; profile should stay closed until Resume.
1341
+ const isPaused = (r) => r && r.health_state === 'paused'
1342
+ && (!r.pause_until || new Date(r.pause_until).getTime() > Date.now());
1343
+ const stillOffline = renderers.filter(r => !runningRenderers.includes(r) && !isPaused(r));
1344
+ const pausedCount = renderers.filter(isPaused).length;
1345
+ console.log(`[scene-dispatch] running=${runningRenderers.length} cap=${parallelLimit} (flowkit=${flowkitQ} dom=${domQ}) offline=${stillOffline.length} paused=${pausedCount} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1346
+
1347
+ // Launch policy: keep ALL non-paused owned renderers ONLINE whenever
1348
+ // there's any queue work — they take turns claiming via the API's
1349
+ // per-worker concurrency cap (Worker.parallel_limit). The cap limits
1350
+ // CONCURRENT CLAIMS, not the number of profiles kept open: e.g. with
1351
+ // parallel_limit=1 + 2 renderers veo01/veo02, both browsers stay open
1352
+ // and alternate scene claims. Operator pauses a specific renderer if
1353
+ // they want it closed (e.g. while solving captcha).
1354
+ //
1355
+ // Old "Close excess profiles" + parallelLimit-bounded launch logic was
1356
+ // removed they conflated "concurrent claims cap" with "max launched"
1357
+ // and forced one of the renderers to sit unlaunched when cap < count.
1358
+ const eligibleToLaunch = queueCount > 0 ? stillOffline : [];
1359
+ for (const toLaunch of eligibleToLaunch) {
1360
+ console.log(`[scene-dispatch] Launching ${toLaunch.name} (queue=${queueCount} running=${runningRenderers.length})`);
1385
1361
  try {
1386
1362
  await this._launchRendererProfile(toLaunch);
1387
1363
  runningRenderers.push(toLaunch);
@@ -1392,6 +1368,26 @@ class CommandPoller {
1392
1368
  }
1393
1369
  }
1394
1370
 
1371
+ // Close any currently-running renderers that just became paused —
1372
+ // operator clicked Pause while a profile was open. Skip if it has an
1373
+ // in-flight command (let it finish naturally); cleanup happens on the
1374
+ // next poll cycle.
1375
+ for (const r of [...runningRenderers]) {
1376
+ if (!isPaused(r)) continue;
1377
+ try {
1378
+ const c = await this.api.rendererHasCommands(r.nst_profile_id);
1379
+ if (c > 0) continue;
1380
+ console.log(`[scene-dispatch] Closing paused renderer ${r.name}`);
1381
+ await this.nst.stopProfile(r.nst_profile_id);
1382
+ delete this._profileLastActivity[r.nst_profile_id];
1383
+ if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
1384
+ const idx = runningRenderers.indexOf(r);
1385
+ if (idx >= 0) runningRenderers.splice(idx, 1);
1386
+ } catch (e) {
1387
+ console.warn(`[scene-dispatch] Failed to close paused ${r.name}: ${e.message}`);
1388
+ }
1389
+ }
1390
+
1395
1391
  if (runningRenderers.length === 0) {
1396
1392
  this._dispatching = false;
1397
1393
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.2",
3
+ "version": "2.5.3",
4
4
  "description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
5
5
  "main": "lib/daemon.js",
6
6
  "bin": {