zames_pro 2.4.1 → 2.4.3

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.
@@ -64,6 +64,12 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
64
64
  const rawResponse = await browser.ask(message);
65
65
  await reportChat();
66
66
  transcript?.log('assistant_raw', { response: rawResponse });
67
+ // Пользователь прервал генерацию (Esc/Ctrl+C): не считаем это ответом
68
+ // модели и не запускаем инструменты — корректно завершаем задачу.
69
+ if (/^\s*\(прервано пользователем\)\s*$/.test(rawResponse)) {
70
+ transcript?.log('user_aborted');
71
+ return rawResponse;
72
+ }
67
73
  const parsed = parseToolCall(rawResponse);
68
74
  if (parsed) {
69
75
  const thought = extractPreToolText(rawResponse);
package/dist/browser.js CHANGED
@@ -316,20 +316,78 @@ export class DeepSeekBrowser {
316
316
  return '';
317
317
  return raw;
318
318
  }
319
+ // Найти кнопку Stop в интерфейсе DeepSeek. Полагаться только на класс
320
+ // нельзя: во время генерации кнопка отправки (та же circle-кнопка)
321
+ // меняет иконку на «квадрат» (stop), сохраняя классы. Поэтому:
322
+ // 1) проверяем явные селекторы (aria-label/текст Stop);
323
+ // 2) ищем primary circle-кнопку и смотрим на её иконку: rect/квадрат =
324
+ // stop, path/стрелка = send.
325
+ async _stopButtonVisible() {
326
+ const explicit = await this._findVisible(STOP_SELECTORS, 250);
327
+ if (explicit)
328
+ return true;
329
+ return await this.page
330
+ .evaluate(() => {
331
+ const btns = Array.from(document.querySelectorAll('div[role="button"], button'));
332
+ for (const b of btns) {
333
+ const cls = (b.className || '').toString();
334
+ if (!/ds-button--(circle|primary|filled)/i.test(cls))
335
+ continue;
336
+ const label = ((b.getAttribute('aria-label') || '') +
337
+ ' ' +
338
+ (b.getAttribute('title') || '') +
339
+ ' ' +
340
+ (b.textContent || '')).toLowerCase();
341
+ if (/stop|останов/.test(label))
342
+ return true;
343
+ // Иконка-квадрат = кнопка Stop; стрелка (path без rect) = отправка.
344
+ const svg = b.querySelector('svg');
345
+ if (svg && svg.querySelector('rect'))
346
+ return true;
347
+ }
348
+ return false;
349
+ })
350
+ .catch(() => false);
351
+ }
319
352
  async _isGenerating() {
320
- const stop = await this._findVisible(STOP_SELECTORS, 300);
321
- return !!stop;
353
+ return await this._stopButtonVisible();
322
354
  }
323
355
  async stopGeneration() {
324
356
  this._abort = true;
357
+ // 1) Пробуем явные селекторы Stop.
325
358
  const btn = await this._findVisible(STOP_SELECTORS, 500);
326
359
  if (btn) {
327
360
  try {
328
- await btn.click({ timeout: 1000 });
361
+ await btn.click({ timeout: 1500 });
329
362
  return true;
330
363
  }
331
364
  catch { }
332
365
  }
366
+ // 2) Кликаем по primary circle-кнопке, если у неё иконка-квадрат.
367
+ const clicked = await this.page
368
+ .evaluate(() => {
369
+ const btns = Array.from(document.querySelectorAll('div[role="button"], button'));
370
+ for (const b of btns) {
371
+ const cls = (b.className || '').toString();
372
+ if (!/ds-button--(circle|primary|filled)/i.test(cls))
373
+ continue;
374
+ const svg = b.querySelector('svg');
375
+ if (svg && svg.querySelector('rect')) {
376
+ b.click();
377
+ return true;
378
+ }
379
+ }
380
+ return false;
381
+ })
382
+ .catch(() => false);
383
+ if (clicked)
384
+ return true;
385
+ // 3) Крайний случай — нажать Esc в поле ввода (иногда останавливает).
386
+ try {
387
+ await this.page.keyboard.press('Escape');
388
+ return true;
389
+ }
390
+ catch { }
333
391
  return false;
334
392
  }
335
393
  async ask(prompt, { timeout = this.answerTimeoutMs } = {}) {
@@ -430,6 +488,10 @@ export class DeepSeekBrowser {
430
488
  await this.page.waitForTimeout(gap);
431
489
  }
432
490
  async _askOnce(prompt, { timeout }) {
491
+ // Сбрасываем флаг прерывания ТОЛЬКО в самом начале отправки. Раньше он
492
+ // сбрасывался перед циклом ожидания — и Esc/Ctrl+C, нажатые во время
493
+ // старта генерации, терялись, запрос не останавливался.
494
+ this._abort = false;
433
495
  const input = await this._findVisible(INPUT_SELECTORS, 10_000);
434
496
  if (!input) {
435
497
  throw new Error('Не найдено поле ввода. Запустите /debug-dom и поправьте INPUT_SELECTORS.');
@@ -468,6 +530,9 @@ export class DeepSeekBrowser {
468
530
  .catch(() => 0);
469
531
  let started = false;
470
532
  while (Date.now() < startDeadline) {
533
+ // Прервали (Esc/Ctrl+C) ещё до старта генерации — выходим сразу.
534
+ if (this._abort)
535
+ return '(прервано пользователем)';
471
536
  const pageText = await this._readPageText();
472
537
  if (isRateLimitText(pageText)) {
473
538
  throw new RateLimitError(pageText.slice(0, 300));
@@ -499,7 +564,6 @@ export class DeepSeekBrowser {
499
564
  const deadline = Date.now() + timeout;
500
565
  let last = '';
501
566
  let stable = 0;
502
- this._abort = false;
503
567
  while (Date.now() < deadline) {
504
568
  if (this._abort) {
505
569
  return last || '(прервано пользователем)';
package/dist/input.js CHANGED
@@ -29,7 +29,7 @@ const PASTE_END = ESC + '[201~';
29
29
  // Ширину выравниваем по максимуму (3), чтобы подсказка не смещалась.
30
30
  const DOTS = ['', '.', '..', '...'];
31
31
  const DOTS_PAD = ' ';
32
- const HINT = theme.dim(' · Enter — отправить, ↑/↓ — история, Ctrl+←/→ — по словам, Esc — стоп');
32
+ const HINT = theme.dim(' · Esc — стоп');
33
33
  function safeJson(v) {
34
34
  try {
35
35
  return JSON.stringify(v);
package/dist/spinner.js CHANGED
@@ -66,7 +66,7 @@ export function createSpinner() {
66
66
  };
67
67
  // Подсказка в строке статуса: во время работы агента терминал живой,
68
68
  // можно печатать следующее сообщение. Без неё это неочевидно.
69
- const HINT = theme.dim(' · печатай — Enter в очередь, Esc стоп');
69
+ const HINT = theme.dim(' · Esc — стоп');
70
70
  // Запуск анимированного статуса: коричневый текст + «бегущие» точки.
71
71
  const startThinking = () => {
72
72
  const base = theme.brown(stripEllipsis(randomThinkingPhrase()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {