zames_pro 2.72.8 → 2.73.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,90 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.73.0] - 2026-10-11
11
+
12
+ ### Added
13
+
14
+ - **qwen:** a second chat provider (`chat.qwen.ai`), selectable with
15
+ `/provider <deepseek|qwen>`, so the operator can switch models without
16
+ leaving zames. Adds `src/qwen-ui.ts` (selectors), `src/qwen-net-capture.ts`
17
+ (phase-aware SSE parser), `src/qwen-browser.ts` (`QwenBrowser`), a separate
18
+ persistent profile (`~/.zames/qwen-profile`), `/model` to list/pick a Qwen
19
+ model, and file attachments via the mode menu's "Upload attachment" item.
20
+ - **provider:** switching `/provider` mid-session now carries the conversation
21
+ over: the current chat is summarized (on the old backend) and persisted as a
22
+ handoff (`src/provider-handoff.ts`); the next launch posts it as the first
23
+ message of the new provider's chat, so context is not lost. Falls back to a
24
+ local history dump when the model summary is unusable. Unit-tested.
25
+ - **provider:** a `PROVIDERS` registry (`src/providers.ts`) is the SINGLE
26
+ source of truth (ids, labels, `/chat` URL prefix), so adding a backend
27
+ (ChatGPT, Gemini, …) is a data change plus the provider's own modules — not a
28
+ dozen ternaries. `/provider` accepts any registered id; the config enum, the
29
+ `{provider}` label and `/doctor` read from the registry. Unit-tested
30
+ (`test/providers.test.ts`).
31
+ - **provider:** a `ChatProvider` interface (`src/types.ts`) both backends
32
+ implement; the agent loop only depends on `BrowserLike` and never sees a
33
+ concrete class. Operator notices (`ds.answer_timeout`, `ds.tool_retry`, …)
34
+ now use a `{provider}` label so they never say "DeepSeek" while driving Qwen.
35
+
36
+ ### Changed
37
+
38
+ - **ui:** the status line shows a provider badge ([Qwen]/[DeepSeek]) so the
39
+ active backend is always visible in a multi-provider session.
40
+ - **qwen:** `listChats` / `fetchChatMessages` now use Qwen's OWN API
41
+ (`GET /api/v2/chats/?page=1&exclude_project=true` and
42
+ `GET /api/v2/chats/<id>?direction=up&limit=N`) with the DOM scrape as a
43
+ fallback, so `/chats` and `/resume` work on a signed-in account (verified
44
+ live: 9 chats with titles, 20 messages). Parsers are pure + tested.
45
+ - **qwen:** password login (`_autoLogin`) fixed to the verified order (email ->
46
+ "Log in with a password" -> password -> submit); the email-CODE flow needs the
47
+ operator to read the mail, so a saved password is preferred. Verified live.
48
+ - **qwen:** the context counter now uses `input_tokens` (the prompt size) —
49
+ the true "used context" — instead of `total_tokens` (which adds the answer),
50
+ so the status bar matches DeepSeek's semantics.
51
+ - **qwen:** thinking / web search are mapped onto Qwen's own UI: deepThinking
52
+ ON selects the "Thinking" mode (OFF → "Fast") in the
53
+ `.qwen-thinking-selector` dropdown, and webSearch toggles the "Web search"
54
+ item in the "+" mode menu. `/thinking` and `/web` therefore work on Qwen too.
55
+ - **qwen:** no 15 s send throttle — a live experiment (4 back-to-back sends at
56
+ 0 ms gap) hit no rate limit, so `qwen.minSendIntervalMs` defaults to 1000 ms.
57
+ The agent-loop watchdog deadline is derived from the Qwen answer timeout
58
+ (a cold guest turn can take minutes), not the DeepSeek 240 s default.
59
+ - **provider:** sessions are tagged with the ACTIVE provider
60
+ (`saveSession({…, provider})`), so `/resume`/`/last`/auto-restore only ever
61
+ reopen a chat in the backend that created it (a chat id is meaningless to the
62
+ other one). `loadLastSession(workdir, provider)` filters accordingly.
63
+ - **qwen:** the restored-dialogue scraper moved to a PURE `src/qwen-chats.ts`
64
+ (mirroring `browser-chats.ts`) and now keeps only the OUTERMOST message
65
+ container — the broad `[class*="message"]` chain matched an outer and an
66
+ inner block, so every turn printed twice. `listChats` opens the sidebar first
67
+ and matches `/c/<id>` links. Compact / auto-compact use the active provider's
68
+ own answer timeout (`providerAnswerTimeoutMs()`), not DeepSeek's.
69
+
70
+ ### Fixed
71
+
72
+ - **qwen:** recover from a lost page/context mid-run (relaunch + re-login, and
73
+ re-navigate to the chat if the input is gone) instead of failing every retry.
74
+ - **qwen:** the guest daily limit (`getstsToken` → `RateLimited`) fails fast
75
+ with a "sign in" message instead of a 20-minute rate-limit wait.
76
+ - **qwen:** the real sign-in flow is **email → one-time code from the letter**
77
+ (not email+password): `_promptAndLogin()` now sends the email, asks the
78
+ operator for the code in the terminal, submits it, and verifies the session
79
+ from the page (guest header CTAs absent). `/provider login` re-runs it.
80
+ - **qwen:** a longer send-button wait when files are attached (the upload keeps
81
+ the button disabled).
82
+
83
+ ## [2.72.9] - 2026-10-10
84
+
85
+ ### Fixed
86
+
87
+ - **release:** do not duplicate the Unreleased body into the version section
88
+ - **context:** raise the context budget and split agents/memory pools
89
+ - **browser:** handle the DeepSeek email MFA dialog on login
90
+ - **subagent:** fail safe when the parent chat id is unknown
91
+ - **tools:** honor a skill's allowed-tools frontmatter
92
+ - **context:** keep newest MEMORY.md notes and write facts to the project memory
93
+
10
94
  ## [2.72.8] - 2026-10-09
