zames_pro 2.29.7 → 2.29.9

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.
@@ -357,6 +357,11 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
357
357
  const looksService = !trimmed ||
358
358
  trimmed.length < 2 ||
359
359
  /^(reading|thinking|searching|analyzing|generating|stop|остановить|читаю|думаю|поиск|анализ)[\s.…]*$/i.test(trimmed) ||
360
+ // DeepSeek sometimes returns its OWN safety-classification block
361
+ // ("<ds_safety>…</ds_safety>Safe") INSTEAD of an answer. It is not a
362
+ // tool-call and not a real final answer — treat it as a service
363
+ // answer so the loop re-asks instead of stopping on it.
364
+ /<ds_safety>|<\/ds_safety>/i.test(trimmed) ||
360
365
  (trimmed.length <= 200 &&
361
366
  /(messages? too frequent|too many requests|rate limit|server (is )?busy|service (is )?unavailable|слишком часто|try again later)/i.test(trimmed));
362
367
  if (looksService && stallRetries < MAX_STALL_RETRIES) {
package/dist/browser.js CHANGED
@@ -167,9 +167,10 @@ const STOP_SELECTORS = [
167
167
  // The agent must click it so the reasoning/answer keeps flowing, otherwise the
168
168
  // turn sits idle until the operator presses Continue by hand.
169
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.
170
+ // The button is found by Playwright's accessible name (getByRole) with an
171
+ // EXACT match, falling back to a raw DOM scan. The class names change between
172
+ // builds, so we do not rely on them.
173
+ const CONTINUE_NAMES = ['Continue', 'Продолжить', 'Продолжение'];
173
174
  // DeepSeek UI service statuses that are NOT the model's answer.
174
175
  // Otherwise the agent takes a status (Reading...) for an answer and breaks parsing.
175
176
  const STATUS_RE = /^(reading|thinking|searching|analyzing|generating|stop|остановить|читаю|думаю|поиск|анализ)[\s.…]*$/i;
@@ -1109,28 +1110,37 @@ export class DeepSeekBrowser {
1109
1110
  // text there is exactly the "agent stopped with a Continue button" bug.
1110
1111
  async _continueButtonVisible() {
1111
1112
  try {
1112
- return await this.page.evaluate(() => {
1113
- const labelOf = (b) => ((b.textContent || '') + ' ' + (b.getAttribute('aria-label') || ''))
1114
- .replace(/\s+/g, ' ')
1115
- .trim();
1116
- const isExact = (t) => /^(continue|продолжить|продолжение)\s*[.!…]?$/i.test(t);
1117
- const cands = Array.from(document.querySelectorAll('div[role="button"], button'));
1118
- for (const e of cands) {
1119
- if (!isExact(labelOf(e)))
1120
- continue;
1121
- const st = getComputedStyle(e);
1122
- if (st.display === 'none' || st.visibility === 'hidden')
1123
- continue;
1124
- if (!e.offsetParent && st.position !== 'fixed')
1125
- continue;
1113
+ for (const name of CONTINUE_NAMES) {
1114
+ const loc = this.page.getByRole('button', { name }).first();
1115
+ if ((await loc.count().catch(() => 0)) === 0)
1116
+ continue;
1117
+ if (await loc.isVisible().catch(() => false))
1126
1118
  return true;
1127
- }
1128
- return false;
1129
- });
1119
+ }
1130
1120
  }
1131
- catch {
1121
+ catch { }
1122
+ // Fallback: the raw DOM scan (older/newer DeepSeek builds where the role
1123
+ // is absent).
1124
+ return this.page
1125
+ .evaluate(() => {
1126
+ const labelOf = (b) => ((b.textContent || '') + ' ' + (b.getAttribute('aria-label') || ''))
1127
+ .replace(/\s+/g, ' ')
1128
+ .trim();
1129
+ const isExact = (t) => /^(continue|продолжить|продолжение)\s*[.!…]?$/i.test(t);
1130
+ const cands = Array.from(document.querySelectorAll('div[role="button"], button'));
1131
+ for (const e of cands) {
1132
+ if (!isExact(labelOf(e)))
1133
+ continue;
1134
+ const st = getComputedStyle(e);
1135
+ if (st.display === 'none' || st.visibility === 'hidden')
1136
+ continue;
1137
+ const r = e.getBoundingClientRect();
1138
+ if (r.width > 0 && r.height > 0)
1139
+ return true;
1140
+ }
1132
1141
  return false;
1133
- }
1142
+ })
1143
+ .catch(() => false);
1134
1144
  }
1135
1145
  // Click the DeepSeek "Continue" button when it is visible (reasoning/answer
1136
1146
  // paused by the server). Returns true when a click happened.
@@ -1141,14 +1151,29 @@ export class DeepSeekBrowser {
1141
1151
  // only act on an EXACT short label so a random "Continue" in prose (a button
1142
1152
  // inside a rendered answer, etc.) is never clicked.
1143
1153
  async _clickContinueIfVisible() {
1154
+ // 1) Playwright's OWN click on the accessible name — this is a TRUSTED
1155
+ // event and reliably triggers React handlers. The old in-page
1156
+ // dispatchEvent/click often did nothing, which is exactly why the
1157
+ // operator saw the button and the "жму Continue" message but no resume.
1158
+ for (const name of CONTINUE_NAMES) {
1159
+ try {
1160
+ const loc = this.page.getByRole('button', { name }).first();
1161
+ if ((await loc.count().catch(() => 0)) === 0)
1162
+ continue;
1163
+ if (!(await loc.isVisible().catch(() => false)))
1164
+ continue;
1165
+ await loc.click({ timeout: 2000 });
1166
+ return true;
1167
+ }
1168
+ catch { }
1169
+ }
1170
+ // 2) Fallback: raw DOM scan + full pointer/mouse event sequence (an older
1171
+ // build without a proper role, or a click intercepted by an overlay).
1144
1172
  try {
1145
1173
  const clicked = await this.page.evaluate(() => {
1146
- // Normalize a label: textContent + aria-label, whitespace collapsed.
1147
1174
  const labelOf = (b) => ((b.textContent || '') + ' ' + (b.getAttribute('aria-label') || ''))
1148
1175
  .replace(/\s+/g, ' ')
1149
1176
  .trim();
1150
- // The button must be a SHORT exact label, otherwise a paragraph or a
1151
- // rendered "Continue" inside the answer could be clicked by mistake.
1152
1177
  const isExact = (t) => /^(continue|продолжить|продолжение)\s*[.!…]?$/i.test(t);
1153
1178
  const cands = Array.from(document.querySelectorAll('div[role="button"], button'));
1154
1179
  for (const e of cands) {
@@ -1158,12 +1183,9 @@ export class DeepSeekBrowser {
1158
1183
  const st = getComputedStyle(e);
1159
1184
  if (st.display === 'none' || st.visibility === 'hidden')
1160
1185
  continue;
1161
- if (!e.offsetParent && st.position !== 'fixed')
1162
- continue;
1163
- // DeepSeek's button is a React component listening for POINTER/mouse
1164
- // events, not just a bare click(). A plain e.click() sometimes does
1165
- // nothing; dispatching the full sequence is what a real press does.
1166
1186
  const rect = e.getBoundingClientRect();
1187
+ if (rect.width === 0 || rect.height === 0)
1188
+ continue;
1167
1189
  const cx = rect.left + rect.width / 2;
1168
1190
  const cy = rect.top + rect.height / 2;
1169
1191
  const opts = {
@@ -1952,8 +1974,12 @@ export class DeepSeekBrowser {
1952
1974
  continue;
1953
1975
  }
1954
1976
  }
1955
- if (truncated) {
1956
- throw new GenerationIncompleteError(this._netBody.slice(-300));
1977
+ // No Continue button to click (DeepSeek shows "Stopped" without one) OR
1978
+ // autoContinue is off. Do NOT wait out the whole timeout — the turn is
1979
+ // dead (truncated / finished with no answer), so tell ask() to RESEND
1980
+ // the prompt right away (that is the only remaining way to continue).
1981
+ if (truncated || noAnswer) {
1982
+ throw new GenerationIncompleteError((this._netBody || 'finished without answer').slice(-300));
1957
1983
  }
1958
1984
  // Reasoning-mode pause: DeepSeek caps the THINK phase and shows a
1959
1985
  // Continue button; the model does not resume by itself. Click it so the
package/dist/i18n.js CHANGED
@@ -534,8 +534,8 @@ const CATALOG = {
534
534
  en: '⚠ No answer yet ({attempt}/{max}): {error} — retrying...',
535
535
  },
536
536
  'ds.incomplete_retry': {
537
- ru: '⏳ DeepSeek оборвал ответ — жму «Continue», чтобы продолжить (повтор {attempt}/{max})...',
538
- en: '⏳ DeepSeek truncated the answer — clicking Continue to resume (retry {attempt}/{max})...',
537
+ ru: '⏳ DeepSeek оборвал ответ и кнопки Continue нет — переотправляю задачу ({attempt}/{max})...',
538
+ en: '⏳ DeepSeek truncated the answer and there is no Continue button — resending the task ({attempt}/{max})...',
539
539
  },
540
540
  'ds.incomplete_give_up': {
541
541
  ru: '✖ DeepSeek {attempt} раз подряд оборвал ответ, не дойдя до конца. Возможно, включён режим размышления — попробуй выключить его (browser.deepThinking) или повтори позже.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.29.7",
3
+ "version": "2.29.9",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {