zames_pro 2.72.9 → 2.73.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/CHANGELOG.md +79 -0
- package/README.md +111 -19
- package/dist/agent-loop.js +17 -5
- package/dist/browser-pure.js +6 -6
- package/dist/browser.js +5 -1
- package/dist/commands.js +7 -0
- package/dist/config.js +56 -1
- package/dist/i18n/common.js +2 -2
- package/dist/i18n/config.js +14 -0
- package/dist/i18n/deepseek.js +26 -26
- package/dist/i18n/help.js +8 -0
- package/dist/i18n/qwen.js +132 -0
- package/dist/i18n.js +2 -0
- package/dist/index.js +295 -28
- package/dist/input.js +23 -0
- package/dist/provider-handoff.js +73 -0
- package/dist/providers.js +50 -0
- package/dist/qwen-browser.js +1743 -0
- package/dist/qwen-chats.js +183 -0
- package/dist/qwen-net-capture.js +221 -0
- package/dist/qwen-ui.js +160 -0
- package/dist/run-task.js +2 -1
- package/dist/sessions.js +15 -4
- package/dist/ui-health.js +27 -21
- package/package.json +8 -3
- package/scripts/coverage-gate.mts +5 -1
- package/scripts/qwen-smoke.mts +189 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,85 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.73.1] - 2026-10-11
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **qwen:** live provider switch, verified send, server-busy handling
|
|
15
|
+
|
|
16
|
+
## [2.73.0] - 2026-10-11
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **qwen:** a second chat provider (`chat.qwen.ai`), selectable with
|
|
21
|
+
`/provider <deepseek|qwen>`, so the operator can switch models without
|
|
22
|
+
leaving zames. Adds `src/qwen-ui.ts` (selectors), `src/qwen-net-capture.ts`
|
|
23
|
+
(phase-aware SSE parser), `src/qwen-browser.ts` (`QwenBrowser`), a separate
|
|
24
|
+
persistent profile (`~/.zames/qwen-profile`), `/model` to list/pick a Qwen
|
|
25
|
+
model, and file attachments via the mode menu's "Upload attachment" item.
|
|
26
|
+
- **provider:** switching `/provider` mid-session now carries the conversation
|
|
27
|
+
over: the current chat is summarized (on the old backend) and persisted as a
|
|
28
|
+
handoff (`src/provider-handoff.ts`); the next launch posts it as the first
|
|
29
|
+
message of the new provider's chat, so context is not lost. Falls back to a
|
|
30
|
+
local history dump when the model summary is unusable. Unit-tested.
|
|
31
|
+
- **provider:** a `PROVIDERS` registry (`src/providers.ts`) is the SINGLE
|
|
32
|
+
source of truth (ids, labels, `/chat` URL prefix), so adding a backend
|
|
33
|
+
(ChatGPT, Gemini, …) is a data change plus the provider's own modules — not a
|
|
34
|
+
dozen ternaries. `/provider` accepts any registered id; the config enum, the
|
|
35
|
+
`{provider}` label and `/doctor` read from the registry. Unit-tested
|
|
36
|
+
(`test/providers.test.ts`).
|
|
37
|
+
- **provider:** a `ChatProvider` interface (`src/types.ts`) both backends
|
|
38
|
+
implement; the agent loop only depends on `BrowserLike` and never sees a
|
|
39
|
+
concrete class. Operator notices (`ds.answer_timeout`, `ds.tool_retry`, …)
|
|
40
|
+
now use a `{provider}` label so they never say "DeepSeek" while driving Qwen.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **ui:** the status line shows a provider badge ([Qwen]/[DeepSeek]) so the
|
|
45
|
+
active backend is always visible in a multi-provider session.
|
|
46
|
+
- **qwen:** `listChats` / `fetchChatMessages` now use Qwen's OWN API
|
|
47
|
+
(`GET /api/v2/chats/?page=1&exclude_project=true` and
|
|
48
|
+
`GET /api/v2/chats/<id>?direction=up&limit=N`) with the DOM scrape as a
|
|
49
|
+
fallback, so `/chats` and `/resume` work on a signed-in account (verified
|
|
50
|
+
live: 9 chats with titles, 20 messages). Parsers are pure + tested.
|
|
51
|
+
- **qwen:** password login (`_autoLogin`) fixed to the verified order (email ->
|
|
52
|
+
"Log in with a password" -> password -> submit); the email-CODE flow needs the
|
|
53
|
+
operator to read the mail, so a saved password is preferred. Verified live.
|
|
54
|
+
- **qwen:** the context counter now uses `input_tokens` (the prompt size) —
|
|
55
|
+
the true "used context" — instead of `total_tokens` (which adds the answer),
|
|
56
|
+
so the status bar matches DeepSeek's semantics.
|
|
57
|
+
- **qwen:** thinking / web search are mapped onto Qwen's own UI: deepThinking
|
|
58
|
+
ON selects the "Thinking" mode (OFF → "Fast") in the
|
|
59
|
+
`.qwen-thinking-selector` dropdown, and webSearch toggles the "Web search"
|
|
60
|
+
item in the "+" mode menu. `/thinking` and `/web` therefore work on Qwen too.
|
|
61
|
+
- **qwen:** no 15 s send throttle — a live experiment (4 back-to-back sends at
|
|
62
|
+
0 ms gap) hit no rate limit, so `qwen.minSendIntervalMs` defaults to 1000 ms.
|
|
63
|
+
The agent-loop watchdog deadline is derived from the Qwen answer timeout
|
|
64
|
+
(a cold guest turn can take minutes), not the DeepSeek 240 s default.
|
|
65
|
+
- **provider:** sessions are tagged with the ACTIVE provider
|
|
66
|
+
(`saveSession({…, provider})`), so `/resume`/`/last`/auto-restore only ever
|
|
67
|
+
reopen a chat in the backend that created it (a chat id is meaningless to the
|
|
68
|
+
other one). `loadLastSession(workdir, provider)` filters accordingly.
|
|
69
|
+
- **qwen:** the restored-dialogue scraper moved to a PURE `src/qwen-chats.ts`
|
|
70
|
+
(mirroring `browser-chats.ts`) and now keeps only the OUTERMOST message
|
|
71
|
+
container — the broad `[class*="message"]` chain matched an outer and an
|
|
72
|
+
inner block, so every turn printed twice. `listChats` opens the sidebar first
|
|
73
|
+
and matches `/c/<id>` links. Compact / auto-compact use the active provider's
|
|
74
|
+
own answer timeout (`providerAnswerTimeoutMs()`), not DeepSeek's.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **qwen:** recover from a lost page/context mid-run (relaunch + re-login, and
|
|
79
|
+
re-navigate to the chat if the input is gone) instead of failing every retry.
|
|
80
|
+
- **qwen:** the guest daily limit (`getstsToken` → `RateLimited`) fails fast
|
|
81
|
+
with a "sign in" message instead of a 20-minute rate-limit wait.
|
|
82
|
+
- **qwen:** the real sign-in flow is **email → one-time code from the letter**
|
|
83
|
+
(not email+password): `_promptAndLogin()` now sends the email, asks the
|
|
84
|
+
operator for the code in the terminal, submits it, and verifies the session
|
|
85
|
+
from the page (guest header CTAs absent). `/provider login` re-runs it.
|
|
86
|
+
- **qwen:** a longer send-button wait when files are attached (the upload keeps
|
|
87
|
+
the button disabled).
|
|
88
|
+
|
|
10
89
|
## [2.72.9] - 2026-10-10
|
|
11
90
|
|
|
12
91
|
### 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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
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
|
|
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-агент, работающий поверх
|
|
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
|
-
профилем, входит в
|
|
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
|
-
- Аккаунт
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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, конфига, браузера, буфера
|
|
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] — переключить
|
|
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] — показать или очистить сообщения, ждущие отправки после
|
package/dist/agent-loop.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
541
|
+
safeWarning(tr('ds.tool_retry', {
|
|
536
542
|
attempt: unparsedRetries,
|
|
537
543
|
max: MAX_UNPARSED_RETRIES,
|
|
538
544
|
}));
|
|
@@ -541,9 +547,11 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
541
547
|
(justRanTool
|
|
542
548
|
? 'You stopped after a tool call and wrote plain text. '
|
|
543
549
|
: 'You wrote plain text without a tool call. ') +
|
|
550
|
+
'Plain text is a PROTOCOL VIOLATION — the operator never sees it. ' +
|
|
544
551
|
'The task is not finished. Reply with EXACTLY one JSON tool-call ' +
|
|
545
552
|
'object, no text before or after, for example: ' +
|
|
546
553
|
'{\"tool\": \"Bash\", \"args\": {\"command\": \"...\"}}. ' +
|
|
554
|
+
'Do NOT explain, plan or narrate — call a tool. ' +
|
|
547
555
|
'If the task is really done — call respond with the final message.';
|
|
548
556
|
continue;
|
|
549
557
|
}
|
|
@@ -604,7 +612,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
604
612
|
? lastText +
|
|
605
613
|
String.fromCharCode(10) +
|
|
606
614
|
String.fromCharCode(10) +
|
|
607
|
-
'(The model stopped calling tools before finishing. The task may be incomplete - check the
|
|
615
|
+
'(The model stopped calling tools before finishing. The task may be incomplete - check the ' +
|
|
616
|
+
providerLabel +
|
|
617
|
+
' chat.)'
|
|
608
618
|
: 'The model stopped calling tools before finishing the task.';
|
|
609
619
|
}
|
|
610
620
|
const suspiciousFinal = responseLooksLikeToolCall(rawResponse) ||
|
|
@@ -676,7 +686,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
676
686
|
if (!isMeaningfulRespond(msg)) {
|
|
677
687
|
transcript?.log('empty_respond_exhausted', { response: rawResponse });
|
|
678
688
|
safeWarning('The model called respond without text, and the retry limit is exhausted. ' +
|
|
679
|
-
'Check the
|
|
689
|
+
'Check the ' +
|
|
690
|
+
providerLabel +
|
|
691
|
+
' chat manually.');
|
|
680
692
|
return 'The model produced no final message (empty respond).';
|
|
681
693
|
}
|
|
682
694
|
safeAssistantMessage(msg);
|
package/dist/browser-pure.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
248
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
249
249
|
}
|
|
250
250
|
catch { }
|
|
251
251
|
}
|
package/dist/browser.js
CHANGED
|
@@ -22,6 +22,7 @@ export { STOP_NAME_RE, CONTINUE_NAME_RE };
|
|
|
22
22
|
import { askPassword, USER_DATA_DIR, parseHeadlessUACache, headlessEngineKey, readCachedHeadlessUA, writeCachedHeadlessUA, headlessUACacheReady, sanitizeHeadlessUA, AUTH_MARKER_FILE, isGenerationIncompleteText, isRateLimitText, isServerBusyText, normText, cleanAnswer, RateLimitError, ServerBusyError, GenerationIncompleteError, cleanSingletonFiles, profileLooksLocked, nukeProfile, } from './browser-pure.js';
|
|
23
23
|
export { askPassword, parseHeadlessUACache, headlessUACacheReady, sanitizeHeadlessUA, isGenerationIncompleteText, isRateLimitText, isServerBusyText, normText, RateLimitError, ServerBusyError, GenerationIncompleteError, };
|
|
24
24
|
export class DeepSeekBrowser {
|
|
25
|
+
providerName = 'deepseek';
|
|
25
26
|
headless;
|
|
26
27
|
debug;
|
|
27
28
|
channel;
|
|
@@ -214,7 +215,10 @@ export class DeepSeekBrowser {
|
|
|
214
215
|
}
|
|
215
216
|
/** Translation bound to this browser's locale. */
|
|
216
217
|
_t(key, params) {
|
|
217
|
-
|
|
218
|
+
// Default the {provider} placeholder to DeepSeek for the shared `ds.*`
|
|
219
|
+
// messages (QwenBrowser overrides it with 'Qwen'), so a provider-agnostic
|
|
220
|
+
// string never lies about which backend is talking.
|
|
221
|
+
return translate(this.locale)(key, { provider: 'DeepSeek', ...params });
|
|
218
222
|
}
|
|
219
223
|
/**
|
|
220
224
|
* Switch the language of the messages the browser itself prints (login
|
package/dist/commands.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { providerLabel } from './providers.js';
|
|
1
2
|
import path from 'path';
|
|
2
3
|
import { isImageName } from './attachments.js';
|
|
3
4
|
import { translate } from './i18n.js';
|
|
@@ -276,6 +277,10 @@ export function renderDoctor(d, t = translate('en')) {
|
|
|
276
277
|
row(true, 'node', d.nodeVersion);
|
|
277
278
|
row(true, 'platform', d.platform);
|
|
278
279
|
row(true, 'workdir', d.workdir);
|
|
280
|
+
if (d.provider) {
|
|
281
|
+
const label = providerLabel(d.provider).toLowerCase();
|
|
282
|
+
row(true, 'provider', label + (d.providerModel ? ' (' + d.providerModel + ')' : ''));
|
|
283
|
+
}
|
|
279
284
|
row(d.gitOk, 'git', d.gitOk
|
|
280
285
|
? t('doctor.repo', { v: d.gitBranch || t('doctor.detached') })
|
|
281
286
|
: t('doctor.not_repo'));
|
|
@@ -1013,6 +1018,8 @@ export const SLASH_COMMANDS = [
|
|
|
1013
1018
|
{ name: '/queue', key: 'help.cmd.queue' },
|
|
1014
1019
|
{ name: '/tasks', key: 'help.cmd.tasks' },
|
|
1015
1020
|
{ name: '/config', key: 'help.cmd.config' },
|
|
1021
|
+
{ name: '/provider', key: 'help.cmd.provider' },
|
|
1022
|
+
{ name: '/model', key: 'help.cmd.model' },
|
|
1016
1023
|
{ name: '/skills', key: 'help.cmd.skills' },
|
|
1017
1024
|
{ name: '/memory', key: 'help.cmd.memory' },
|
|
1018
1025
|
{ name: '/remember', key: 'help.cmd.remember' },
|
package/dist/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from 'os';
|
|
|
4
4
|
import { DEFAULT_DIAGNOSTICS } from './diagnostics.js';
|
|
5
5
|
import { DEFAULT_LOCALE } from './i18n.js';
|
|
6
6
|
import { writeJsonAtomic } from './fsutil.js';
|
|
7
|
+
import { PROVIDER_IDS } from './providers.js';
|
|
7
8
|
const ZAMES_HOME = path.join(os.homedir(), '.zames');
|
|
8
9
|
const HOME_CONFIG = path.join(ZAMES_HOME, 'config.json');
|
|
9
10
|
// The PROJECT config path is resolved LAZILY: `/cd` changes process.cwd() at
|
|
@@ -14,8 +15,23 @@ export function projectConfigPath() {
|
|
|
14
15
|
}
|
|
15
16
|
export const DEFAULTS = {
|
|
16
17
|
maxIterations: 0,
|
|
18
|
+
// Default backend stays DeepSeek so existing users see no change; the
|
|
19
|
+
// operator switches with /provider qwen (persisted to the home config).
|
|
20
|
+
provider: 'deepseek',
|
|
21
|
+
qwen: {
|
|
22
|
+
model: '',
|
|
23
|
+
enabled: false,
|
|
24
|
+
// Qwen tolerates back-to-back sends (verified live: 4 sends at 0 ms gap,
|
|
25
|
+
// no rate limit), so the DeepSeek 15 s throttle is pure latency here.
|
|
26
|
+
minSendIntervalMs: 1000,
|
|
27
|
+
// The first send after a cold load can take minutes ("Model loading…" +
|
|
28
|
+
// thinking); a GUEST turn measured up to ~9 min live on a cold profile, so
|
|
29
|
+
// give it generous room. A signed-in session answers in seconds; this only
|
|
30
|
+
// bounds the worst case.
|
|
31
|
+
answerTimeoutMs: 900000,
|
|
32
|
+
},
|
|
17
33
|
// Headless by default: the browser runs without a visible window. Set
|
|
18
|
-
// headless: false (or pass --headed) to watch/debug the
|
|
34
|
+
// headless: false (or pass --headed) to watch/debug the page.
|
|
19
35
|
headless: true,
|
|
20
36
|
debug: false,
|
|
21
37
|
browserChannel: null,
|
|
@@ -91,6 +107,16 @@ export function loadConfig() {
|
|
|
91
107
|
}
|
|
92
108
|
return merged;
|
|
93
109
|
}
|
|
110
|
+
// The agent-loop watchdog deadline per provider. The loop default (240s) is
|
|
111
|
+
// tuned for DeepSeek; a Qwen GUEST turn regularly takes 7-8 minutes (cold
|
|
112
|
+
// "Model loading…" + a long thinking phase) and would trip the watchdog on
|
|
113
|
+
// every send. Derive the deadline from the provider's own answer timeout so
|
|
114
|
+
// there is headroom. DeepSeek keeps browser.askDeadlineMs (the loop default).
|
|
115
|
+
export function providerAskDeadlineMs(cfg) {
|
|
116
|
+
if (cfg.provider === 'qwen')
|
|
117
|
+
return cfg.qwen.answerTimeoutMs + 120_000;
|
|
118
|
+
return cfg.browser.askDeadlineMs;
|
|
119
|
+
}
|
|
94
120
|
function deepMerge(target, source) {
|
|
95
121
|
for (const key of Object.keys(source)) {
|
|
96
122
|
if (source[key] &&
|
|
@@ -152,6 +178,35 @@ export const CONFIG_SCHEMA = [
|
|
|
152
178
|
labelKey: 'cfg.f.browser_authSaveSession',
|
|
153
179
|
groupKey: 'cfg.group.browser',
|
|
154
180
|
},
|
|
181
|
+
{
|
|
182
|
+
path: 'provider',
|
|
183
|
+
type: 'enum',
|
|
184
|
+
values: [...PROVIDER_IDS],
|
|
185
|
+
labelKey: 'cfg.f.provider',
|
|
186
|
+
groupKey: 'cfg.group.browser',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
path: 'qwen.model',
|
|
190
|
+
type: 'string',
|
|
191
|
+
labelKey: 'cfg.f.qwen_model',
|
|
192
|
+
groupKey: 'cfg.group.browser',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
path: 'qwen.minSendIntervalMs',
|
|
196
|
+
type: 'number',
|
|
197
|
+
min: 0,
|
|
198
|
+
max: 600000,
|
|
199
|
+
labelKey: 'cfg.f.qwen_minSendIntervalMs',
|
|
200
|
+
groupKey: 'cfg.group.browser',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
path: 'qwen.answerTimeoutMs',
|
|
204
|
+
type: 'number',
|
|
205
|
+
min: 1000,
|
|
206
|
+
max: 3600000,
|
|
207
|
+
labelKey: 'cfg.f.qwen_answerTimeoutMs',
|
|
208
|
+
groupKey: 'cfg.group.browser',
|
|
209
|
+
},
|
|
155
210
|
{
|
|
156
211
|
path: 'debug',
|
|
157
212
|
type: 'boolean',
|
package/dist/i18n/common.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Common UI strings: app/common, status, tasks, messages, diff, context, undo, cd.
|
|
2
2
|
export const commonMessages = {
|
|
3
3
|
'app.tagline': {
|
|
4
|
-
ru: 'агент поверх chat.deepseek.com через Playwright',
|
|
5
|
-
en: 'coding agent over chat.deepseek.com via Playwright',
|
|
4
|
+
ru: 'агент поверх chat.deepseek.com / chat.qwen.ai через Playwright',
|
|
5
|
+
en: 'coding agent over chat.deepseek.com / chat.qwen.ai via Playwright',
|
|
6
6
|
},
|
|
7
7
|
'common.yes': { ru: 'да', en: 'yes' },
|
|
8
8
|
'common.no': { ru: 'нет', en: 'no' },
|
package/dist/i18n/config.js
CHANGED
|
@@ -6,6 +6,20 @@ export const configMessages = {
|
|
|
6
6
|
'cfg.group.checkpoint': { ru: 'Чекпойнты', en: 'Checkpoints' },
|
|
7
7
|
'cfg.group.transcript': { ru: 'Транскрипт', en: 'Transcript' },
|
|
8
8
|
'cfg.group.browser': { ru: 'Браузер / DeepSeek', en: 'Browser / DeepSeek' },
|
|
9
|
+
'cfg.group.qwen': { ru: 'Qwen', en: 'Qwen' },
|
|
10
|
+
'cfg.f.provider': { ru: 'Провайдер чата', en: 'Chat provider' },
|
|
11
|
+
'cfg.f.qwen_model': {
|
|
12
|
+
ru: 'Модель Qwen (id)',
|
|
13
|
+
en: 'Qwen model (id)',
|
|
14
|
+
},
|
|
15
|
+
'cfg.f.qwen_minSendIntervalMs': {
|
|
16
|
+
ru: 'Qwen: пауза между отправками (мс)',
|
|
17
|
+
en: 'Qwen: pause between sends (ms)',
|
|
18
|
+
},
|
|
19
|
+
'cfg.f.qwen_answerTimeoutMs': {
|
|
20
|
+
ru: 'Qwen: таймаут ответа (мс)',
|
|
21
|
+
en: 'Qwen: answer timeout (ms)',
|
|
22
|
+
},
|
|
9
23
|
'cfg.f.ui_contextLimit': {
|
|
10
24
|
ru: 'Размер контекста (токены)',
|
|
11
25
|
en: 'Context window (tokens)',
|