channel-worker 2.1.2 → 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.
package/lib/api-client.js CHANGED
@@ -90,6 +90,10 @@ class ApiClient {
90
90
  return this.request('GET', `/workers/renderer-has-commands?nst_profile_id=${encodeURIComponent(nstProfileId)}`);
91
91
  }
92
92
 
93
+ async resetRendererCommands(nstProfileId) {
94
+ return this.request('POST', '/workers/reset-renderer-commands', { nst_profile_id: nstProfileId });
95
+ }
96
+
93
97
  // Extension download
94
98
  async getExtensionVersion() {
95
99
  const data = await this.request('GET', '/extension-download/version');
@@ -1158,16 +1158,18 @@ 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 {
1169
+ await this.api.resetRendererCommands(r.nst_profile_id);
1168
1170
  await this._launchRendererProfile(r);
1169
1171
  runningRenderers.push(r);
1170
- console.log(`[scene-dispatch] ${r.name} recovered`);
1172
+ console.log(`[scene-dispatch] ${r.name} recovered (${cmdCount} commands reset)`);
1171
1173
  } catch (err) {
1172
1174
  console.error(`[scene-dispatch] Failed to recover ${r.name}: ${err.message}`);
1173
1175
  }
@@ -1175,6 +1177,28 @@ class CommandPoller {
1175
1177
  } catch {}
1176
1178
  }
1177
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
+
1178
1202
  // 4. Check queued commands
1179
1203
  const queueCount = await this.api.getSceneQueueCount();
1180
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.2",
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": {