vg-coder-cli 2.0.61 → 2.0.63
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/package.json
CHANGED
package/src/server/api-server.js
CHANGED
|
@@ -668,6 +668,25 @@ class ApiServer {
|
|
|
668
668
|
res.json({ workers: taskQueue.listWorkers() });
|
|
669
669
|
});
|
|
670
670
|
|
|
671
|
+
// Reset cooldown manually. Useful khi operator chắc chắn AI Studio đã hết
|
|
672
|
+
// rate-limit (vd quan sát qua noVNC submit prompt được) nhưng server timer
|
|
673
|
+
// 30 phút chưa expire. Body: { email: "alice@gmail.com" } hoặc {} cho tất cả.
|
|
674
|
+
this.app.post('/api/workers/reset-cooldown', (req, res) => {
|
|
675
|
+
const targetEmail = (req.body?.email || '').toString().toLowerCase().trim();
|
|
676
|
+
const reset = [];
|
|
677
|
+
for (const w of taskQueue.workers.values()) {
|
|
678
|
+
if (w.status !== 'rate_limited') continue;
|
|
679
|
+
if (targetEmail && w.email !== targetEmail) continue;
|
|
680
|
+
w.status = 'idle';
|
|
681
|
+
w.cooldownUntil = 0;
|
|
682
|
+
w.lastFailureCode = null;
|
|
683
|
+
w.lastFailureMessage = null;
|
|
684
|
+
reset.push({ id: w.id, email: w.email });
|
|
685
|
+
}
|
|
686
|
+
if (reset.length) setImmediate(() => taskQueue._drain());
|
|
687
|
+
res.json({ ok: true, reset, count: reset.length });
|
|
688
|
+
});
|
|
689
|
+
|
|
671
690
|
this.app.get('/api/launchers', (req, res) => {
|
|
672
691
|
res.json({ launchers: taskQueue.listLaunchers() });
|
|
673
692
|
});
|
package/src/server/task-queue.js
CHANGED
|
@@ -109,13 +109,21 @@ class TaskQueue {
|
|
|
109
109
|
|
|
110
110
|
listWorkers() {
|
|
111
111
|
const out = [];
|
|
112
|
+
const now = Date.now();
|
|
112
113
|
for (const w of this.workers.values()) {
|
|
114
|
+
const cd = w.cooldownUntil || 0;
|
|
115
|
+
const remainingMs = cd > now ? cd - now : 0;
|
|
113
116
|
out.push({
|
|
114
117
|
id: w.id,
|
|
115
118
|
email: w.email,
|
|
116
119
|
status: w.status,
|
|
117
120
|
currentTaskId: w.currentTaskId,
|
|
118
|
-
cooldownUntil:
|
|
121
|
+
cooldownUntil: cd,
|
|
122
|
+
cooldownUntilISO: cd ? new Date(cd).toISOString() : null,
|
|
123
|
+
cooldownRemainingMs: remainingMs,
|
|
124
|
+
cooldownRemainingMinutes: Math.ceil(remainingMs / 60_000),
|
|
125
|
+
lastFailureCode: w.lastFailureCode || null,
|
|
126
|
+
lastFailureMessage: w.lastFailureMessage || null,
|
|
119
127
|
lastSeen: w.lastSeen,
|
|
120
128
|
meta: { domain: w.meta?.domain, chatId: w.meta?.chatId, registeredAt: w.meta?.registeredAt }
|
|
121
129
|
});
|
|
@@ -616,10 +624,14 @@ class TaskQueue {
|
|
|
616
624
|
if (RL_CODES.has(code)) {
|
|
617
625
|
worker.status = 'rate_limited';
|
|
618
626
|
worker.cooldownUntil = Date.now() + RATE_LIMIT_COOLDOWN_MS;
|
|
627
|
+
worker.lastFailureCode = code;
|
|
628
|
+
worker.lastFailureMessage = message.slice(0, 300);
|
|
619
629
|
worker.currentTaskId = null;
|
|
620
630
|
console.log(chalk.yellow(`[TaskQueue] Worker ${worker.email} rate-limited until ${new Date(worker.cooldownUntil).toLocaleTimeString()}`));
|
|
621
631
|
} else {
|
|
622
632
|
// Plain failure — worker becomes idle again
|
|
633
|
+
worker.lastFailureCode = code;
|
|
634
|
+
worker.lastFailureMessage = message.slice(0, 300);
|
|
623
635
|
worker.currentTaskId = null;
|
|
624
636
|
worker.status = 'idle';
|
|
625
637
|
}
|
|
@@ -133,12 +133,15 @@ async function fetchAsFile(url, name, mime) {
|
|
|
133
133
|
async function waitForNewAssistantTurn(baselineCount, timeoutMs = 90_000) {
|
|
134
134
|
const start = Date.now();
|
|
135
135
|
while (Date.now() - start < timeoutMs) {
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
if (rl) { const e = new Error(rl.message); e.code = rl.code; throw e; }
|
|
140
|
-
|
|
136
|
+
// Chỉ surface rate-limit/quota khi turn count đã vượt baseline. Trước
|
|
137
|
+
// đó turn cuối vẫn là turn cũ (có thể là error từ session restore) —
|
|
138
|
+
// detectRateLimit match phải khớp với turn MỚI.
|
|
141
139
|
const turns = document.querySelectorAll('ms-chat-turn');
|
|
140
|
+
if (turns.length > baselineCount) {
|
|
141
|
+
// Turn mới đã render — bây giờ mới check (toast vẫn check global ok)
|
|
142
|
+
const rl = detectRateLimit();
|
|
143
|
+
if (rl) { const e = new Error(rl.message); e.code = rl.code; throw e; }
|
|
144
|
+
}
|
|
142
145
|
if (turns.length >= baselineCount + 2) {
|
|
143
146
|
const last = turns[turns.length - 1];
|
|
144
147
|
const role = last.querySelector('[data-turn-role]')?.getAttribute('data-turn-role');
|
|
@@ -164,8 +167,15 @@ function detectRateLimit() {
|
|
|
164
167
|
if (/failed to generate/i.test(t)) return { code: 'generation_failed', message: t.slice(0, 500) };
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
// Inline model-error
|
|
168
|
-
|
|
170
|
+
// Inline model-error — CHỈ check trong turn CUỐI (response cho prompt vừa
|
|
171
|
+
// submit). AI Studio restore chat session từ cookies → turn cũ với
|
|
172
|
+
// .model-error rate-limit có thể vẫn trong DOM dù limit đã hết.
|
|
173
|
+
// Global querySelector('.model-error') match turn cũ → mark cooldown
|
|
174
|
+
// infinite loop. Scope theo last ms-chat-turn để chỉ trigger trên response
|
|
175
|
+
// mới.
|
|
176
|
+
const turns = document.querySelectorAll('ms-chat-turn');
|
|
177
|
+
const lastTurn = turns[turns.length - 1];
|
|
178
|
+
const modelErr = lastTurn?.querySelector('.model-error');
|
|
169
179
|
if (modelErr) {
|
|
170
180
|
const text = (modelErr.textContent || '').trim();
|
|
171
181
|
if (/rate limit/i.test(text)) return { code: 'rate_limit_exceeded', message: text };
|