zames_pro 2.29.0 → 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
@@ -1125,6 +1210,11 @@ export class DeepSeekBrowser {
1125
1210
  let attempt = 0;
1126
1211
  let rateLimitRetries = 0;
1127
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;
1128
1218
  while (attempt < this.askRetries) {
1129
1219
  attempt++;
1130
1220
  try {
@@ -1182,6 +1272,28 @@ export class DeepSeekBrowser {
1182
1272
  return '(прервано пользователем)';
1183
1273
  continue;
1184
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
+ }
1185
1297
  console.error('\n' +
1186
1298
  theme.warn(this._t('ds.ask_retry', {
1187
1299
  attempt,
@@ -1527,6 +1639,20 @@ export class DeepSeekBrowser {
1527
1639
  if (isServerBusyText(pageText)) {
1528
1640
  throw new ServerBusyError(pageText.slice(0, 300));
1529
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
+ }
1530
1656
  const cur = await this._readLastAnswerTextCleanDom().catch(() => '');
1531
1657
  const bodyLen = await this.page
1532
1658
  .evaluate(() => document.body.innerText.length)
@@ -1660,6 +1786,22 @@ export class DeepSeekBrowser {
1660
1786
  throw new ServerBusyError(pageText.slice(0, 300));
1661
1787
  }
1662
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
+ }
1663
1805
  const netFresh = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
1664
1806
  // The DOM may be empty while the answer is already in the network
1665
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.29.0",
3
+ "version": "2.29.1",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {