zames_pro 2.4.0 → 2.4.2

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/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 } = {}) {
@@ -337,6 +395,8 @@ export class DeepSeekBrowser {
337
395
  let attempt = 0;
338
396
  let rateLimitRetries = 0;
339
397
  while (attempt < this.askRetries) {
398
+ if (this._abort)
399
+ return '(прервано пользователем)';
340
400
  attempt++;
341
401
  try {
342
402
  return await this._askOnce(prompt, { timeout });
@@ -430,6 +490,10 @@ export class DeepSeekBrowser {
430
490
  await this.page.waitForTimeout(gap);
431
491
  }
432
492
  async _askOnce(prompt, { timeout }) {
493
+ // Сбрасываем флаг прерывания ТОЛЬКО в самом начале отправки. Раньше он
494
+ // сбрасывался перед циклом ожидания — и Esc/Ctrl+C, нажатые во время
495
+ // старта генерации, терялись, запрос не останавливался.
496
+ this._abort = false;
433
497
  const input = await this._findVisible(INPUT_SELECTORS, 10_000);
434
498
  if (!input) {
435
499
  throw new Error('Не найдено поле ввода. Запустите /debug-dom и поправьте INPUT_SELECTORS.');
@@ -468,6 +532,9 @@ export class DeepSeekBrowser {
468
532
  .catch(() => 0);
469
533
  let started = false;
470
534
  while (Date.now() < startDeadline) {
535
+ // Прервали (Esc/Ctrl+C) ещё до старта генерации — выходим сразу.
536
+ if (this._abort)
537
+ return '(прервано пользователем)';
471
538
  const pageText = await this._readPageText();
472
539
  if (isRateLimitText(pageText)) {
473
540
  throw new RateLimitError(pageText.slice(0, 300));
@@ -499,7 +566,6 @@ export class DeepSeekBrowser {
499
566
  const deadline = Date.now() + timeout;
500
567
  let last = '';
501
568
  let stable = 0;
502
- this._abort = false;
503
569
  while (Date.now() < deadline) {
504
570
  if (this._abort) {
505
571
  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);
@@ -38,6 +38,14 @@ function safeJson(v) {
38
38
  return String(v);
39
39
  }
40
40
  }
41
+ // Сколько визуальных строк занимает текст при данной ширине терминала
42
+ // (с учётом переноса). Нужно, чтобы курсор/стирание блока не сбивались,
43
+ // когда статус длиннее ширины терминала и переносится на 2+ строки.
44
+ export function visRows(s, cols) {
45
+ const width = Math.max(1, Number(cols) || 80);
46
+ const len = visLen(s);
47
+ return Math.max(1, Math.ceil(len / width));
48
+ }
41
49
  // Видимая длина строки без ANSI-последовательностей.
42
50
  export function visLen(s) {
43
51
  s = String(s);
@@ -251,7 +259,9 @@ export class LineEditor {
251
259
  let top = 0;
252
260
  if (this.statusText) {
253
261
  out += this.statusText + NL;
254
- top = 1;
262
+ // Статус может переноситься на несколько строк — учитываем это,
263
+ // иначе стирание блока промахивается и статусы «стопкой» копятся.
264
+ top = visRows(this.statusText, cols);
255
265
  }
256
266
  const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
257
267
  out += lay.rows.map((r) => r.prefix + r.text).join(NL);
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.0",
3
+ "version": "2.4.2",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {