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 +143 -1
- package/dist/config.js +25 -0
- package/dist/i18n.js +20 -0
- package/package.json +1 -1
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)',
|