zames_pro 2.28.2 → 2.29.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/browser.js CHANGED
@@ -160,6 +160,16 @@ const STOP_SELECTORS = [
160
160
  'button:has-text("Остановить")',
161
161
  'button[aria-label*="Stop" i]',
162
162
  ];
163
+ // DeepSeek's "Continue" button. With the reasoning ("Deep thinking") toggle
164
+ // on, the server caps the THINK phase: the reasoning stops mid-way and the UI
165
+ // offers a Continue button (this is NOT the same as a truncated turn with
166
+ // generation_err — there the answer failed; here the model simply paused).
167
+ // The agent must click it so the reasoning/answer keeps flowing, otherwise the
168
+ // turn sits idle until the operator presses Continue by hand.
169
+ //
170
+ // The button is found by scanning every div[role=button]/button and matching
171
+ // an EXACT short label ("Continue" / "Продолжить"), because the class names
172
+ // change and :has-text is not valid in a page-side querySelectorAll.
163
173
  // DeepSeek UI service statuses that are NOT the model's answer.
164
174
  // Otherwise the agent takes a status (Reading...) for an answer and breaks parsing.
165
175
  const STATUS_RE = /^(reading|thinking|searching|analyzing|generating|stop|остановить|читаю|думаю|поиск|анализ)[\s.…]*$/i;
@@ -177,6 +187,19 @@ const RATE_LIMIT_RE = /(messages? too frequent|too many requests|rate limit|сл
177
187
  // rate limit, these usually clear in seconds, so we retry quickly instead of
178
188
  // waiting minutes. "Server busy", 503, "temporarily unavailable", etc.
179
189
  const SERVER_BUSY_RE = /(server (is )?busy|server error|service (is )?unavailable|temporarily unavailable|internal server error|502|503|504|server overloaded|сервер занят|сервер перегружен|сервис недоступен|внутренняя ошибка|попробуйте позже)/i;
190
+ // DeepSeek turns a generation that FAILED mid-way into a truncated turn
191
+ // (`quasi_status: INCOMPLETE` + `finish_reason: generation_err`) and shows a
192
+ // "Continue" button in the UI. The failure is invisible to the answer text:
193
+ // the DOM keeps the partial answer (often the previous one), so the finish
194
+ // loop used to wait out the whole timeout and throw ds.send_no_new_answer.
195
+ // The SSE body IS available (net-capture keeps it as `_netCapture`), so we
196
+ // recognize the failed/truncated turn from it and retry instead of hanging.
197
+ const GENERATION_ERR_RE = /"finish_reason":\s*"generation_err"/i;
198
+ const INCOMPLETE_STATUS_RE = /"quasi_status","v":"INCOMPLETE"/i;
199
+ export function isGenerationIncompleteText(text) {
200
+ const t = String(text || '');
201
+ return GENERATION_ERR_RE.test(t) || INCOMPLETE_STATUS_RE.test(t);
202
+ }
180
203
  export function isRateLimitText(text) {
181
204
  return RATE_LIMIT_RE.test(String(text || ''));
182
205
  }
@@ -210,6 +233,20 @@ export class ServerBusyError extends Error {
210
233
  this.name = 'ServerBusyError';
211
234
  }
212
235
  }
236
+ // The generation was truncated by the server (`generation_err` / INCOMPLETE).
237
+ // Distinct from ServerBusyError: the DeepSeek backend answered, but the turn
238
+ // did not finish, and the UI offers a "Continue" button. The agent retries the
239
+ // same send (a new message into the chat) instead of waiting for an answer
240
+ // that will never arrive. This is what the operator hit with the reasoning
241
+ // (deep thinking) mode on: the long THINK phase makes the truncation far more
242
+ // likely, so with thinking off everything worked and with it on the turn kept
243
+ // stopping and showing "Continue".
244
+ export class GenerationIncompleteError extends Error {
245
+ constructor(detail) {
246
+ super('Generation incomplete (server truncated the answer). ' + detail);
247
+ this.name = 'GenerationIncompleteError';
248
+ }
249
+ }
213
250
  // ---------- profile cleanup ----------
