channel-worker 2.3.4 → 2.4.0
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 +7 -0
- package/lib/command-poller.js +80 -3
- package/package.json +1 -1
package/lib/api-client.js
CHANGED
|
@@ -83,6 +83,13 @@ class ApiClient {
|
|
|
83
83
|
return this.request('GET', '/workers/scene-queue-count');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// ChatGPT subscription dispatch — returns { count, profile_id } so the
|
|
87
|
+
// daemon knows whether to launch a dedicated capture profile (distinct
|
|
88
|
+
// from the Veo3/FlowKit renderers pool).
|
|
89
|
+
async getChatgptQueue() {
|
|
90
|
+
return this.request('GET', '/workers/chatgpt-queue-count');
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
async sceneDispatch(nstProfileId) {
|
|
87
94
|
return this.request('POST', '/workers/scene-dispatch', { nst_profile_id: nstProfileId });
|
|
88
95
|
}
|
package/lib/command-poller.js
CHANGED
|
@@ -31,14 +31,21 @@ class CommandPoller {
|
|
|
31
31
|
this._dispatchTimer = setInterval(() => this._dispatchScenes(), 5000);
|
|
32
32
|
this._dispatching = false;
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
|
|
34
|
+
// ChatGPT-subscription dispatcher — launches the configured profile when
|
|
35
|
+
// a thumbnail command is waiting, independent from the scene renderers pool.
|
|
36
|
+
console.log('[chatgpt-dispatch] Started (every 5s)');
|
|
37
|
+
this._chatgptDispatchTimer = setInterval(() => this._dispatchChatgpt(), 5000);
|
|
38
|
+
this._chatgptDispatching = false;
|
|
39
|
+
|
|
40
|
+
// Profile timeout — close idle profiles after 30s with no work
|
|
41
|
+
this._profileTimeoutTimer = setInterval(() => this._checkProfileTimeouts(), 10000);
|
|
36
42
|
this._profileLastActivity = {}; // { nst_profile_id: timestamp }
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
stop() {
|
|
40
46
|
if (this.timer) { clearInterval(this.timer); this.timer = null; }
|
|
41
47
|
if (this._dispatchTimer) { clearInterval(this._dispatchTimer); this._dispatchTimer = null; }
|
|
48
|
+
if (this._chatgptDispatchTimer) { clearInterval(this._chatgptDispatchTimer); this._chatgptDispatchTimer = null; }
|
|
42
49
|
if (this._profileTimeoutTimer) { clearInterval(this._profileTimeoutTimer); this._profileTimeoutTimer = null; }
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -1227,6 +1234,35 @@ class CommandPoller {
|
|
|
1227
1234
|
const stillOffline = renderers.filter(r => !runningRenderers.includes(r));
|
|
1228
1235
|
console.log(`[scene-dispatch] running=${runningRenderers.length}/${parallelLimit} (flowkit=${flowkitQ} dom=${domQ}) offline=${stillOffline.length} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
|
|
1229
1236
|
|
|
1237
|
+
// 5b. Close excess profiles — when parallelLimit drops (e.g., after a DOM→FlowKit
|
|
1238
|
+
// switch), idle profiles over the cap should shut down instead of staying open
|
|
1239
|
+
// and getting throttled at /flowkit/claim-command.
|
|
1240
|
+
if (runningRenderers.length > parallelLimit) {
|
|
1241
|
+
const excess = runningRenderers.length - parallelLimit;
|
|
1242
|
+
const idleRenderers = [];
|
|
1243
|
+
for (const r of runningRenderers) {
|
|
1244
|
+
// Only daemon-launched profiles are eligible — skip manual launches
|
|
1245
|
+
if (!this._profileLastActivity[r.nst_profile_id]) continue;
|
|
1246
|
+
try {
|
|
1247
|
+
const c = await this.api.rendererHasCommands(r.nst_profile_id);
|
|
1248
|
+
if (c === 0) idleRenderers.push(r);
|
|
1249
|
+
} catch {}
|
|
1250
|
+
}
|
|
1251
|
+
const toClose = idleRenderers.slice(0, excess);
|
|
1252
|
+
for (const r of toClose) {
|
|
1253
|
+
console.log(`[scene-dispatch] Closing excess profile ${r.name} (over cap ${parallelLimit})`);
|
|
1254
|
+
try {
|
|
1255
|
+
await this.nst.stopProfile(r.nst_profile_id);
|
|
1256
|
+
delete this._profileLastActivity[r.nst_profile_id];
|
|
1257
|
+
if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
|
|
1258
|
+
const idx = runningRenderers.indexOf(r);
|
|
1259
|
+
if (idx >= 0) runningRenderers.splice(idx, 1);
|
|
1260
|
+
} catch (e) {
|
|
1261
|
+
console.warn(`[scene-dispatch] Failed to close excess ${r.name}: ${e.message}`);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1230
1266
|
// Count how many running profiles are actually free (no commands)
|
|
1231
1267
|
let freeRunning = 0;
|
|
1232
1268
|
for (const r of runningRenderers) {
|
|
@@ -1314,6 +1350,47 @@ class CommandPoller {
|
|
|
1314
1350
|
await this.nst.launchProfile(renderer.nst_profile_id, { proxy: renderer.proxy || null, extensionPath, forceRelaunch: options.forceRelaunch });
|
|
1315
1351
|
}
|
|
1316
1352
|
|
|
1353
|
+
// ─── ChatGPT Subscription Dispatch ─────────────────────────────────────────
|
|
1354
|
+
// Separate dispatcher for the dedicated ChatGPT profile (configured via
|
|
1355
|
+
// Setting: chatgpt_sub_profile_id). Launches the profile on-demand when a
|
|
1356
|
+
// generate_thumbnail command is pending, so Veo3 renderers stay untouched.
|
|
1357
|
+
|
|
1358
|
+
async _dispatchChatgpt() {
|
|
1359
|
+
if (this._chatgptDispatching) return;
|
|
1360
|
+
this._chatgptDispatching = true;
|
|
1361
|
+
try {
|
|
1362
|
+
if (!this.nst) return;
|
|
1363
|
+
const queue = await this.api.getChatgptQueue().catch(() => null);
|
|
1364
|
+
if (!queue || !queue.profile_id || queue.count <= 0) return;
|
|
1365
|
+
|
|
1366
|
+
const profileId = queue.profile_id;
|
|
1367
|
+
// Skip launch if already running (check via Nstbrowser running list).
|
|
1368
|
+
let alreadyRunning = false;
|
|
1369
|
+
try {
|
|
1370
|
+
const running = await this.nst.getRunningBrowsers();
|
|
1371
|
+
alreadyRunning = running.some(b => b.profileId === profileId || (b.name && b.name.toLowerCase() === profileId.toLowerCase()));
|
|
1372
|
+
} catch {}
|
|
1373
|
+
if (alreadyRunning) {
|
|
1374
|
+
this._profileLastActivity[profileId] = Date.now();
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
console.log(`[chatgpt-dispatch] Launching ${profileId} (queue=${queue.count})`);
|
|
1379
|
+
try {
|
|
1380
|
+
// Reuse _launchRendererProfile contract — it just needs { nst_profile_id, os, proxy }.
|
|
1381
|
+
await this._launchRendererProfile({ nst_profile_id: profileId, os: 'windows', proxy: null });
|
|
1382
|
+
this._profileLastActivity[profileId] = Date.now();
|
|
1383
|
+
console.log(`[chatgpt-dispatch] ${profileId} launched`);
|
|
1384
|
+
} catch (err) {
|
|
1385
|
+
console.error(`[chatgpt-dispatch] Failed to launch ${profileId}: ${err.message}`);
|
|
1386
|
+
}
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
console.error(`[chatgpt-dispatch] Error: ${err.message}`);
|
|
1389
|
+
} finally {
|
|
1390
|
+
this._chatgptDispatching = false;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1317
1394
|
// ─── Profile Timeout ───────────────────────────────────────────────────────
|
|
1318
1395
|
// Close profiles that have been idle (no commands assigned) for too long.
|
|
1319
1396
|
|
|
@@ -1321,7 +1398,7 @@ class CommandPoller {
|
|
|
1321
1398
|
try {
|
|
1322
1399
|
if (!this.nst) return;
|
|
1323
1400
|
|
|
1324
|
-
const IDLE_TIMEOUT =
|
|
1401
|
+
const IDLE_TIMEOUT = 30 * 1000; // 30s — matches old extension-side behavior
|
|
1325
1402
|
const now = Date.now();
|
|
1326
1403
|
const running = await this.nst.getRunningBrowsers();
|
|
1327
1404
|
if (running.length === 0) return;
|