zames_pro 2.1.3 → 2.2.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.
@@ -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
- for (let attempt = 1; attempt <= this.askRetries; attempt++) {
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
@@ -38,6 +38,8 @@ export const DEFAULTS = {
38
38
  stabilityChecks: 3,
39
39
  stabilityDelayMs: 1000,
40
40
  minSendIntervalMs: 15000,
41
+ rateLimitWaitMs: 300000,
42
+ maxRateLimitRetries: 6,
41
43
  },
42
44
  };
43
45
  export function loadConfig() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.1.3",
3
+ "version": "2.2.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {