zames_pro 2.1.3 → 2.2.1
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/dist/agent-loop.js +33 -0
- package/dist/browser.js +58 -3
- package/dist/config.js +2 -0
- package/package.json +1 -1
package/dist/agent-loop.js
CHANGED
|
@@ -53,6 +53,12 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
53
53
|
// её переотправить вызов. Ограничиваем число таких попыток.
|
|
54
54
|
let malformedRetries = 0;
|
|
55
55
|
const MAX_MALFORMED_RETRIES = 3;
|
|
56
|
+
// Счётчик «пустой/служебный ответ без вызова инструмента». Модель иногда
|
|
57
|
+
// отвечает статусом интерфейса (Reading…), тостом лимита или пустой
|
|
58
|
+
// строкой. Это НЕ финальный ответ — просим её продолжить, а не молча
|
|
59
|
+
// останавливаемся (раньше агент на этом вставал).
|
|
60
|
+
let stallRetries = 0;
|
|
61
|
+
const MAX_STALL_RETRIES = 5;
|
|
56
62
|
for (let i = 0; i < maxIterations; i++) {
|
|
57
63
|
onThinking();
|
|
58
64
|
const rawResponse = await browser.ask(message);
|
|
@@ -98,6 +104,33 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
98
104
|
'Например: {"tool": "Read", "args": {"path": "src/index.js"}}';
|
|
99
105
|
continue;
|
|
100
106
|
}
|
|
107
|
+
// Пустой или служебный ответ (статус интерфейса, тост лимита, пробелы)
|
|
108
|
+
// не считаем финальным: просим модель продолжить. Иначе агент
|
|
109
|
+
// останавливается, хотя должен был вызвать инструмент.
|
|
110
|
+
const trimmed = (rawResponse || '').trim();
|
|
111
|
+
const looksService = !trimmed ||
|
|
112
|
+
trimmed.length < 2 ||
|
|
113
|
+
/^(reading|thinking|searching|analyzing|generating|stop|остановить|читаю|думаю|поиск|анализ)[\s.…]*$/i.test(trimmed) ||
|
|
114
|
+
/(messages? too frequent|too many requests|rate limit|слишком часто|try again later)/i.test(trimmed);
|
|
115
|
+
if (looksService && stallRetries < MAX_STALL_RETRIES) {
|
|
116
|
+
stallRetries++;
|
|
117
|
+
transcript?.log('stall_retry', {
|
|
118
|
+
attempt: stallRetries,
|
|
119
|
+
response: rawResponse,
|
|
120
|
+
});
|
|
121
|
+
if (debugLog) {
|
|
122
|
+
console.error('внимание: пустой/служебный ответ, прошу продолжить (попытка ' +
|
|
123
|
+
stallRetries +
|
|
124
|
+
'/' +
|
|
125
|
+
MAX_STALL_RETRIES +
|
|
126
|
+
')');
|
|
127
|
+
}
|
|
128
|
+
message =
|
|
129
|
+
'Продолжи выполнение задачи. Если нужен инструмент — ответь РОВНО ' +
|
|
130
|
+
'одним JSON-объектом вызова: {"tool": "...", "args": {...}}. ' +
|
|
131
|
+
'Если задача выполнена — вызови инструмент respond с итоговым сообщением.';
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
101
134
|
onAssistantMessage(rawResponse);
|
|
102
135
|
transcript?.log('assistant_final', { message: rawResponse });
|
|
103
136
|
return rawResponse;
|
package/dist/browser.js
CHANGED
|
@@ -39,6 +39,20 @@ const STOP_SELECTORS = [
|
|
|
39
39
|
// Служебные статусы интерфейса DeepSeek, которые НЕ являются ответом модели.
|
|
40
40
|
// Иначе агент принимает статус (Reading...) за ответ и ломает разбор.
|
|
41
41
|
const STATUS_RE = /^(reading|thinking|searching|analyzing|generating|stop|остановить|читаю|думаю|поиск|анализ)[\s.…]*$/i;
|
|
42
|
+
// Ответ DeepSeek при превышении лимита частоты. В этом случае сообщение НЕ
|
|
43
|
+
// отправлено — нужно подождать и повторить, а не считать это ответом модели.
|
|
44
|
+
const RATE_LIMIT_RE = /(messages? too frequent|too many requests|rate limit|слишком часто|повторите позже|try again later)/i;
|
|
45
|
+
export function isRateLimitText(text) {
|
|
46
|
+
return RATE_LIMIT_RE.test(String(text || ''));
|
|
47
|
+
}
|
|
48
|
+
// Ошибка «слишком часто»: отличается от прочих, чтобы ask() ждал долго
|
|
49
|
+
// (лимиты DeepSeek сбрасываются за минуты) и повторял отправку сам.
|
|
50
|
+
export class RateLimitError extends Error {
|
|
51
|
+
constructor(detail) {
|
|
52
|
+
super('Messages too frequent. Try again later. ' + detail);
|
|
53
|
+
this.name = 'RateLimitError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
42
56
|
// ---------- profile cleanup ----------
|
|
43
57
|
async function killStaleChrome() {
|
|
44
58
|
if (process.platform !== 'win32')
|
|
@@ -83,6 +97,8 @@ export class DeepSeekBrowser {
|
|
|
83
97
|
stabilityChecks;
|
|
84
98
|
stabilityDelayMs;
|
|
85
99
|
minSendIntervalMs;
|
|
100
|
+
rateLimitWaitMs;
|
|
101
|
+
maxRateLimitRetries;
|
|
86
102
|
_lastSentAt;
|
|
87
103
|
_abort;
|
|
88
104
|
context;
|
|
@@ -93,7 +109,7 @@ export class DeepSeekBrowser {
|
|
|
93
109
|
_netSniff;
|
|
94
110
|
_netSniffLimit;
|
|
95
111
|
_netHookInstalled;
|
|
96
|
-
constructor({ headless = false, debug = false, channel = 'chrome', answerTimeoutMs = 180000, askRetries = 3, stabilityChecks = 3, stabilityDelayMs = 1000, minSendIntervalMs = 15000, } = {}) {
|
|
112
|
+
constructor({ headless = false, debug = false, channel = 'chrome', answerTimeoutMs = 180000, askRetries = 3, stabilityChecks = 3, stabilityDelayMs = 1000, minSendIntervalMs = 15000, rateLimitWaitMs = 300000, maxRateLimitRetries = 6, } = {}) {
|
|
97
113
|
this.headless = headless;
|
|
98
114
|
this.debug = debug;
|
|
99
115
|
this.channel = channel;
|
|
@@ -102,6 +118,8 @@ export class DeepSeekBrowser {
|
|
|
102
118
|
this.stabilityChecks = stabilityChecks;
|
|
103
119
|
this.stabilityDelayMs = stabilityDelayMs;
|
|
104
120
|
this.minSendIntervalMs = minSendIntervalMs;
|
|
121
|
+
this.rateLimitWaitMs = rateLimitWaitMs;
|
|
122
|
+
this.maxRateLimitRetries = maxRateLimitRetries;
|
|
105
123
|
this._lastSentAt = 0;
|
|
106
124
|
this._abort = false;
|
|
107
125
|
this._netCapture = '';
|
|
@@ -282,6 +300,13 @@ export class DeepSeekBrowser {
|
|
|
282
300
|
return out.replace(new RegExp(NL + '{3,}', 'g'), NL + NL).trim();
|
|
283
301
|
}, ANSWER_SELECTORS);
|
|
284
302
|
}
|
|
303
|
+
// Полный видимый текст страницы — по нему ловим тосты/ошибки интерфейса
|
|
304
|
+
// DeepSeek (в частности «Messages too frequent. Try again later.»).
|
|
305
|
+
async _readPageText() {
|
|
306
|
+
return await this.page
|
|
307
|
+
.evaluate(() => document.body.innerText || '')
|
|
308
|
+
.catch(() => '');
|
|
309
|
+
}
|
|
285
310
|
async _readLastAnswerTextClean() {
|
|
286
311
|
const raw = await this._readLastAnswerText().catch(() => '');
|
|
287
312
|
const t = (raw || '').trim();
|
|
@@ -309,12 +334,30 @@ export class DeepSeekBrowser {
|
|
|
309
334
|
}
|
|
310
335
|
async ask(prompt, { timeout = this.answerTimeoutMs } = {}) {
|
|
311
336
|
let lastErr = null;
|
|
312
|
-
|
|
337
|
+
let attempt = 0;
|
|
338
|
+
let rateLimitRetries = 0;
|
|
339
|
+
while (attempt < this.askRetries) {
|
|
340
|
+
attempt++;
|
|
313
341
|
try {
|
|
314
342
|
return await this._askOnce(prompt, { timeout });
|
|
315
343
|
}
|
|
316
344
|
catch (e) {
|
|
317
345
|
lastErr = e;
|
|
346
|
+
// Лимит частоты: DeepSeek не принял сообщение. Ждём долго —
|
|
347
|
+
// лимиты сбрасываются за минуты, — и повторяем отправку сами.
|
|
348
|
+
// Эти паузы НЕ расходуют обычные попытки ask().
|
|
349
|
+
if (e instanceof RateLimitError) {
|
|
350
|
+
attempt--;
|
|
351
|
+
rateLimitRetries++;
|
|
352
|
+
if (rateLimitRetries > this.maxRateLimitRetries) {
|
|
353
|
+
console.error(theme.error(`✖ DeepSeek не принял сообщение после ${rateLimitRetries} пауз по ${Math.ceil(this.rateLimitWaitMs / 60000)} мин.`));
|
|
354
|
+
throw e;
|
|
355
|
+
}
|
|
356
|
+
console.error(theme.warn(`⏳ DeepSeek: «слишком часто». Жду ${Math.ceil(this.rateLimitWaitMs / 60000)} мин (${rateLimitRetries}/${this.maxRateLimitRetries}) и повторю...`));
|
|
357
|
+
await this.page.waitForTimeout(this.rateLimitWaitMs);
|
|
358
|
+
await this.newChat().catch(() => { });
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
318
361
|
console.error(`\n⚠ ask() попытка ${attempt}/${this.askRetries} провалилась: ${e.message}`);
|
|
319
362
|
if (/closed|crash|Target page|browser/i.test(e.message)) {
|
|
320
363
|
console.error('⚠ перезапускаю браузер...');
|
|
@@ -416,13 +459,19 @@ export class DeepSeekBrowser {
|
|
|
416
459
|
}
|
|
417
460
|
this._lastSentAt = Date.now();
|
|
418
461
|
// Ждём старта: либо появился Stop, либо изменился текст ответа,
|
|
419
|
-
// либо вырос общий объём текста на странице.
|
|
462
|
+
// либо вырос общий объём текста на странице. Параллельно ловим
|
|
463
|
+
// тост о превышении лимита частоты — это НЕ ответ модели, а отказ
|
|
464
|
+
// приёма сообщения, и его надо обработать паузой и повтором.
|
|
420
465
|
const startDeadline = Date.now() + 15_000;
|
|
421
466
|
const startBodyLen = await this.page
|
|
422
467
|
.evaluate(() => document.body.innerText.length)
|
|
423
468
|
.catch(() => 0);
|
|
424
469
|
let started = false;
|
|
425
470
|
while (Date.now() < startDeadline) {
|
|
471
|
+
const pageText = await this._readPageText();
|
|
472
|
+
if (isRateLimitText(pageText)) {
|
|
473
|
+
throw new RateLimitError(pageText.slice(0, 300));
|
|
474
|
+
}
|
|
426
475
|
const gen = await this._isGenerating();
|
|
427
476
|
const cur = await this._readLastAnswerTextClean().catch(() => '');
|
|
428
477
|
const bodyLen = await this.page
|
|
@@ -435,6 +484,12 @@ export class DeepSeekBrowser {
|
|
|
435
484
|
await this.page.waitForTimeout(300);
|
|
436
485
|
}
|
|
437
486
|
if (!started) {
|
|
487
|
+
// Перед ошибкой ещё раз проверим на лимит частоты: иногда тост
|
|
488
|
+
// появляется с задержкой и не успевает попасть в ранние проверки.
|
|
489
|
+
const pageText = await this._readPageText();
|
|
490
|
+
if (isRateLimitText(pageText)) {
|
|
491
|
+
throw new RateLimitError(pageText.slice(0, 300));
|
|
492
|
+
}
|
|
438
493
|
throw new Error('Ответ не начал генерироваться за 15с. Возможно, сообщение не отправилось.');
|
|
439
494
|
}
|
|
440
495
|
// Ждём, пока ответ перестанет меняться. Условие "не генерируется"
|
package/dist/config.js
CHANGED