channel-worker 2.4.3 → 2.4.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.
- package/lib/command-poller.js +31 -4
- package/package.json +1 -1
package/lib/command-poller.js
CHANGED
|
@@ -1176,18 +1176,30 @@ class CommandPoller {
|
|
|
1176
1176
|
const offlineRenderers = renderers.filter(r => !runningRenderers.includes(r));
|
|
1177
1177
|
|
|
1178
1178
|
// 3. Crash recovery
|
|
1179
|
-
// 3a. Offline profiles with assigned commands → reset + relaunch
|
|
1179
|
+
// 3a. Offline profiles with assigned commands → reset + relaunch.
|
|
1180
|
+
// BUT: Nstbrowser API can briefly drop a profile from its running list
|
|
1181
|
+
// during heavy ops (image gen, captcha solving). The reset endpoint
|
|
1182
|
+
// returns { skipped: 'heartbeat-fresh' } if the extension is still
|
|
1183
|
+
// sending heartbeats — meaning the profile is alive even though
|
|
1184
|
+
// Nstbrowser doesn't list it. Skip relaunch in that case to avoid
|
|
1185
|
+
// a tabs-close + tabs-open loop.
|
|
1180
1186
|
for (const r of offlineRenderers) {
|
|
1181
1187
|
try {
|
|
1182
1188
|
const cmdCount = await this.api.rendererHasCommands(r.nst_profile_id);
|
|
1183
1189
|
if (cmdCount > 0) {
|
|
1184
|
-
console.log(`[scene-dispatch] Crash recovery: ${r.name} has ${cmdCount} commands but not running —
|
|
1190
|
+
console.log(`[scene-dispatch] Crash recovery: ${r.name} has ${cmdCount} commands but not running — checking heartbeat`);
|
|
1185
1191
|
try {
|
|
1186
|
-
await this.api.resetRendererCommands(r.nst_profile_id);
|
|
1192
|
+
const resetRes = await this.api.resetRendererCommands(r.nst_profile_id);
|
|
1193
|
+
if (resetRes && resetRes.skipped === 'heartbeat-fresh') {
|
|
1194
|
+
console.log(`[scene-dispatch] ${r.name} heartbeat fresh — extension is alive, skipping relaunch`);
|
|
1195
|
+
runningRenderers.push(r);
|
|
1196
|
+
this._profileLastActivity[r.nst_profile_id] = Date.now();
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1187
1199
|
await this._launchRendererProfile(r);
|
|
1188
1200
|
runningRenderers.push(r);
|
|
1189
1201
|
this._profileLastActivity[r.nst_profile_id] = Date.now();
|
|
1190
|
-
console.log(`[scene-dispatch] ${r.name} recovered (${cmdCount} commands reset)`);
|
|
1202
|
+
console.log(`[scene-dispatch] ${r.name} recovered (${(resetRes && resetRes.modified) || cmdCount} commands reset)`);
|
|
1191
1203
|
} catch (err) {
|
|
1192
1204
|
console.error(`[scene-dispatch] Failed to recover ${r.name}: ${err.message}`);
|
|
1193
1205
|
}
|
|
@@ -1454,6 +1466,21 @@ class CommandPoller {
|
|
|
1454
1466
|
// User-launched profiles have no tracking entry — leave them alone.
|
|
1455
1467
|
if (!lastActivity) continue;
|
|
1456
1468
|
if ((now - lastActivity) > IDLE_TIMEOUT) {
|
|
1469
|
+
// Per-profile in-flight check — getSceneQueueCount only counts
|
|
1470
|
+
// QUEUED cmds, not RUNNING ones. Without this, a profile actively
|
|
1471
|
+
// running a master cast / scene cmd gets closed mid-task because
|
|
1472
|
+
// the queue is empty after the cmd was claimed.
|
|
1473
|
+
let inFlight = 0;
|
|
1474
|
+
try {
|
|
1475
|
+
const probeId = name || profileId;
|
|
1476
|
+
inFlight = await this.api.rendererHasCommands(probeId);
|
|
1477
|
+
} catch {}
|
|
1478
|
+
if (inFlight > 0) {
|
|
1479
|
+
// Bump activity so we re-evaluate after another IDLE_TIMEOUT.
|
|
1480
|
+
this._profileLastActivity[profileId] = now;
|
|
1481
|
+
if (name) this._profileLastActivity[name] = now;
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1457
1484
|
console.log(`[profile-timeout] Closing idle profile ${browser.name || profileId} (idle ${Math.round((now - lastActivity) / 1000)}s)`);
|
|
1458
1485
|
try {
|
|
1459
1486
|
await this.nst.stopProfile(profileId);
|