214
251
  async function cleanSingletonFiles() {
215
252
  const names = ['SingletonLock', 'SingletonCookie', 'SingletonSocket'];
@@ -249,6 +286,12 @@ export class DeepSeekBrowser {
249
286
  maxRateLimitRetries;
250
287
  maxServerBusyRetries;
251
288
  serverBusyWaitMs;
289
+ // Retries for a turn the server truncated (generation_err / INCOMPLETE).
290
+ maxIncompleteRetries;
291
+ incompleteWaitMs;
292
+ // Click DeepSeek's "Continue" button automatically when the reasoning phase
293
+ // is paused by the server (deep thinking mode).
294
+ autoContinue;
252
295
  // Desired state of the DeepSeek chat toggles, applied before each send.
253
296
  // deepThinking: the "Deep thinking" toggle (reasoning; the reasoning text
254
297
  // is never read/shown). webSearch: the "Smart search" toggle.
@@ -307,7 +350,7 @@ export class DeepSeekBrowser {
307
350
  // must be stripped (DeepSeek's CDN answers 403 to it); computed in the
308
351
  // constructor, overridable by an explicit UA in the options/config.
309
352
  userAgent;
310
- constructor({ headless = false, debug = false, channel = 'chrome', answerTimeoutMs = 180000, askRetries = 3, stabilityChecks = 2, stabilityDelayMs = 400, minSendIntervalMs = 15000, rateLimitWaitMs = 300000, maxRateLimitRetries = 6, maxServerBusyRetries = 5, serverBusyWaitMs = 3000, deepThinking = false, webSearch = true, auth, locale, onAuthSave, userAgent, } = {}) {
353
+ constructor({ headless = false, debug = false, channel = 'chrome', answerTimeoutMs = 180000, askRetries = 3, stabilityChecks = 2, stabilityDelayMs = 400, minSendIntervalMs = 15000, rateLimitWaitMs = 300000, maxRateLimitRetries = 6, maxServerBusyRetries = 5, serverBusyWaitMs = 3000, maxIncompleteRetries = 4, incompleteWaitMs = 2000, autoContinue = true, deepThinking = false, webSearch = true, auth, locale, onAuthSave, userAgent, } = {}) {
311
354
  this.headless = headless;
312
355
  this.debug = debug;
313
356
  this.channel = channel;
@@ -320,6 +363,9 @@ export class DeepSeekBrowser {
320
363
  this.maxRateLimitRetries = maxRateLimitRetries;
321
364
  this.maxServerBusyRetries = maxServerBusyRetries;
322
365
  this.serverBusyWaitMs = serverBusyWaitMs;
366
+ this.maxIncompleteRetries = maxIncompleteRetries;
367
+ this.incompleteWaitMs = incompleteWaitMs;
368
+ this.autoContinue = autoContinue;
323
369
  this.deepThinking = deepThinking;
324
370
  this.webSearch = webSearch;
325
371
  this.auth = {
@@ -1017,6 +1063,45 @@ export class DeepSeekBrowser {
1017
1063
  async _isGenerating() {
1018
1064
  return await this._stopButtonVisible();
1019
1065
  }
1066
+ // Click the DeepSeek "Continue" button when it is visible (reasoning/answer
1067
+ // paused by the server). Returns true when a click happened.
1068
+ //
1069
+ // With the "Deep thinking" toggle on, DeepSeek caps the THINK phase and
1070
+ // shows Continue; the model does NOT resume by itself. Without this, a long
1071
+ // reasoning turn sat idle until the operator pressed Continue by hand. We
1072
+ // only act on an EXACT short label so a random "Continue" in prose (a button
1073
+ // inside a rendered answer, etc.) is never clicked.
1074
+ async _clickContinueIfVisible() {
1075
+ try {
1076
+ const clicked = await this.page.evaluate(() => {
1077
+ // Normalize a label: textContent + aria-label, whitespace collapsed.
1078
+ const labelOf = (b) => ((b.textContent || '') + ' ' + (b.getAttribute('aria-label') || ''))
1079
+ .replace(/\s+/g, ' ')
1080
+ .trim();
1081
+ // The button must be a SHORT exact label, otherwise a paragraph or a
1082
+ // rendered "Continue" inside the answer could be clicked by mistake.
1083
+ const isExact = (t) => /^(continue|продолжить|продолжение)\s*[.!…]?$/i.test(t);
1084
+ const cands = Array.from(document.querySelectorAll('div[role="button"], button'));
1085
+ for (const e of cands) {
1086
+ const t = labelOf(e);
1087
+ if (!isExact(t))
1088
+ continue;
1089
+ const st = getComputedStyle(e);
1090
+ if (st.display === 'none' || st.visibility === 'hidden')
1091
+ continue;
1092
+ if (!e.offsetParent && st.position !== 'fixed')
1093
+ continue;
1094
+ e.click();
1095
+ return true;
1096
+ }
1097
+ return false;
1098
+ });
1099
+ return !!clicked;
1100
+ }
1101
+ catch {
1102
+ return false;
1103
+ }
1104
+ }
1020
1105
  // DeepSeek exposes two toggle buttons above the input: "Deep thinking"
1021
1106
  // (reasoning) and "Smart search" (web search). Their labels are localized,
1022
1107
  // so we match by a loose regex on the visible text and read the state from
@@ -1051,6 +1136,14 @@ export class DeepSeekBrowser {
1051
1136
  getToggleStatesSync() {
1052
1137
  return this._toggles;
1053
1138
  }
1139
+ // Update the cached toggle states (used by the status icons) without a send.
1140
+ // Called from /config so the icons change immediately.
1141
+ setToggleState(s) {
1142
+ if (typeof s.deepThinking === 'boolean')
1143
+ this._toggles.deepThinking = s.deepThinking;
1144
+ if (typeof s.webSearch === 'boolean')
1145
+ this._toggles.webSearch = s.webSearch;
1146
+ }
1054
1147
  // The CURRENT state of the two DeepSeek toggles, read from the DOM
1055
1148
  // (aria-pressed) so the status line reflects what is actually on, not what
1056
1149
  // the config asked for. Falls back to the configured values when the buttons
@@ -1117,6 +1210,11 @@ export class DeepSeekBrowser {
1117
1210
  let attempt = 0;
1118
1211
  let rateLimitRetries = 0;
1119
1212
  let serverBusyRetries = 0;
1213
+ // Bumped when DeepSeek truncated the turn (`generation_err`/INCOMPLETE).
1214
+ // The retry does NOT consume a regular ask() attempt: it is a server-side
1215
+ // hiccup, not a failed send, and it is more likely in the reasoning
1216
+ // (deep thinking) mode, where the long THINK phase gets cut off.
1217
+ let incompleteRetries = 0;
1120
1218
  while (attempt < this.askRetries) {
1121
1219
  attempt++;
1122
1220
  try {
@@ -1174,6 +1272,28 @@ export class DeepSeekBrowser {
1174
1272
  return '(прервано пользователем)';
1175
1273
  continue;
1176
1274
  }
1275
+ // The turn was TRUNCATED by the server (`generation_err`, INCOMPLETE):
1276
+ // DeepSeek answered with a partial (often empty) answer and shows a
1277
+ // "Continue" button. In reasoning mode this is much more likely. We
1278
+ // resend the SAME prompt into the chat (that is what the Continue
1279
+ // button does) instead of waiting for an answer that will never come.
1280
+ if (e instanceof GenerationIncompleteError) {
1281
+ incompleteRetries++;
1282
+ if (incompleteRetries > this.maxIncompleteRetries) {
1283
+ console.error(theme.error(this._t('ds.incomplete_give_up', {
1284
+ attempt: incompleteRetries,
1285
+ })));
1286
+ throw e;
1287
+ }
1288
+ console.error(theme.warn(this._t('ds.incomplete_retry', {
1289
+ attempt: incompleteRetries,
1290
+ max: this.maxIncompleteRetries,
1291
+ })));
1292
+ const aborted = await this._sleepInterruptible(this.incompleteWaitMs);
1293
+ if (aborted)
1294
+ return '(прервано пользователем)';
1295
+ continue;
1296
+ }
1177
1297
  console.error('\n' +
1178
1298
  theme.warn(this._t('ds.ask_retry', {
1179
1299
  attempt,
@@ -1519,6 +1639,20 @@ export class DeepSeekBrowser {
1519
1639
  if (isServerBusyText(pageText)) {
1520
1640
  throw new ServerBusyError(pageText.slice(0, 300));
1521
1641
  }
1642
+ // The turn was truncated by the server: the SSE body carries
1643
+ // finish_reason=generation_err / quasi_status=INCOMPLETE and the UI
1644
+ // shows "Continue". Retry instead of waiting out the whole timeout.
1645
+ if (this._netCapture &&
1646
+ this._netCaptureAt >= this._lastSentAt &&
1647
+ isGenerationIncompleteText(this._netCapture)) {
1648
+ throw new GenerationIncompleteError(this._netCapture.slice(-300));
1649
+ }
1650
+ // Reasoning-mode pause during the THINK phase: click Continue so the
1651
+ // generation resumes (the button appears before any RESPONSE text, so
1652
+ // the start-wait would otherwise spin until the deadline).
1653
+ if (this.autoContinue && (await this._clickContinueIfVisible())) {
1654
+ this._askDebug('CLICKED Continue (start-loop)');
1655
+ }
1522
1656
  const cur = await this._readLastAnswerTextCleanDom().catch(() => '');
1523
1657
  const bodyLen = await this.page
1524
1658
  .evaluate(() => document.body.innerText.length)
@@ -1652,6 +1786,22 @@ export class DeepSeekBrowser {
1652
1786
  throw new ServerBusyError(pageText.slice(0, 300));
1653
1787
  }
1654
1788
  }
1789
+ // The server truncated the turn (generation_err / INCOMPLETE). The DOM
1790
+ // keeps the partial answer, so without this check the loop waits out the
1791
+ // whole timeout and throws ds.send_no_new_answer — the operator sees the
1792
+ // agent "stop" with a Continue button in the chat. Retry the send.
1793
+ if (this._netCapture &&
1794
+ this._netCaptureAt >= this._lastSentAt &&
1795
+ isGenerationIncompleteText(this._netCapture)) {
1796
+ throw new GenerationIncompleteError(this._netCapture.slice(-300));
1797
+ }
1798
+ // Reasoning-mode pause: DeepSeek caps the THINK phase and shows a
1799
+ // Continue button; the model does not resume by itself. Click it so the
1800
+ // answer keeps flowing instead of waiting for the operator.
1801
+ if (this.autoContinue && (await this._clickContinueIfVisible())) {
1802
+ this._askDebug('CLICKED Continue (finish-loop)');
1803
+ continue;
1804
+ }
1655
1805
  const netFresh = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
1656
1806
  // The DOM may be empty while the answer is already in the network
1657
1807
  // capture (a resumed chat re-renders its history, the answer selector
package/dist/config.js CHANGED
@@ -43,6 +43,9 @@ export const DEFAULTS = {
43
43
  minSendIntervalMs: 15000,
44
44
  rateLimitWaitMs: 300000,
45
45
  maxRateLimitRetries: 6,
46
+ maxIncompleteRetries: 4,
47
+ incompleteWaitMs: 2000,
48
+ autoContinue: true,
46
49
  deepThinking: false,
47
50
  webSearch: true,
48
51
  auth: {
@@ -230,6 +233,28 @@ export const CONFIG_SCHEMA = [
230
233
  labelKey: 'cfg.f.browser_maxRateLimitRetries',
231
234
  groupKey: 'cfg.group.browser',
232
235
  },
236
+ {
237
+ path: 'browser.maxIncompleteRetries',
238
+ type: 'number',
239
+ min: 0,
240
+ max: 100,
241
+ labelKey: 'cfg.f.browser_maxIncompleteRetries',
242
+ groupKey: 'cfg.group.browser',
243
+ },
244
+ {
245
+ path: 'browser.incompleteWaitMs',
246
+ type: 'number',
247
+ min: 0,
248
+ max: 600000,
249
+ labelKey: 'cfg.f.browser_incompleteWaitMs',
250
+ groupKey: 'cfg.group.browser',
251
+ },
252
+ {
253
+ path: 'browser.autoContinue',
254
+ type: 'boolean',
255
+ labelKey: 'cfg.f.browser_autoContinue',
256
+ groupKey: 'cfg.group.browser',
257
+ },
233
258
  {
234
259
  path: 'browser.deepThinking',
235
260
  type: 'boolean',
package/dist/i18n.js CHANGED
@@ -533,6 +533,14 @@ const CATALOG = {
533
533
  ru: '⚠ Ответ не получен ({attempt}/{max}): {error} — повторяю...',
534
534
  en: '⚠ No answer yet ({attempt}/{max}): {error} — retrying...',
535
535
  },
536
+ 'ds.incomplete_retry': {
537
+ ru: '⏳ DeepSeek оборвал ответ (кнопка Continue в чате) — повторяю отправку ({attempt}/{max})...',
538
+ en: '⏳ DeepSeek truncated the answer (the Continue button in the chat) — resending ({attempt}/{max})...',
539
+ },
540
+ 'ds.incomplete_give_up': {
541
+ ru: '✖ DeepSeek {attempt} раз подряд оборвал ответ, не дойдя до конца. Возможно, включён режим размышления — попробуй выключить его (browser.deepThinking) или повтори позже.',
542
+ en: '✖ DeepSeek truncated the answer {attempt} times in a row. The reasoning mode (deep thinking) may be on — try turning it off (browser.deepThinking) or retry later.',
543
+ },
536
544
  'ds.ask_restart_browser': {
537
545
  ru: '⚠ Браузер потерял страницу — перезапускаю и вхожу заново...',
538
546
  en: '⚠ The browser lost the page — restarting and signing in again...',
@@ -858,6 +866,18 @@ const CATALOG = {
858
866
  ru: 'Число повторов при лимите',
859
867
  en: 'Rate-limit retry count',
860
868
  },
869
+ 'cfg.f.browser_maxIncompleteRetries': {
870
+ ru: 'Повторы при обрыве ответа (Continue)',
871
+ en: 'Retries when the answer is truncated (Continue)',
872
+ },
873
+ 'cfg.f.browser_incompleteWaitMs': {
874
+ ru: 'Пауза перед повтором после обрыва, мс',
875
+ en: 'Wait before retrying a truncated turn, ms',
876
+ },
877
+ 'cfg.f.browser_autoContinue': {
878
+ ru: 'Автонажатие кнопки Continue (режим размышления)',
879
+ en: 'Auto-click Continue (reasoning mode)',
880
+ },
861
881
  'cfg.f.browser_deepThinking': {
862
882
  ru: 'Глубокое мышление (долго; размышления не выводятся)',
863
883
  en: 'Deep thinking (slow; reasoning is hidden)',
package/dist/index.js CHANGED
@@ -1407,13 +1407,22 @@ async function main() {
1407
1407
  }
1408
1408
  // Reflect browser toggles on the LIVE browser object. The browser is
1409
1409
  // created once at startup, so without this a /config set browser.*
1410
- // change would only apply after a restart.
1410
+ // change would only apply after a restart. We also update the cached
1411
+ // toggle states (_toggles) that the status icons read, and repaint the
1412
+ // editor, so the 🧠/🌐 icons change IMMEDIATELY (previously they only
1413
+ // updated after the next send, when _applyToggles() ran).
1411
1414
  if (path === 'browser.deepThinking' || path === 'browser.webSearch') {
1412
1415
  const b = browser;
1413
1416
  if (path === 'browser.deepThinking')
1414
1417
  b.deepThinking = !!value;
1415
1418
  else
1416
1419
  b.webSearch = !!value;
1420
+ b.setToggleState({
1421
+ deepThinking: b.deepThinking,
1422
+ webSearch: b.webSearch,
1423
+ });
1424
+ if (editor)
1425
+ editor.refreshStatus();
1417
1426
  }
1418
1427
  }
1419
1428
  function configSetRaw(field, raw) {
package/dist/input.js CHANGED
@@ -720,6 +720,11 @@ export class LineEditor {
720
720
  this.rendered = true;
721
721
  this.cursorRowFromTop = top + lay.cursorRow;
722
722
  }
723
+ // Public repaint hook: called when an external state that the status line
724
+ // reads (e.g. the toggle icons) changed, so it can be redrawn immediately.
725
+ refreshStatus() {
726
+ this._render();
727
+ }
723
728
  _render() {
724
729
  this._eraseBlock();
725
730
  this._writeBlock();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.28.2",
3
+ "version": "2.29.1",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {