channel-worker 2.1.3 → 2.1.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.
@@ -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,6 +1177,28 @@ class CommandPoller {
1177
1177
  } catch {}
1178
1178
  }
1179
1179
 
1180
+ // 3b. Running profiles with stale heartbeat + commands → extension stuck, close + reset + relaunch
1181
+ const HEARTBEAT_STALE = 90 * 1000; // 90s
1182
+ const now = Date.now();
1183
+ for (const r of [...runningRenderers]) {
1184
+ try {
1185
+ const cmdCount = await this.api.rendererHasCommands(r.nst_profile_id);
1186
+ if (cmdCount === 0) continue;
1187
+ const hb = r.last_heartbeat ? new Date(r.last_heartbeat).getTime() : 0;
1188
+ if (hb && (now - hb) < HEARTBEAT_STALE) continue; // heartbeat fresh, extension alive
1189
+ // Stale heartbeat + has commands → extension stuck
1190
+ console.log(`[scene-dispatch] Stuck: ${r.name} running but heartbeat stale (${hb ? Math.round((now - hb) / 1000) + 's' : 'never'}) — restarting`);
1191
+ try {
1192
+ await this.nst.stopProfile(r.nst_profile_id).catch(() => {});
1193
+ await this.api.resetRendererCommands(r.nst_profile_id);
1194
+ await this._launchRendererProfile(r);
1195
+ console.log(`[scene-dispatch] ${r.name} restarted`);
1196
+ } catch (err) {
1197
+ console.error(`[scene-dispatch] Failed to restart ${r.name}: ${err.message}`);
1198
+ }
1199
+ } catch {}
1200
+ }
1201
+
1180
1202
  // 4. Check queued commands
1181
1203
  const queueCount = await this.api.getSceneQueueCount();
1182
1204
  if (!queueCount) { this._dispatching = false; return; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.1.3",
3
+ "version": "2.1.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": {