zames_pro 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/dist/agent-loop.js +9 -47
- package/dist/browser.js +51 -38
- package/dist/index.js +9 -0
- package/package.json +1 -1
package/dist/agent-loop.js
CHANGED
|
@@ -6,9 +6,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
6
6
|
await browser.newChat();
|
|
7
7
|
transcript?.log('new_chat');
|
|
8
8
|
}
|
|
9
|
-
// Сообщаем вызывающему актуальный chat id.
|
|
10
|
-
// (он появляется только после первой отправки), поэтому зовём колбэк и
|
|
11
|
-
// здесь, и после первой реальной отправки ниже.
|
|
9
|
+
// Сообщаем вызывающему актуальный chat id.
|
|
12
10
|
let lastReportedChatId = null;
|
|
13
11
|
const reportChat = async () => {
|
|
14
12
|
let id = null;
|
|
@@ -43,29 +41,26 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
43
41
|
gitContext: gitText,
|
|
44
42
|
});
|
|
45
43
|
onThinking();
|
|
46
|
-
|
|
44
|
+
// system-prompt — отправка агента: с паузой (agent: true).
|
|
45
|
+
await browser.ask(systemPrompt, { timeout: 60_000, agent: true });
|
|
47
46
|
await reportChat();
|
|
48
47
|
}
|
|
49
48
|
let message = task;
|
|
50
49
|
transcript?.log('task', { task });
|
|
51
|
-
// Счётчик «ответ похож на tool-call, но не распознан». Чтобы модель,
|
|
52
|
-
// написавшая битый JSON/XML, не останавливала агента молча, мы просим
|
|
53
|
-
// её переотправить вызов. Ограничиваем число таких попыток.
|
|
54
50
|
let malformedRetries = 0;
|
|
55
51
|
const MAX_MALFORMED_RETRIES = 3;
|
|
56
|
-
// Счётчик «пустой/служебный ответ без вызова инструмента». Модель иногда
|
|
57
|
-
// отвечает статусом интерфейса (Reading…), тостом лимита или пустой
|
|
58
|
-
// строкой. Это НЕ финальный ответ — просим её продолжить, а не молча
|
|
59
|
-
// останавливаемся (раньше агент на этом вставал).
|
|
60
52
|
let stallRetries = 0;
|
|
61
53
|
const MAX_STALL_RETRIES = 5;
|
|
62
54
|
for (let i = 0; i < maxIterations; i++) {
|
|
63
55
|
onThinking();
|
|
64
|
-
|
|
56
|
+
// Первое сообщение (task) — пользовательский ввод: без паузы.
|
|
57
|
+
// Последующие (tool-result и просьбы переотправить) — агентские:
|
|
58
|
+
// с паузой, чтобы не упираться в лимит частоты.
|
|
59
|
+
const isFirst = i === 0;
|
|
60
|
+
const rawResponse = await browser.ask(message, { agent: !isFirst });
|
|
65
61
|
await reportChat();
|
|
66
62
|
transcript?.log('assistant_raw', { response: rawResponse });
|
|
67
|
-
// Пользователь прервал генерацию (Esc/Ctrl+C)
|
|
68
|
-
// модели и не запускаем инструменты — корректно завершаем задачу.
|
|
63
|
+
// Пользователь прервал генерацию (Esc/Ctrl+C).
|
|
69
64
|
if (/^\s*\(прервано пользователем\)\s*$/.test(rawResponse)) {
|
|
70
65
|
transcript?.log('user_aborted');
|
|
71
66
|
return rawResponse;
|
|
@@ -78,17 +73,12 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
78
73
|
}
|
|
79
74
|
const parsedCalls = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
|
|
80
75
|
if (parsedCalls.some((p) => p && p._permissive)) {
|
|
81
|
-
// Пишем в транскрипт (для отладки), но не сыпем в консоль.
|
|
82
76
|
transcript?.log('permissive_parse', { response: rawResponse });
|
|
83
77
|
if (debugLog) {
|
|
84
78
|
console.error('внимание: tool-call распознан нестрогим парсером');
|
|
85
79
|
}
|
|
86
80
|
}
|
|
87
81
|
if (!parsed) {
|
|
88
|
-
// Ответ не распознан как tool-call. Если он ПОХОЖ на попытку вызова
|
|
89
|
-
// (есть tool/invoke/parameter, но JSON/XML битый) — это почти всегда
|
|
90
|
-
// ошибка формата. Не считаем её финальным ответом (иначе агент молча
|
|
91
|
-
// остановится), а просим модель переотправить вызов корректно.
|
|
92
82
|
const looksLikeToolCall = /("tool"\s*:|\btool_calls?\b|\binvoke\b|\bparameter\b|DSML|function_call)/i.test(rawResponse) || /\{\s*"?(tool|name)"?\s*:/.test(rawResponse);
|
|
93
83
|
if (looksLikeToolCall && malformedRetries < MAX_MALFORMED_RETRIES) {
|
|
94
84
|
malformedRetries++;
|
|
@@ -110,9 +100,6 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
110
100
|
'Например: {"tool": "Read", "args": {"path": "src/index.js"}}';
|
|
111
101
|
continue;
|
|
112
102
|
}
|
|
113
|
-
// Пустой или служебный ответ (статус интерфейса, тост лимита, пробелы)
|
|
114
|
-
// не считаем финальным: просим модель продолжить. Иначе агент
|
|
115
|
-
// останавливается, хотя должен был вызвать инструмент.
|
|
116
103
|
const trimmed = (rawResponse || '').trim();
|
|
117
104
|
const looksService = !trimmed ||
|
|
118
105
|
trimmed.length < 2 ||
|
|
@@ -272,7 +259,6 @@ function tryParseArray(str) {
|
|
|
272
259
|
return null;
|
|
273
260
|
}
|
|
274
261
|
}
|
|
275
|
-
// Жадный разбор args для грязного JSON (незаэкранированные кавычки в строке).
|
|
276
262
|
function parseArgsGreedy(str) {
|
|
277
263
|
const result = {};
|
|
278
264
|
let i = str.indexOf('{') + 1;
|
|
@@ -416,8 +402,6 @@ function parseArgsPermissive(str) {
|
|
|
416
402
|
i = j + 1;
|
|
417
403
|
}
|
|
418
404
|
else if (str[i] === '{' || str[i] === '[') {
|
|
419
|
-
// Вложенный объект/массив: находим сбалансированный фрагмент и
|
|
420
|
-
// пытаемся распарсить его как JSON (или как вложенный permissive).
|
|
421
405
|
const open = str[i];
|
|
422
406
|
const close = open === '{' ? '}' : ']';
|
|
423
407
|
const end = findMatching(str, i, open, close);
|
|
@@ -522,15 +506,8 @@ function parseToolCallPermissive(text) {
|
|
|
522
506
|
const openIdx = text.indexOf('{', argsIdx);
|
|
523
507
|
if (openIdx === -1)
|
|
524
508
|
return null;
|
|
525
|
-
// Берём сбалансированный по скобкам фрагмент args (с учётом строк),
|
|
526
|
-
// а не первый попавшийся '}'. Иначе вложенные объекты/массивы или
|
|
527
|
-
// фигурные скобки внутри строк ломают разбор.
|
|
528
509
|
const endIdx = findMatching(text, openIdx, '{', '}');
|
|
529
510
|
if (tool === 'Edit') {
|
|
530
|
-
// Edit-аргументы часто содержат сырые кавычки и переводы строк, из-за
|
|
531
|
-
// чего балансировка скобок сбоит. parseEditArgs рассчитан ровно на
|
|
532
|
-
// такой случай, поэтому пробуем его и на «хвосте» до конца текста,
|
|
533
|
-
// а не только на сбалансированном фрагменте.
|
|
534
511
|
const balanced = endIdx === -1 ? null : text.slice(openIdx, endIdx + 1);
|
|
535
512
|
const tailToEnd = text.slice(openIdx);
|
|
536
513
|
for (const frag of [balanced, tailToEnd]) {
|
|
@@ -541,9 +518,6 @@ function parseToolCallPermissive(text) {
|
|
|
541
518
|
return { tool, args: e };
|
|
542
519
|
}
|
|
543
520
|
}
|
|
544
|
-
// Балансировка скобок сбоит, если в строковых значениях есть сырые
|
|
545
|
-
// кавычки/скобки (частый случай для Bash/Write с кодом внутри). Тогда
|
|
546
|
-
// пробуем разобрать args из «хвоста» до конца текста и жадным парсером.
|
|
547
521
|
const frags = [];
|
|
548
522
|
if (endIdx !== -1)
|
|
549
523
|
frags.push(text.slice(openIdx, endIdx + 1));
|
|
@@ -558,8 +532,6 @@ function parseToolCallPermissive(text) {
|
|
|
558
532
|
}
|
|
559
533
|
return null;
|
|
560
534
|
}
|
|
561
|
-
// Специализированный разбор args для Edit: ровно три поля path/old_string/
|
|
562
|
-
// new_string, значения могут содержать сырые кавычки и переводы строк.
|
|
563
535
|
function parseEditArgs(str) {
|
|
564
536
|
const keyRe = (name) => new RegExp('"' + name + '"\\s*:\\s*"');
|
|
565
537
|
const readValue = (name, nextNames) => {
|
|
@@ -665,9 +637,6 @@ export function parseToolCall(text) {
|
|
|
665
637
|
const second = tryParse(repaired);
|
|
666
638
|
if (second)
|
|
667
639
|
return second;
|
|
668
|
-
// Сырые переводы строк/табы внутри строковых значений (old_string,
|
|
669
|
-
// new_string, content) делают JSON невалидным. Экранируем их и
|
|
670
|
-
// пробуем снова, иначе многострочный вызов не распознаётся.
|
|
671
640
|
const ctrl = repairRawControlChars(raw);
|
|
672
641
|
const third = tryParse(ctrl);
|
|
673
642
|
if (third)
|
|
@@ -680,22 +649,15 @@ export function parseToolCall(text) {
|
|
|
680
649
|
parseToolCallPermissive(repairRawControlChars(cleaned));
|
|
681
650
|
if (permissive)
|
|
682
651
|
return { ...permissive, _permissive: true };
|
|
683
|
-
// Fallback: модель могла обернуть JSON в прозу и/или добавить мусорные
|
|
684
|
-
// теги после него. Отрезаем всё до первого '"tool"' и всё, что похоже
|
|
685
|
-
// на XML/DSML-хвост, затем пробуем permissive-разбор ещё раз.
|
|
686
652
|
const toolIdx = cleaned.search(/["']?tool["']?\s:/);
|
|
687
653
|
if (toolIdx > 0) {
|
|
688
654
|
let tail = cleaned.slice(toolIdx);
|
|
689
|
-
// Регексп обязан матчить многосимвольные теги (<|DSML|invoke ...>),
|
|
690
|
-
// поэтому <[^>]*>, а не <[^>]> — иначе DSML-хвост не срезается,
|
|
691
|
-
// permissive-разбор падает и агент молча останавливается.
|
|
692
655
|
tail = tail.replace(/<[^>]*>.*$/s, '').trim();
|
|
693
656
|
const tailPermissive = parseToolCallPermissive('{"' + tail) ||
|
|
694
657
|
parseToolCallPermissive('{"' + repairRawControlChars(tail));
|
|
695
658
|
if (tailPermissive)
|
|
696
659
|
return { ...tailPermissive, _permissive: true };
|
|
697
660
|
}
|
|
698
|
-
// Последний fallback: модель ответила XML/DSML-блоком вместо JSON.
|
|
699
661
|
const xmlCalls = parseXmlToolCalls(cleaned);
|
|
700
662
|
if (xmlCalls)
|
|
701
663
|
return Array.isArray(xmlCalls) ? xmlCalls : [xmlCalls];
|
package/dist/browser.js
CHANGED
|
@@ -39,8 +39,7 @@ 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
|
-
// отправлено — нужно подождать и повторить, а не считать это ответом модели.
|
|
42
|
+
// Ответ DeepSeek при превышении лимита частоты.
|
|
44
43
|
const RATE_LIMIT_RE = /(messages? too frequent|too many requests|rate limit|слишком часто|повторите позже|try again later)/i;
|
|
45
44
|
export function isRateLimitText(text) {
|
|
46
45
|
return RATE_LIMIT_RE.test(String(text || ''));
|
|
@@ -101,6 +100,10 @@ export class DeepSeekBrowser {
|
|
|
101
100
|
maxRateLimitRetries;
|
|
102
101
|
_lastSentAt;
|
|
103
102
|
_abort;
|
|
103
|
+
// Пользователь нажал Esc/Ctrl+C — «стоп» для ВСЕЙ текущей пачки задач
|
|
104
|
+
// (включая очередь). В отличие от _abort (сбрасывается на каждую
|
|
105
|
+
// отправку), этот флаг живёт до явного запуска новой задачи с промпта.
|
|
106
|
+
_stopped;
|
|
104
107
|
context;
|
|
105
108
|
page;
|
|
106
109
|
_netCapture;
|
|
@@ -122,6 +125,7 @@ export class DeepSeekBrowser {
|
|
|
122
125
|
this.maxRateLimitRetries = maxRateLimitRetries;
|
|
123
126
|
this._lastSentAt = 0;
|
|
124
127
|
this._abort = false;
|
|
128
|
+
this._stopped = false;
|
|
125
129
|
this._netCapture = '';
|
|
126
130
|
this._netCaptureAt = 0;
|
|
127
131
|
this._netChatId = null;
|
|
@@ -300,11 +304,31 @@ export class DeepSeekBrowser {
|
|
|
300
304
|
return out.replace(new RegExp(NL + '{3,}', 'g'), NL + NL).trim();
|
|
301
305
|
}, ANSWER_SELECTORS);
|
|
302
306
|
}
|
|
303
|
-
//
|
|
304
|
-
//
|
|
307
|
+
// Читаем ТОЛЬКО видимые тосты/уведомления/ошибки, а не весь текст
|
|
308
|
+
// страницы. Иначе ловим «try again later» из служебных/скрытых блоков
|
|
309
|
+
// и уходим в ложное ожидание лимита на 5 минут.
|
|
305
310
|
async _readPageText() {
|
|
306
311
|
return await this.page
|
|
307
|
-
.evaluate(() =>
|
|
312
|
+
.evaluate(() => {
|
|
313
|
+
const sels = [
|
|
314
|
+
'[role="alert"]',
|
|
315
|
+
'[class*="toast" i]',
|
|
316
|
+
'[class*="notification" i]',
|
|
317
|
+
'[class*="alert" i]',
|
|
318
|
+
'[class*="error" i]',
|
|
319
|
+
];
|
|
320
|
+
let out = '';
|
|
321
|
+
for (const s of sels) {
|
|
322
|
+
for (const el of Array.from(document.querySelectorAll(s))) {
|
|
323
|
+
const e = el;
|
|
324
|
+
const st = getComputedStyle(e);
|
|
325
|
+
if (st.display === 'none' || st.visibility === 'hidden')
|
|
326
|
+
continue;
|
|
327
|
+
out += ' ' + (e.innerText || '');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
})
|
|
308
332
|
.catch(() => '');
|
|
309
333
|
}
|
|
310
334
|
async _readLastAnswerTextClean() {
|
|
@@ -318,10 +342,7 @@ export class DeepSeekBrowser {
|
|
|
318
342
|
}
|
|
319
343
|
// Найти кнопку Stop в интерфейсе DeepSeek. Полагаться только на класс
|
|
320
344
|
// нельзя: во время генерации кнопка отправки (та же circle-кнопка)
|
|
321
|
-
// меняет иконку на «квадрат» (stop), сохраняя классы.
|
|
322
|
-
// 1) проверяем явные селекторы (aria-label/текст Stop);
|
|
323
|
-
// 2) ищем primary circle-кнопку и смотрим на её иконку: rect/квадрат =
|
|
324
|
-
// stop, path/стрелка = send.
|
|
345
|
+
// меняет иконку на «квадрат» (stop), сохраняя классы.
|
|
325
346
|
async _stopButtonVisible() {
|
|
326
347
|
const explicit = await this._findVisible(STOP_SELECTORS, 250);
|
|
327
348
|
if (explicit)
|
|
@@ -354,7 +375,7 @@ export class DeepSeekBrowser {
|
|
|
354
375
|
}
|
|
355
376
|
async stopGeneration() {
|
|
356
377
|
this._abort = true;
|
|
357
|
-
|
|
378
|
+
this._stopped = true;
|
|
358
379
|
const btn = await this._findVisible(STOP_SELECTORS, 500);
|
|
359
380
|
if (btn) {
|
|
360
381
|
try {
|
|
@@ -363,7 +384,6 @@ export class DeepSeekBrowser {
|
|
|
363
384
|
}
|
|
364
385
|
catch { }
|
|
365
386
|
}
|
|
366
|
-
// 2) Кликаем по primary circle-кнопке, если у неё иконка-квадрат.
|
|
367
387
|
const clicked = await this.page
|
|
368
388
|
.evaluate(() => {
|
|
369
389
|
const btns = Array.from(document.querySelectorAll('div[role="button"], button'));
|
|
@@ -382,7 +402,6 @@ export class DeepSeekBrowser {
|
|
|
382
402
|
.catch(() => false);
|
|
383
403
|
if (clicked)
|
|
384
404
|
return true;
|
|
385
|
-
// 3) Крайний случай — нажать Esc в поле ввода (иногда останавливает).
|
|
386
405
|
try {
|
|
387
406
|
await this.page.keyboard.press('Escape');
|
|
388
407
|
return true;
|
|
@@ -390,22 +409,21 @@ export class DeepSeekBrowser {
|
|
|
390
409
|
catch { }
|
|
391
410
|
return false;
|
|
392
411
|
}
|
|
393
|
-
async ask(prompt, { timeout = this.answerTimeoutMs } = {}) {
|
|
412
|
+
async ask(prompt, { timeout = this.answerTimeoutMs, agent = false, } = {}) {
|
|
394
413
|
let lastErr = null;
|
|
395
414
|
let attempt = 0;
|
|
396
415
|
let rateLimitRetries = 0;
|
|
397
416
|
while (attempt < this.askRetries) {
|
|
398
417
|
attempt++;
|
|
399
418
|
try {
|
|
400
|
-
return await this._askOnce(prompt, { timeout });
|
|
419
|
+
return await this._askOnce(prompt, { timeout, agent });
|
|
401
420
|
}
|
|
402
421
|
catch (e) {
|
|
403
422
|
lastErr = e;
|
|
404
|
-
// Лимит частоты: DeepSeek не принял сообщение. Ждём долго
|
|
405
|
-
//
|
|
406
|
-
//
|
|
423
|
+
// Лимит частоты: DeepSeek не принял сообщение. Ждём долго и
|
|
424
|
+
// повторяем отправку в ТОТ ЖЕ чат (без newChat — иначе теряется
|
|
425
|
+
// контекст). Паузы не расходуют обычные попытки ask().
|
|
407
426
|
if (e instanceof RateLimitError) {
|
|
408
|
-
attempt--;
|
|
409
427
|
rateLimitRetries++;
|
|
410
428
|
if (rateLimitRetries > this.maxRateLimitRetries) {
|
|
411
429
|
console.error(theme.error(`✖ DeepSeek не принял сообщение после ${rateLimitRetries} пауз по ${Math.ceil(this.rateLimitWaitMs / 60000)} мин.`));
|
|
@@ -413,7 +431,6 @@ export class DeepSeekBrowser {
|
|
|
413
431
|
}
|
|
414
432
|
console.error(theme.warn(`⏳ DeepSeek: «слишком часто». Жду ${Math.ceil(this.rateLimitWaitMs / 60000)} мин (${rateLimitRetries}/${this.maxRateLimitRetries}) и повторю...`));
|
|
415
433
|
await this.page.waitForTimeout(this.rateLimitWaitMs);
|
|
416
|
-
await this.newChat().catch(() => { });
|
|
417
434
|
continue;
|
|
418
435
|
}
|
|
419
436
|
console.error(`\n⚠ ask() попытка ${attempt}/${this.askRetries} провалилась: ${e.message}`);
|
|
@@ -478,26 +495,29 @@ export class DeepSeekBrowser {
|
|
|
478
495
|
await this.page.keyboard.insertText(text);
|
|
479
496
|
}
|
|
480
497
|
}
|
|
481
|
-
|
|
498
|
+
// Пауза между отправками. Применяется ТОЛЬКО к сообщениям агента
|
|
499
|
+
// (tool-result, system-prompt), чтобы не упираться в лимит частоты.
|
|
500
|
+
// Пользовательский ввод отправляется без задержки.
|
|
501
|
+
async _waitForSendSlot(agent) {
|
|
502
|
+
if (!agent)
|
|
503
|
+
return;
|
|
482
504
|
if (!this._lastSentAt)
|
|
483
505
|
return;
|
|
484
506
|
const gap = this.minSendIntervalMs - (Date.now() - this._lastSentAt);
|
|
485
507
|
if (gap <= 0)
|
|
486
508
|
return;
|
|
487
|
-
console.error(theme.warn(`⏳ пауза ${Math.ceil(gap / 1000)}с перед
|
|
509
|
+
console.error(theme.warn(`⏳ пауза ${Math.ceil(gap / 1000)}с перед отправкой...`));
|
|
488
510
|
await this.page.waitForTimeout(gap);
|
|
489
511
|
}
|
|
490
|
-
async _askOnce(prompt, { timeout }) {
|
|
491
|
-
// Сбрасываем флаг прерывания ТОЛЬКО в самом начале отправки.
|
|
492
|
-
// сбрасывался перед циклом ожидания — и Esc/Ctrl+C, нажатые во время
|
|
493
|
-
// старта генерации, терялись, запрос не останавливался.
|
|
512
|
+
async _askOnce(prompt, { timeout, agent }) {
|
|
513
|
+
// Сбрасываем флаг прерывания ТОЛЬКО в самом начале отправки.
|
|
494
514
|
this._abort = false;
|
|
495
515
|
const input = await this._findVisible(INPUT_SELECTORS, 10_000);
|
|
496
516
|
if (!input) {
|
|
497
517
|
throw new Error('Не найдено поле ввода. Запустите /debug-dom и поправьте INPUT_SELECTORS.');
|
|
498
518
|
}
|
|
499
519
|
const beforeText = await this._readLastAnswerTextClean().catch(() => '');
|
|
500
|
-
await this._waitForSendSlot();
|
|
520
|
+
await this._waitForSendSlot(agent);
|
|
501
521
|
this._netCapture = '';
|
|
502
522
|
this._netCaptureAt = 0;
|
|
503
523
|
await this._setInputText(input, prompt);
|
|
@@ -522,15 +542,13 @@ export class DeepSeekBrowser {
|
|
|
522
542
|
this._lastSentAt = Date.now();
|
|
523
543
|
// Ждём старта: либо появился Stop, либо изменился текст ответа,
|
|
524
544
|
// либо вырос общий объём текста на странице. Параллельно ловим
|
|
525
|
-
// тост о превышении лимита частоты
|
|
526
|
-
// приёма сообщения, и его надо обработать паузой и повтором.
|
|
545
|
+
// тост о превышении лимита частоты (только тосты, не весь body).
|
|
527
546
|
const startDeadline = Date.now() + 15_000;
|
|
528
547
|
const startBodyLen = await this.page
|
|
529
548
|
.evaluate(() => document.body.innerText.length)
|
|
530
549
|
.catch(() => 0);
|
|
531
550
|
let started = false;
|
|
532
551
|
while (Date.now() < startDeadline) {
|
|
533
|
-
// Прервали (Esc/Ctrl+C) ещё до старта генерации — выходим сразу.
|
|
534
552
|
if (this._abort)
|
|
535
553
|
return '(прервано пользователем)';
|
|
536
554
|
const pageText = await this._readPageText();
|
|
@@ -549,18 +567,13 @@ export class DeepSeekBrowser {
|
|
|
549
567
|
await this.page.waitForTimeout(300);
|
|
550
568
|
}
|
|
551
569
|
if (!started) {
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
if (isRateLimitText(pageText)) {
|
|
556
|
-
throw new RateLimitError(pageText.slice(0, 300));
|
|
557
|
-
}
|
|
570
|
+
// Больше НЕ проверяем лимит по всему тексту страницы — это давало
|
|
571
|
+
// ложные срабатывания и 5-минутные паузы. Просто сообщаем, что
|
|
572
|
+
// генерация не началась.
|
|
558
573
|
throw new Error('Ответ не начал генерироваться за 15с. Возможно, сообщение не отправилось.');
|
|
559
574
|
}
|
|
560
575
|
// Ждём, пока ответ перестанет меняться. Условие "не генерируется"
|
|
561
|
-
// проверяем через рост текста, а НЕ через _isGenerating()
|
|
562
|
-
// у DeepSeek ненадёжна, и если она не находится, ответ бы никогда не
|
|
563
|
-
// вернулся.
|
|
576
|
+
// проверяем через рост текста, а НЕ через _isGenerating().
|
|
564
577
|
const deadline = Date.now() + timeout;
|
|
565
578
|
let last = '';
|
|
566
579
|
let stable = 0;
|
package/dist/index.js
CHANGED
|
@@ -626,6 +626,9 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
626
626
|
// В TTY-режиме UI — это LineEditor: он владеет вводом (очередь, Esc,
|
|
627
627
|
// Ctrl+C) и рисует статус НАД постоянной строкой ввода. В не-TTY режиме
|
|
628
628
|
// (пайпы) — обычный спиннер + watchInput.
|
|
629
|
+
// Новая задача с промпта — сбрасываем «стоп» от прошлого прерывания.
|
|
630
|
+
browser._stopped = false;
|
|
631
|
+
browser._abort = false;
|
|
629
632
|
const ui = editor || mod.createSpinner();
|
|
630
633
|
const stopWatching = editor
|
|
631
634
|
? () => { }
|
|
@@ -667,6 +670,12 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
667
670
|
onChatReady,
|
|
668
671
|
debugLog: debug,
|
|
669
672
|
});
|
|
673
|
+
// Прервали (Esc/Ctrl+C) — не запускаем следующие задачи из очереди
|
|
674
|
+
// и очищаем её, чтобы «стоп» действительно останавливал всё.
|
|
675
|
+
if (browser._stopped) {
|
|
676
|
+
queue.length = 0;
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
670
679
|
if (!queue.length)
|
|
671
680
|
break;
|
|
672
681
|
const queued = queue.shift() ?? '';
|