channel-worker 2.0.3 → 2.1.1

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.
package/lib/api-client.js CHANGED
@@ -86,6 +86,10 @@ class ApiClient {
86
86
  return this.request('POST', '/workers/scene-dispatch', { nst_profile_id: nstProfileId });
87
87
  }
88
88
 
89
+ async rendererHasCommands(nstProfileId) {
90
+ return this.request('GET', `/workers/renderer-has-commands?nst_profile_id=${encodeURIComponent(nstProfileId)}`);
91
+ }
92
+
89
93
  // Extension download
90
94
  async getExtensionVersion() {
91
95
  const data = await this.request('GET', '/extension-download/version');
@@ -1166,75 +1166,55 @@ class CommandPoller {
1166
1166
  });
1167
1167
  const offlineRenderers = renderers.filter(r => !runningRenderers.includes(r));
1168
1168
 
1169
- // 6. Pick target profile — launch new if under limit, else assign to running
1170
- let target = null;
1171
- console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} offline=${offlineRenderers.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1172
-
1173
- if (runningRenderers.length < parallelLimit && offlineRenderers.length > 0) {
1174
- // Under limit — launch a new profile first
1175
- target = offlineRenderers[0];
1176
- console.log(`[scene-dispatch] Launching ${target.name} (running: ${runningRenderers.length}/${parallelLimit})`);
1169
+ // 5b. Crash recoveryre-launch offline profiles that have assigned commands
1170
+ for (const r of offlineRenderers) {
1177
1171
  try {
1178
- await this.nst.ensureProfile(target.nst_profile_id, { os: target.os || 'windows', proxy: target.proxy || null });
1179
-
1180
- const path = require('path');
1181
- const os_mod = require('os');
1182
- const defaultCCExtPath = path.join(os_mod.homedir(), 'content-creator-ext');
1183
- const baseExtPath = this.config.content_creator_ext_path || defaultCCExtPath;
1184
- await this._ensureContentCreatorExt(baseExtPath);
1185
-
1186
- // Create unique ext dir per profile + version (forces Chromium to reload SW)
1187
- let extensionPath = baseExtPath;
1188
- const fs = require('fs');
1189
- const extVersion = (() => { try { return JSON.parse(fs.readFileSync(path.join(baseExtPath, 'manifest.json'), 'utf8')).version; } catch { return '0'; } })();
1190
- const uniqueExtPath = baseExtPath + '-' + target.nst_profile_id + '-v' + extVersion;
1191
- try {
1192
- // Clean old version dirs for this profile
1193
- const parent = path.dirname(baseExtPath);
1194
- const prefix = path.basename(baseExtPath) + '-' + target.nst_profile_id;
1172
+ const cmdCount = await this.api.rendererHasCommands(r.nst_profile_id);
1173
+ if (cmdCount > 0) {
1174
+ console.log(`[scene-dispatch] Crash recovery: ${r.name} has ${cmdCount} commands but not running — relaunching`);
1195
1175
  try {
1196
- fs.readdirSync(parent)
1197
- .filter(d => d.startsWith(prefix) && d !== path.basename(uniqueExtPath))
1198
- .forEach(d => { try { fs.rmSync(path.join(parent, d), { recursive: true }); } catch {} });
1199
- } catch {}
1200
- if (fs.existsSync(uniqueExtPath)) fs.rmSync(uniqueExtPath, { recursive: true });
1201
- fs.mkdirSync(uniqueExtPath, { recursive: true });
1202
- fs.cpSync(baseExtPath, uniqueExtPath, { recursive: true });
1203
- fs.writeFileSync(path.join(uniqueExtPath, 'config.json'), JSON.stringify({
1204
- channelManagerApi: this.api.baseUrl,
1205
- profileId: target.nst_profile_id,
1206
- workerToken: this.config.worker_token || '',
1207
- workerType: 'veo3',
1208
- }));
1209
- extensionPath = uniqueExtPath;
1210
- } catch (e) {
1211
- console.warn(`[scene-dispatch] Ext dir failed: ${e.message}, using base`);
1176
+ await this._launchRendererProfile(r);
1177
+ runningRenderers.push(r);
1178
+ console.log(`[scene-dispatch] ${r.name} recovered`);
1179
+ } catch (err) {
1180
+ console.error(`[scene-dispatch] Failed to recover ${r.name}: ${err.message}`);
1181
+ }
1212
1182
  }
1183
+ } catch {}
1184
+ }
1185
+
1186
+ // 6. Launch profiles up to parallel limit
1187
+ console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} offline=${offlineRenderers.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1213
1188
 
1214
- await this.nst.launchProfile(target.nst_profile_id, { proxy: target.proxy || null, extensionPath });
1215
- console.log(`[scene-dispatch] ${target.name} launched`);
1189
+ const neededLaunches = Math.min(parallelLimit - runningRenderers.length, offlineRenderers.length, queueCount);
1190
+ for (let li = 0; li < neededLaunches; li++) {
1191
+ const toLaunch = offlineRenderers[li];
1192
+ console.log(`[scene-dispatch] Launching ${toLaunch.name} (running: ${runningRenderers.length + li}/${parallelLimit})`);
1193
+ try {
1194
+ await this._launchRendererProfile(toLaunch);
1195
+ runningRenderers.push(toLaunch); // now considered running
1196
+ console.log(`[scene-dispatch] ${toLaunch.name} launched`);
1216
1197
  } catch (err) {
1217
- console.error(`[scene-dispatch] Failed to launch ${target.name}: ${err.message}`);
1218
- target = runningRenderers[0] || null; // fallback to running
1198
+ console.error(`[scene-dispatch] Failed to launch ${toLaunch.name}: ${err.message}`);
1219
1199
  }
1220
1200
  }
1221
1201
 
1222
- // Fallback: assign to least loaded running renderer
1223
- if (!target && runningRenderers.length > 0) {
1224
- target = runningRenderers[0]; // extension processes commands sequentially per profile
1225
- }
1226
-
1227
- if (!target) {
1228
- // No renderers available at all — wait for next cycle
1202
+ if (runningRenderers.length === 0) {
1229
1203
  this._dispatching = false;
1230
1204
  return;
1231
1205
  }
1232
1206
 
1233
- // 7. Claim command and assign to profile
1234
- const cmd = await this.api.sceneDispatch(target.nst_profile_id);
1235
- if (cmd) {
1207
+ // 7. Assign queued commands round-robin across running renderers
1208
+ let assigned = 0;
1209
+ for (let qi = 0; qi < queueCount; qi++) {
1210
+ const target = runningRenderers[qi % runningRenderers.length];
1211
+ const cmd = await this.api.sceneDispatch(target.nst_profile_id);
1212
+ if (!cmd) break; // queue empty
1236
1213
  this._profileLastActivity[target.nst_profile_id] = Date.now();
1237
- console.log(`[scene-dispatch] Assigned ${cmd.type} → ${target.name} (queue: ${queueCount - 1})`);
1214
+ assigned++;
1215
+ }
1216
+ if (assigned > 0) {
1217
+ console.log(`[scene-dispatch] Assigned ${assigned} commands across ${runningRenderers.length} renderers`);
1238
1218
  }
1239
1219
  } catch (err) {
1240
1220
  console.error(`[scene-dispatch] Error: ${err.message}`);
@@ -1242,6 +1222,44 @@ class CommandPoller {
1242
1222
  this._dispatching = false;
1243
1223
  }
1244
1224
 
1225
+ async _launchRendererProfile(renderer) {
1226
+ await this.nst.ensureProfile(renderer.nst_profile_id, { os: renderer.os || 'windows', proxy: renderer.proxy || null });
1227
+
1228
+ const path = require('path');
1229
+ const os_mod = require('os');
1230
+ const fs = require('fs');
1231
+ const defaultCCExtPath = path.join(os_mod.homedir(), 'content-creator-ext');
1232
+ const baseExtPath = this.config.content_creator_ext_path || defaultCCExtPath;
1233
+ await this._ensureContentCreatorExt(baseExtPath);
1234
+
1235
+ let extensionPath = baseExtPath;
1236
+ const extVersion = (() => { try { return JSON.parse(fs.readFileSync(path.join(baseExtPath, 'manifest.json'), 'utf8')).version; } catch { return '0'; } })();
1237
+ const uniqueExtPath = baseExtPath + '-' + renderer.nst_profile_id + '-v' + extVersion;
1238
+ try {
1239
+ const parent = path.dirname(baseExtPath);
1240
+ const prefix = path.basename(baseExtPath) + '-' + renderer.nst_profile_id;
1241
+ try {
1242
+ fs.readdirSync(parent)
1243
+ .filter(d => d.startsWith(prefix) && d !== path.basename(uniqueExtPath))
1244
+ .forEach(d => { try { fs.rmSync(path.join(parent, d), { recursive: true }); } catch {} });
1245
+ } catch {}
1246
+ if (fs.existsSync(uniqueExtPath)) fs.rmSync(uniqueExtPath, { recursive: true });
1247
+ fs.mkdirSync(uniqueExtPath, { recursive: true });
1248
+ fs.cpSync(baseExtPath, uniqueExtPath, { recursive: true });
1249
+ fs.writeFileSync(path.join(uniqueExtPath, 'config.json'), JSON.stringify({
1250
+ channelManagerApi: this.api.baseUrl,
1251
+ profileId: renderer.nst_profile_id,
1252
+ workerToken: this.config.worker_token || '',
1253
+ workerType: 'veo3',
1254
+ }));
1255
+ extensionPath = uniqueExtPath;
1256
+ } catch (e) {
1257
+ console.warn(`[scene-dispatch] Ext dir failed: ${e.message}, using base`);
1258
+ }
1259
+
1260
+ await this.nst.launchProfile(renderer.nst_profile_id, { proxy: renderer.proxy || null, extensionPath });
1261
+ }
1262
+
1245
1263
  // ─── Profile Timeout ───────────────────────────────────────────────────────
1246
1264
  // Close profiles that have been idle (no commands assigned) for too long.
1247
1265
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.0.3",
3
+ "version": "2.1.1",
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": {