channel-worker 2.5.2 → 2.5.4

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,26 +1336,81 @@ 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)}]`);
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: STRICT parallel_limit cap on physical launches.
1348
+ // With pc2.parallel_limit=1 + 2 renderers (veo03/veo04 share same Veo
1349
+ // Google account), only ONE of them is launched at any time. The
1350
+ // second stays closed until the first finishes / is paused / cap is
1351
+ // bumped. This prevents Google flagging "2 concurrent sessions on
1352
+ // same account" → captcha.
1353
+ //
1354
+ // Earlier iteration tried "launch all, cap only claims" — that left
1355
+ // both browsers open, both pinging Flow (token refresh, telemetry),
1356
+ // and Veo's per-account session monitor counted them as 2 active
1357
+ // sessions → tripped captcha on the supposedly-idle one.
1358
+ const needNew = Math.max(0, queueCount);
1359
+ const neededLaunches = Math.min(
1360
+ parallelLimit - runningRenderers.length,
1361
+ stillOffline.length,
1362
+ needNew,
1363
+ );
1364
+ for (let li = 0; li < Math.max(0, neededLaunches); li++) {
1365
+ const toLaunch = stillOffline[li];
1366
+ console.log(`[scene-dispatch] Launching ${toLaunch.name} (${runningRenderers.length + li + 1}/${parallelLimit})`);
1367
+ try {
1368
+ await this._launchRendererProfile(toLaunch);
1369
+ runningRenderers.push(toLaunch);
1370
+ this._profileLastActivity[toLaunch.nst_profile_id] = Date.now();
1371
+ console.log(`[scene-dispatch] ${toLaunch.name} launched`);
1372
+ } catch (err) {
1373
+ console.error(`[scene-dispatch] Failed to launch ${toLaunch.name}: ${err.message}`);
1374
+ }
1375
+ }
1376
+
1377
+ // Close any currently-running renderers that just became paused —
1378
+ // operator clicked Pause while a profile was open. Skip if it has an
1379
+ // in-flight command (let it finish naturally); cleanup happens on the
1380
+ // next poll cycle.
1381
+ for (const r of [...runningRenderers]) {
1382
+ if (!isPaused(r)) continue;
1383
+ try {
1384
+ const c = await this.api.rendererHasCommands(r.nst_profile_id);
1385
+ if (c > 0) continue;
1386
+ console.log(`[scene-dispatch] Closing paused renderer ${r.name}`);
1387
+ await this.nst.stopProfile(r.nst_profile_id);
1388
+ delete this._profileLastActivity[r.nst_profile_id];
1389
+ if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
1390
+ const idx = runningRenderers.indexOf(r);
1391
+ if (idx >= 0) runningRenderers.splice(idx, 1);
1392
+ } catch (e) {
1393
+ console.warn(`[scene-dispatch] Failed to close paused ${r.name}: ${e.message}`);
1394
+ }
1395
+ }
1341
1396
 
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.
1397
+ // Close EXCESS running profiles when count > parallel_limit. Happens
1398
+ // when operator drops the cap (eg 2→1 to throttle captcha) the
1399
+ // already-launched 2nd profile should retire so it doesn't keep
1400
+ // refreshing tokens against the shared Google account. Only close
1401
+ // if it has no in-flight command (let work finish first).
1345
1402
  if (runningRenderers.length > parallelLimit) {
1346
1403
  const excess = runningRenderers.length - parallelLimit;
1347
- const idleRenderers = [];
1404
+ const idleRunning = [];
1348
1405
  for (const r of runningRenderers) {
1349
- // Only daemon-launched profiles are eligible — skip manual launches
1350
1406
  if (!this._profileLastActivity[r.nst_profile_id]) continue;
1351
1407
  try {
1352
1408
  const c = await this.api.rendererHasCommands(r.nst_profile_id);
1353
- if (c === 0) idleRenderers.push(r);
1409
+ if (c === 0) idleRunning.push(r);
1354
1410
  } catch {}
1355
1411
  }
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})`);
1412
+ for (const r of idleRunning.slice(0, excess)) {
1413
+ console.log(`[scene-dispatch] Closing excess ${r.name} (cap=${parallelLimit})`);
1359
1414
  try {
1360
1415
  await this.nst.stopProfile(r.nst_profile_id);
1361
1416
  delete this._profileLastActivity[r.nst_profile_id];
@@ -1368,30 +1423,6 @@ class CommandPoller {
1368
1423
  }
1369
1424
  }
1370
1425
 
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})`);
1385
- try {
1386
- await this._launchRendererProfile(toLaunch);
1387
- runningRenderers.push(toLaunch);
1388
- this._profileLastActivity[toLaunch.nst_profile_id] = Date.now();
1389
- console.log(`[scene-dispatch] ${toLaunch.name} launched`);
1390
- } catch (err) {
1391
- console.error(`[scene-dispatch] Failed to launch ${toLaunch.name}: ${err.message}`);
1392
- }
1393
- }
1394
-
1395
1426
  if (runningRenderers.length === 0) {
1396
1427
  this._dispatching = false;
1397
1428
  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.4",
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": {