channel-worker 2.1.3 → 2.1.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.
@@ -1158,14 +1158,14 @@ class CommandPoller {
1158
1158
  });
1159
1159
  const offlineRenderers = renderers.filter(r => !runningRenderers.includes(r));
1160
1160
 
1161
- // 3. Crash recovery — re-launch offline profiles that have assigned commands
1161
+ // 3. Crash recovery
1162
+ // 3a. Offline profiles with assigned commands → reset + relaunch
1162
1163
  for (const r of offlineRenderers) {
1163
1164
  try {
1164
1165
  const cmdCount = await this.api.rendererHasCommands(r.nst_profile_id);
1165
1166
  if (cmdCount > 0) {
1166
1167
  console.log(`[scene-dispatch] Crash recovery: ${r.name} has ${cmdCount} commands but not running — relaunching`);
1167
1168
  try {
1168
- // Reset stuck "running" commands back to "pending" so extension can pick them up
1169
1169
  await this.api.resetRendererCommands(r.nst_profile_id);
1170
1170
  await this._launchRendererProfile(r);
1171
1171
  runningRenderers.push(r);
@@ -1177,17 +1177,41 @@ class CommandPoller {
1177
1177
  } catch {}
1178
1178
  }
1179
1179
 
1180
+ // 3b. Running profiles with stale heartbeat + PENDING commands → extension never started
1181
+ // Only restart if commands are still "pending" (not "running" = extension picked up but busy)
1182
+ const HEARTBEAT_STALE = 3 * 60 * 1000; // 3 minutes (extension can be busy rendering for 5+ min)
1183
+ const now = Date.now();
1184
+ for (const r of [...runningRenderers]) {
1185
+ try {
1186
+ // Only check profiles that have pending commands (not running = actively processing)
1187
+ const cmdCount = await this.api.rendererHasCommands(r.nst_profile_id);
1188
+ if (cmdCount === 0) continue;
1189
+ const hb = r.last_heartbeat ? new Date(r.last_heartbeat).getTime() : 0;
1190
+ if (hb && (now - hb) < HEARTBEAT_STALE) continue;
1191
+ console.log(`[scene-dispatch] Stuck: ${r.name} running but heartbeat stale (${hb ? Math.round((now - hb) / 1000) + 's' : 'never'}) — restarting`);
1192
+ try {
1193
+ await this.nst.stopProfile(r.nst_profile_id).catch(() => {});
1194
+ await this.api.resetRendererCommands(r.nst_profile_id);
1195
+ await this._launchRendererProfile(r);
1196
+ console.log(`[scene-dispatch] ${r.name} restarted`);
1197
+ } catch (err) {
1198
+ console.error(`[scene-dispatch] Failed to restart ${r.name}: ${err.message}`);
1199
+ }
1200
+ } catch {}
1201
+ }
1202
+
1180
1203
  // 4. Check queued commands
1181
1204
  const queueCount = await this.api.getSceneQueueCount();
1182
1205
  if (!queueCount) { this._dispatching = false; return; }
1183
1206
 
1184
- // 5. Launch new profiles up to parallel limit
1207
+ // 5. Launch new profiles up to parallel limit (recompute offline after recovery)
1185
1208
  const parallelLimit = parseInt(await this.api.getSetting('veo3_parallel_limit')) || 1;
1186
- console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} offline=${offlineRenderers.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1209
+ const stillOffline = renderers.filter(r => !runningRenderers.includes(r));
1210
+ console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} offline=${stillOffline.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
1187
1211
 
1188
- const neededLaunches = Math.min(parallelLimit - runningRenderers.length, offlineRenderers.length, queueCount);
1212
+ const neededLaunches = Math.min(parallelLimit - runningRenderers.length, stillOffline.length, queueCount);
1189
1213
  for (let li = 0; li < neededLaunches; li++) {
1190
- const toLaunch = offlineRenderers[li];
1214
+ const toLaunch = stillOffline[li];
1191
1215
  console.log(`[scene-dispatch] Launching ${toLaunch.name} (running: ${runningRenderers.length + li}/${parallelLimit})`);
1192
1216
  try {
1193
1217
  await this._launchRendererProfile(toLaunch);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.1.3",
3
+ "version": "2.1.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": {