channel-worker 2.5.3 → 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.
@@ -1344,20 +1344,26 @@ class CommandPoller {
1344
1344
  const pausedCount = renderers.filter(isPaused).length;
1345
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
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).
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.
1354
1353
  //
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})`);
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})`);
1361
1367
  try {
1362
1368
  await this._launchRendererProfile(toLaunch);
1363
1369
  runningRenderers.push(toLaunch);
@@ -1388,6 +1394,35 @@ class CommandPoller {
1388
1394
  }
1389
1395
  }
1390
1396
 
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).
1402
+ if (runningRenderers.length > parallelLimit) {
1403
+ const excess = runningRenderers.length - parallelLimit;
1404
+ const idleRunning = [];
1405
+ for (const r of runningRenderers) {
1406
+ if (!this._profileLastActivity[r.nst_profile_id]) continue;
1407
+ try {
1408
+ const c = await this.api.rendererHasCommands(r.nst_profile_id);
1409
+ if (c === 0) idleRunning.push(r);
1410
+ } catch {}
1411
+ }
1412
+ for (const r of idleRunning.slice(0, excess)) {
1413
+ console.log(`[scene-dispatch] Closing excess ${r.name} (cap=${parallelLimit})`);
1414
+ try {
1415
+ await this.nst.stopProfile(r.nst_profile_id);
1416
+ delete this._profileLastActivity[r.nst_profile_id];
1417
+ if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
1418
+ const idx = runningRenderers.indexOf(r);
1419
+ if (idx >= 0) runningRenderers.splice(idx, 1);
1420
+ } catch (e) {
1421
+ console.warn(`[scene-dispatch] Failed to close excess ${r.name}: ${e.message}`);
1422
+ }
1423
+ }
1424
+ }
1425
+
1391
1426
  if (runningRenderers.length === 0) {
1392
1427
  this._dispatching = false;
1393
1428
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.3",
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": {