11
95
 
12
96
  ### Fixed
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">zames_pro</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>A terminal coding agent that drives chat.deepseek.com through Playwright — no API key required.</strong>
8
+ <strong>A terminal coding agent that drives real web chat LLMs (DeepSeek, Qwen, …) through a browser — no API key required.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -22,7 +22,9 @@
22
22
  <strong>English</strong> | <a href="#readme-ru">Русский</a>
23
23
  </p>
24
24
 
25
- A terminal coding agent that works on top of [chat.deepseek.com](https://chat.deepseek.com/) through Playwright.
25
+ A terminal coding agent that works on top of real web chat LLMs
26
+ ([chat.deepseek.com](https://chat.deepseek.com/), [chat.qwen.ai](https://chat.qwen.ai/), …)
27
+ through Playwright.
26
28
  In spirit it is similar to Claude Code / Codex CLI: it starts in the current
27
29
  directory, reads and edits files, runs commands, and commits to git.
28
30
 
@@ -58,9 +60,14 @@ and runs right after it, in the same chat. `Esc` aborts the current generation,
58
60
  ## How it works
59
61
 
60
62
  zames does not call the model API. It launches a headless Chromium with a
61
- persistent profile, signs in to `chat.deepseek.com` like a human, types the task
63
+ persistent profile, signs in to a chat web app like a human, types the task
62
64
  into the chat box, and reads the answer back.
63
65
 
66
+ Two chat backends are available, switched with `/provider <deepseek|qwen>`
67
+ (restart required): **DeepSeek** (`chat.deepseek.com`, the default) and
68
+ **Qwen** (`chat.qwen.ai`). Each keeps its own persistent profile, so switching
69
+ never signs the other one out.
70
+
64
71
  <p align="center">
65
72
  <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="how zames works" width="860">
66
73
  </p>
@@ -84,6 +91,7 @@ Key pieces:
84
91
 
85
92
  - [Quick start](#quick-start) · [Features](#features) · [Why zames?](#why-zames) · [Requirements](#requirements)
86
93
  - [How it works](#how-it-works) · [Installation](#installation) · [Signing in](#signing-in) · [Usage](#usage)
94
+ - [Switching providers (DeepSeek / Qwen)](#switching-providers-deepseek--qwen)
87
95
  - [Tools](#tools) · [Slash commands](#slash-commands)
88
96
  - [Project context, skills and memory](#project-context-skills-and-memory) · [MCP (external tools)](#mcp-external-tools) · [Configuration](#configuration)
89
97
  - [FAQ](#faq) · [Links](#links) · [License](#license)
@@ -122,11 +130,12 @@ Key pieces:
122
130
  ## Requirements
123
131
 
124
132
  - Node.js >= 20
125
- - A DeepSeek account. On first launch zames asks for your DeepSeek
126
- login/password in the terminal (and stores them in `~/.zames/config.json`
127
- after a successful sign-in, so a later logout is handled automatically
128
- without asking you again). You can also sign in manually in the browser
129
- window when the browser is headed.
133
+ - An account on the chat you want to use — **DeepSeek** (`chat.deepseek.com`,
134
+ the default) or **Qwen** (`chat.qwen.ai`). Qwen also works in guest mode for
135
+ a few messages. On first launch zames asks for your login in the terminal
136
+ (and stores it in `~/.zames/config.json` after a successful sign-in, so a
137
+ later logout is handled automatically without asking you again). You can also
138
+ sign in manually in the browser window when the browser is headed.
130
139
 
131
140
  ## Signing in
132
141
 
@@ -159,6 +168,36 @@ version). You do not need `--headed` just to log in.
159
168
  Credentials and toggles can also be edited from `/config`
160
169
  (`browser.auth.username`, `browser.auth.password`, `browser.auth.saveSession`).
161
170
 
171
+ ## Switching providers (DeepSeek / Qwen)
172
+
173
+ zames can drive either **DeepSeek** (`chat.deepseek.com`, the default) or
174
+ **Qwen** (`chat.qwen.ai`):
175
+
176
+ ```bash
177
+ /provider qwen # switch to Qwen (persisted); restart zames to take effect
178
+ /provider deepseek # switch back
179
+ /provider status # show the active provider / model / login state
180
+ /provider login # (re)run the sign-in check
181
+ /model # list Qwen models
182
+ /model qwen3.7-plus # pick one
183
+ ```
184
+
185
+ Notes:
186
+
187
+ - The two backends use **separate persistent profiles**
188
+ (`~/.zames/profile` for DeepSeek, `~/.zames/qwen-profile` for Qwen), so
189
+ switching never signs the other one out. A provider switch takes effect on
190
+ the next launch — the browser is created once at startup.
191
+ - Qwen works in **guest mode** without an account, but a guest is limited to a
192
+ handful of messages per day; for long agent runs sign in (email / Google /
193
+ GitHub — no phone required). zames auto-logins from the saved credentials or
194
+ asks in the terminal, exactly like DeepSeek. `/model` changes the Qwen model
195
+ at runtime (no restart).
196
+ - File attachments on Qwen go through the mode menu's **Upload attachment**
197
+ item; DeepSeek uses its own paperclip.
198
+ - `/doctor` shows the active provider and the UI health of that provider's
199
+ page.
200
+
162
201
  ## Installation
163
202
 
164
203
  ```bash
@@ -302,7 +341,11 @@ Codex CLI:
302
341
  context size in tokens (DeepSeek's `accumulated_token_usage`).
303
342
  - /export [file] — write the session transcript to a Markdown file
304
343
  (zames-export-<stamp>.md by default).
305
- - /doctor — diagnose node, git, config, browser, clipboard and MCP.
344
+ - /doctor — diagnose node, git, config, browser, clipboard, MCP and the
345
+ active chat provider (it also reports that provider's UI health).
346
+ - /provider <deepseek|qwen> — switch the chat backend (restart to apply);
347
+ /provider status shows the active provider/model/login; /provider login
348
+ re-runs the sign-in check. /model [id] lists or picks a Qwen model at runtime.
306
349
  - /add-dir <path> — validate an extra directory (the sandbox is fixed at
307
350
  startup; relaunch with --dir to write there).
308
351
  - /resume <n> (after /chats) and /resume-id <id> — open a chat and PRINT its
@@ -329,7 +372,10 @@ tests`). /cron "<min> <hour> <dom> <month> <dow>" <task> — run on a schedule.
329
372
  /jobs [rm <id>|clear] lists and stops them. A fired job is put into the same
330
373
  message queue you type into, so it runs when the agent is free (never mid-
331
374
  generation) and still respects the send throttle.
332
- - /thinking [on|off] and /web [on|off] — toggle Deep thinking / Smart search.
375
+ - /thinking [on|off] and /web [on|off] — toggle the reasoning / web-search
376
+ behaviour. On **DeepSeek** these are the "Deep thinking" / "Smart search"
377
+ toggles; on **Qwen** they drive its thinking MODE (Thinking / Fast) and the
378
+ "Web search" item in the "+" menu.
333
379
  These (and /queue, /jobs, /goal, `/config <sub>`) can be used WHILE the agent
334
380
  is working — they do not touch the in-flight generation.
335
381
  - /queue [clear] — list or clear the messages waiting to be sent after the
@@ -494,7 +540,9 @@ MIT
494
540
  <details>
495
541
  <summary>🇷🇺 Читать по-русски (Russian)</summary>
496
542
 
497
- Терминальный coding-агент, работающий поверх [chat.deepseek.com](https://chat.deepseek.com/) через Playwright.
543
+ Терминальный coding-агент, работающий поверх веб-чатов LLM
544
+ ([chat.deepseek.com](https://chat.deepseek.com/), [chat.qwen.ai](https://chat.qwen.ai/), …)
545
+ через Playwright.
498
546
  По духу он похож на Claude Code / Codex CLI: запускается в текущей
499
547
  директории, читает и правит файлы, выполняет команды и коммитит в git.
500
548
 
@@ -531,8 +579,13 @@ zames # один раз войти, дальше прост
531
579
  ## Как это работает
532
580
 
533
581
  zames не вызывает API модели. Он запускает headless Chromium с постоянным
534
- профилем, входит в `chat.deepseek.com` как человек, печатает задачу в поле
535
- чата и читает ответ обратно.
582
+ профилем, входит в веб-чат как человек, печатает задачу в поле чата и читает
583
+ ответ обратно.
584
+
585
+ Доступны два бэкенда, переключаются командой `/provider <deepseek|qwen>`
586
+ (нужен перезапуск): **DeepSeek** (`chat.deepseek.com`, по умолчанию) и
587
+ **Qwen** (`chat.qwen.ai`). У каждого свой постоянный профиль, поэтому
588
+ переключение не разлогинивает другой.
536
589
 
537
590
  <p align="center">
538
591
  <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="как работает zames" width="860">
@@ -560,6 +613,7 @@ zames не вызывает API модели. Он запускает headless C
560
613
 
561
614
  - [Быстрый старт](#быстрый-старт) · [Возможности](#возможности) · [Почему zames?](#почему-zames) · [Требования](#требования)
562
615
  - [Как это работает](#как-это-работает) · [Установка](#установка) · [Вход в аккаунт](#вход-в-аккаунт) · [Использование](#использование)
616
+ - [Переключение провайдера (DeepSeek / Qwen)](#переключение-провайдера-deepseek--qwen)
563
617
  - [Инструменты](#инструменты) · [Слэш-команды](#слэш-команды)
564
618
  - [Контекст проекта, навыки и память](#контекст-проекта-навыки-и-память) · [MCP (внешние инструменты)](#mcp-внешние-инструменты) · [Конфигурация](#конфигурация)
565
619
  - [FAQ](#faq) · [Ссылки](#ссылки) · [Лицензия](#лицензия)
@@ -600,10 +654,12 @@ zames не вызывает API модели. Он запускает headless C
600
654
  ## Требования
601
655
 
602
656
  - Node.js >= 20
603
- - Аккаунт DeepSeek. При первом запуске zames спросит логин/пароль DeepSeek в
604
- терминале (и сохранит их в `~/.zames/config.json` после успешного входа,
605
- поэтому более поздний разлогин обрабатывается автоматически, без повторных
606
- вопросов). Можно также войти вручную в окне браузера, когда он headed.
657
+ - Аккаунт в нужном чате — **DeepSeek** (`chat.deepseek.com`, по умолчанию) или
658
+ **Qwen** (`chat.qwen.ai`). Qwen также работает в гостевом режиме на несколько
659
+ сообщений. При первом запуске zames спросит логин в терминале (и сохранит его
660
+ в `~/.zames/config.json` после успешного входа, поэтому более поздний
661
+ разлогин обрабатывается автоматически, без повторных вопросов). Можно также
662
+ войти вручную в окне браузера, когда он headed.
607
663
 
608
664
  ## Вход в аккаунт
609
665
 
@@ -636,6 +692,35 @@ Headless работает из коробки: headless Chrome обычно пр
636
692
  Учётные данные и переключатели можно также править из `/config`
637
693
  (`browser.auth.username`, `browser.auth.password`, `browser.auth.saveSession`).
638
694
 
695
+ ## Переключение провайдера (DeepSeek / Qwen)
696
+
697
+ zames умеет работать с **DeepSeek** (`chat.deepseek.com`, по умолчанию) и
698
+ **Qwen** (`chat.qwen.ai`):
699
+
700
+ ```bash
701
+ /provider qwen # переключиться на Qwen (сохраняется); нужен перезапуск
702
+ /provider deepseek # вернуться обратно
703
+ /provider status # активный провайдер / модель / статус входа
704
+ /provider login # повторно пройти проверку входа
705
+ /model # список моделей Qwen
706
+ /model qwen3.7-plus # выбрать модель
707
+ ```
708
+
709
+ Замечания:
710
+
711
+ - У бэкендов **разные постоянные профили** (`~/.zames/profile` у DeepSeek,
712
+ `~/.zames/qwen-profile` у Qwen), поэтому переключение не разлогинивает
713
+ другой. Смена провайдера применяется при следующем запуске — браузер
714
+ создаётся один раз на старте.
715
+ - Qwen работает в **гостевом режиме** без аккаунта, но гостю доступно всего
716
+ несколько сообщений в день; для долгих прогонов войдите (email / Google /
717
+ GitHub — телефон не нужен). zames сам логинится по сохранённым кредам или
718
+ спросит в терминале, как и с DeepSeek. `/model` меняет модель Qwen на лету
719
+ (без перезапуска).
720
+ - Вложения в Qwen идут через пункт **Upload attachment** в меню режима; в
721
+ DeepSeek — своя скрепка.
722
+ - `/doctor` показывает активный провайдер и состояние UI его страницы.
723
+
639
724
  ## Установка
640
725
 
641
726
  ```bash
@@ -785,7 +870,11 @@ zames --task "..." --output-format jsonl \
785
870
  `accumulated_token_usage`).
786
871
  - /export [file] — записать транскрипт сессии в Markdown-файл
787
872
  (по умолчанию zames-export-<stamp>.md).
788
- - /doctor — диагностика node, git, конфига, браузера, буфера обмена и MCP.
873
+ - /doctor — диагностика node, git, конфига, браузера, буфера обмена, MCP и
874
+ активного провайдера чата (плюс состояние UI его страницы).
875
+ - /provider <deepseek|qwen> — сменить бэкенд чата (нужен перезапуск);
876
+ /provider status показывает активный провайдер/модель/вход; /provider login
877
+ повторно проверяет вход. /model [id] покажет или выберет модель Qwen на лету.
789
878
  - /add-dir <path> — проверить дополнительную директорию (песочница
790
879
  фиксируется при старте; для записи туда перезапустите с --dir).
791
880
  - /resume <n> (после /chats) и /resume-id <id> — открыть чат и НАПЕЧАТАТЬ его
@@ -814,7 +903,10 @@ zames --task "..." --output-format jsonl \
814
903
  Сработавшая задача попадает в ту же очередь сообщений, что и ваш ввод, так
815
904
  что выполняется, когда агент свободен (никогда посреди генерации), и всё
816
905
  равно соблюдает троттлинг отправки.
817
- - /thinking [on|off] и /web [on|off] — переключить Deep thinking / Smart search.
906
+ - /thinking [on|off] и /web [on|off] — переключить режим размышления /
907
+ веб-поиска. В **DeepSeek** это тумблеры «Deep thinking» / «Smart search»;
908
+ в **Qwen** — это режим размышления (Thinking / Fast) и пункт «Web search»
909
+ в меню «+».
818
910
  Их (а также /queue, /jobs, /goal, `/config <sub>`) можно использовать, ПОКА
819
911
  агент работает — они не трогают текущую генерацию.
820
912
  - /queue [clear] — показать или очистить сообщения, ждущие отправки после
@@ -1,4 +1,5 @@
1
1
  import { buildSystemPrompt } from './system-prompt.js';
2
+ import { providerLabel as providerHumanLabel } from './providers.js';
2
3
  import { loadProjectContext } from './context.js';
3
4
  import { getGitContext, formatGitContext } from './gitTools.js';
4
5
  import { translate } from './i18n.js';
@@ -52,6 +53,11 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
52
53
  const safeToolResult = safe(onToolResult);
53
54
  const safeAssistantMessage = safe(onAssistantMessage);
54
55
  const safeWarning = safe(onWarning);
56
+ // Human label of the active backend, so provider-agnostic notices do not say
57
+ // "DeepSeek" while the loop is actually driving Qwen. Falls back to DeepSeek
58
+ // when the browser (a test double) does not expose a provider name.
59
+ const providerLabel = providerHumanLabel(browser.providerName || 'deepseek');
60
+ const tr = (key, params) => translate(locale)(key, { provider: providerLabel, ...params });
55
61
  // One AbortController per task. A tool gets `toolAbort.signal`; the loop
56
62
  // aborts it as soon as the operator presses Esc/Ctrl+C (browser._abort /
57
63
  // _stopped). This is what actually kills a running `npm test` instead of
@@ -323,7 +329,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
323
329
  });
324
330
  // Show how many watchdog retries remain, so the operator can tell a
325
331
  // single hiccup from a genuine stall (the budget used to be invisible).
326
- safeWarning(translate(locale)('ds.answer_timeout', {
332
+ safeWarning(tr('ds.answer_timeout', {
327
333
  sec: Math.round(askDeadlineMs / 1000),
328
334
  attempt: Math.min(afterToolRetries + 1, MAX_AFTER_TOOL_RETRIES),
329
335
  max: MAX_AFTER_TOOL_RETRIES,
@@ -336,7 +342,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
336
342
  transcript?.log('ask_timeout_exhausted', {
337
343
  message: 'ask() did not return an answer and the retry limit is exhausted',
338
344
  });
339
- safeWarning(translate(locale)('ds.answer_timeout_give_up'));
345
+ safeWarning(tr('ds.answer_timeout_give_up'));
340
346
  return 'ask() watchdog: no model answer received';
341
347
  }
342
348
  await reportChat();
@@ -532,7 +538,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
532
538
  // once per task, otherwise the retry budget spams the terminal.
533
539
  if (!toolRetryWarned) {
534
540
  toolRetryWarned = true;
535
- safeWarning(translate(locale)('ds.tool_retry', {
541
+ safeWarning(tr('ds.tool_retry', {
536
542
  attempt: unparsedRetries,
537
543
  max: MAX_UNPARSED_RETRIES,
538
544
  }));
@@ -604,7 +610,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
604
610
  ? lastText +
605
611
  String.fromCharCode(10) +
606
612
  String.fromCharCode(10) +
607
- '(The model stopped calling tools before finishing. The task may be incomplete - check the DeepSeek chat.)'
613
+ '(The model stopped calling tools before finishing. The task may be incomplete - check the ' +
614
+ providerLabel +
615
+ ' chat.)'
608
616
  : 'The model stopped calling tools before finishing the task.';
609
617
  }
610
618
  const suspiciousFinal = responseLooksLikeToolCall(rawResponse) ||
@@ -676,7 +684,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
676
684
  if (!isMeaningfulRespond(msg)) {
677
685
  transcript?.log('empty_respond_exhausted', { response: rawResponse });
678
686
  safeWarning('The model called respond without text, and the retry limit is exhausted. ' +
679
- 'Check the DeepSeek chat manually.');
687
+ 'Check the ' +
688
+ providerLabel +
689
+ ' chat manually.');
680
690
  return 'The model produced no final message (empty respond).';
681
691
  }
682
692
  safeAssistantMessage(msg);
@@ -57,6 +57,27 @@ export function readLoginErrorInDom() {
57
57
  }
58
58
  return out.replace(/\s+/g, ' ').trim();
59
59
  }
60
+ /**
61
+ * Read the account email shown in the MFA verification dialog, so the operator
62
+ * knows WHERE the one-time code was sent (the code arrives by email; without
63
+ * this the operator cannot tell which inbox to check). Best-effort: '' when the
64
+ * dialog or the email line is absent.
65
+ */
66
+ export function readMfaAccountInDom() {
67
+ const sels = [
68
+ '.ds-mfa-verification-account',
69
+ '.ds-mfa-verification-modal [title*="@"]',
70
+ ];
71
+ for (const s of sels) {
72
+ const el = document.querySelector(s);
73
+ if (!el)
74
+ continue;
75
+ const t = (el.getAttribute('title') || el.innerText || '').trim();
76
+ if (t)
77
+ return t;
78
+ }
79
+ return '';
80
+ }
60
81
  /**
61
82
  * The answer as RENDERED on the page, ALWAYS from the DOM (never the network
62
83
  * capture). Reasoning blocks (think class) are skipped and must never be
@@ -224,28 +224,28 @@ export class GenerationIncompleteError extends Error {
224
224
  }
225
225
  }
226
226
  // ---------- profile cleanup ----------
227
- export async function cleanSingletonFiles() {
227
+ export async function cleanSingletonFiles(dir = USER_DATA_DIR) {
228
228
  const names = ['SingletonLock', 'SingletonCookie', 'SingletonSocket'];
229
229
  for (const name of names) {
230
- const p = path.join(USER_DATA_DIR, name);
230
+ const p = path.join(dir, name);
231
231
  try {
232
232
  await fs.unlink(p);
233
233
  }
234
234
  catch { }
235
235
  }
236
236
  }
237
- export async function profileLooksLocked() {
237
+ export async function profileLooksLocked(dir = USER_DATA_DIR) {
238
238
  try {
239
- await fs.access(path.join(USER_DATA_DIR, 'SingletonLock'));
239
+ await fs.access(path.join(dir, 'SingletonLock'));
240
240
  return true;
241
241
  }
242
242
  catch {
243
243
  return false;
244
244
  }
245
245
  }
246
- export async function nukeProfile() {
246
+ export async function nukeProfile(dir = USER_DATA_DIR) {
247
247
  try {
248
- await fs.rm(USER_DATA_DIR, { recursive: true, force: true });
248
+ await fs.rm(dir, { recursive: true, force: true });
249
249
  }
250
250
  catch { }
251
251
  }