channel-worker 2.3.3 → 2.3.5

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.
@@ -1207,14 +1207,52 @@ class CommandPoller {
1207
1207
  } catch {}
1208
1208
  }
1209
1209
 
1210
- // 4. Check queued commands
1211
- const queueCount = await this.api.getSceneQueueCount();
1210
+ // 4. Check queued commands — new API returns {total, flowkit_count, dom_count}
1211
+ const queueInfo = await this.api.getSceneQueueCount();
1212
+ const queueCount = typeof queueInfo === 'number' ? queueInfo : (queueInfo?.total || 0);
1213
+ const flowkitQ = typeof queueInfo === 'number' ? 0 : (queueInfo?.flowkit_count || 0);
1214
+ const domQ = typeof queueInfo === 'number' ? queueCount : (queueInfo?.dom_count || 0);
1212
1215
  if (!queueCount) { this._dispatching = false; return; }
1213
1216
 
1214
- // 5. Launch new profiles up to parallel limit (recompute offline after recovery)
1215
- const parallelLimit = parseInt(await this.api.getSetting('veo3_parallel_limit')) || 1;
1217
+ // 5. Pick the right parallel limit based on queue composition
1218
+ // - queue toàn flowkit → dùng flowkit_max_concurrent
1219
+ // - queue toàn DOM → dùng veo3_parallel_limit
1220
+ // - mixed (hiếm) → dùng max của 2 để không bottleneck nhánh nào
1221
+ const flowkitLimit = parseInt(await this.api.getSetting('flowkit_max_concurrent')) || 5;
1222
+ const veo3Limit = parseInt(await this.api.getSetting('veo3_parallel_limit')) || 1;
1223
+ let parallelLimit;
1224
+ if (flowkitQ > 0 && domQ === 0) parallelLimit = flowkitLimit;
1225
+ else if (domQ > 0 && flowkitQ === 0) parallelLimit = veo3Limit;
1226
+ else parallelLimit = Math.max(flowkitLimit, veo3Limit);
1216
1227
  const stillOffline = renderers.filter(r => !runningRenderers.includes(r));
1217
- console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} offline=${stillOffline.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1228
+ console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} (flowkit=${flowkitQ} dom=${domQ}) offline=${stillOffline.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1229
+
1230
+ // 5b. Close excess profiles — when parallelLimit drops (e.g., after a DOM→FlowKit
1231
+ // switch), idle profiles over the cap should shut down instead of staying open
1232
+ // and getting throttled at /flowkit/claim-command.
1233
+ if (runningRenderers.length > parallelLimit) {
1234
+ const excess = runningRenderers.length - parallelLimit;
1235
+ const idleRenderers = [];
1236
+ for (const r of runningRenderers) {
1237
+ try {
1238
+ const c = await this.api.rendererHasCommands(r.nst_profile_id);
1239
+ if (c === 0) idleRenderers.push(r);
1240
+ } catch {}
1241
+ }
1242
+ const toClose = idleRenderers.slice(0, excess);
1243
+ for (const r of toClose) {
1244
+ console.log(`[scene-dispatch] Closing excess profile ${r.name} (over cap ${parallelLimit})`);
1245
+ try {
1246
+ await this.nst.stopProfile(r.nst_profile_id);
1247
+ delete this._profileLastActivity[r.nst_profile_id];
1248
+ if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
1249
+ const idx = runningRenderers.indexOf(r);
1250
+ if (idx >= 0) runningRenderers.splice(idx, 1);
1251
+ } catch (e) {
1252
+ console.warn(`[scene-dispatch] Failed to close excess ${r.name}: ${e.message}`);
1253
+ }
1254
+ }
1255
+ }
1218
1256
 
1219
1257
  // Count how many running profiles are actually free (no commands)
1220
1258
  let freeRunning = 0;
@@ -1316,7 +1354,8 @@ class CommandPoller {
1316
1354
  if (running.length === 0) return;
1317
1355
 
1318
1356
  // Check if there are any queued commands — if so, don't close anything
1319
- const queueCount = await this.api.getSceneQueueCount();
1357
+ const qi = await this.api.getSceneQueueCount();
1358
+ const queueCount = typeof qi === 'number' ? qi : (qi?.total || 0);
1320
1359
  if (queueCount > 0) return;
1321
1360
 
1322
1361
  for (const browser of running) {
@@ -66,7 +66,16 @@ class NstManager {
66
66
 
67
67
  // Create profile if not exists, return profileId
68
68
  async ensureProfile(name, options = {}) {
69
- const existing = await this.findProfile(name);
69
+ // Retry findProfile up to 3 times — Nstbrowser API can transiently return empty
70
+ let existing = null;
71
+ for (let attempt = 1; attempt <= 3; attempt++) {
72
+ existing = await this.findProfile(name);
73
+ if (existing) break;
74
+ if (attempt < 3) {
75
+ console.log(`[nst] Profile "${name}" not found (attempt ${attempt}/3), retrying in 2s...`);
76
+ await new Promise(r => setTimeout(r, 2000));
77
+ }
78
+ }
70
79
  if (existing) {
71
80
  console.log(`[nst] Profile "${name}" exists: ${existing}`);
72
81
  // Set proxy on existing profile if provided
@@ -75,7 +84,7 @@ class NstManager {
75
84
  }
76
85
 
77
86
  const platform = (options.os || 'windows').toLowerCase() === 'mac' ? 'MacOS' : 'Windows';
78
- console.log(`[nst] WARNING: Profile "${name}" NOT FOUND — creating new profile (${platform})...`);
87
+ console.log(`[nst] WARNING: Profile "${name}" NOT FOUND after 3 retries — creating new profile (${platform})...`);
79
88
  const res = await this.api('/profiles', {
80
89
  method: 'POST',
81
90
  body: JSON.stringify({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
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": {