zames_pro 2.73.8 → 2.73.10
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 +14 -0
- package/README.md +52 -0
- package/dist/cli-args.js +1 -0
- package/dist/config.js +5 -3
- package/dist/extraTools.js +9 -1
- package/dist/i18n/help.js +4 -0
- package/dist/index.js +22 -1
- package/dist/qwen-chats.js +28 -9
- package/dist/qwen-net-capture.js +5 -1
- package/dist/shell.js +6 -13
- package/dist/tools.js +10 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.73.10] - 2026-10-12
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **tools:** reject invalid base64 instead of writing garbage
|
|
15
|
+
|
|
16
|
+
## [2.73.9] - 2026-10-12
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **qwen:** raise the default send pause to 4s (rate limit is real)
|
|
21
|
+
- **qwen:** read the real history shape (map) + handle quota_limit
|
|
22
|
+
- **cli:** honor --provider <id>; stop blocking bash -c in the sandbox
|
|
23
|
+
|
|
10
24
|
## [2.73.8] - 2026-10-12
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -467,6 +467,32 @@ Their tools show up in the agent as `server__tool` (e.g.
|
|
|
467
467
|
A server that fails to connect is skipped with a warning and never breaks the
|
|
468
468
|
agent.
|
|
469
469
|
|
|
470
|
+
### Recommended servers (verified)
|
|
471
|
+
|
|
472
|
+
A few small, high-value servers tested end to end with zames (both the
|
|
473
|
+
DeepSeek and Qwen backends):
|
|
474
|
+
|
|
475
|
+
- **Context7** (`@upstash/context7-mcp`) — fetches CURRENT, version-specific
|
|
476
|
+
documentation for a library before the agent writes code, instead of relying
|
|
477
|
+
on a stale training snapshot. Tools: `resolve_library_id` then `query_docs`.
|
|
478
|
+
Verified live on DeepSeek AND Qwen: the agent called both tools and got the
|
|
479
|
+
current Zod API. This is the one to add first.
|
|
480
|
+
|
|
481
|
+
```json
|
|
482
|
+
{ "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"] } }
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
- **Sequential Thinking** (`@modelcontextprotocol/server-sequential-thinking`)
|
|
486
|
+
— one `sequentialthinking` tool that gives the model an explicit, revisable
|
|
487
|
+
plan before it acts; useful for long multi-step tasks.
|
|
488
|
+
|
|
489
|
+
- **Memory** (`@modelcontextprotocol/server-memory`) — a local knowledge graph
|
|
490
|
+
(9 tools) for durable facts across chats. NOTE: zames already has `MEMORY.md`
|
|
491
|
+
+ `AGENTS.md`; add this only if you want a queryable graph.
|
|
492
|
+
|
|
493
|
+
Keep the stack SMALL: every server loads its tool definitions into the model's
|
|
494
|
+
context. Add one when you feel its absence, not before.
|
|
495
|
+
|
|
470
496
|
## Configuration
|
|
471
497
|
|
|
472
498
|
Global config: `~/.zames/config.json`
|
|
@@ -1010,6 +1036,32 @@ MCP-браузер и браузер агента подерутся за оди
|
|
|
1010
1036
|
Сервер, который не смог подключиться, пропускается с предупреждением и
|
|
1011
1037
|
никогда не роняет агента.
|
|
1012
1038
|
|
|
1039
|
+
### Рекомендованные серверы (проверены)
|
|
1040
|
+
|
|
1041
|
+
Несколько небольших полезных серверов, протестированных вживую с zames
|
|
1042
|
+
(на обоих бэкендах — DeepSeek и Qwen):
|
|
1043
|
+
|
|
1044
|
+
- **Context7** (`@upstash/context7-mcp`) — подтягивает АКТУАЛЬНУЮ
|
|
1045
|
+
документацию библиотеки (для конкретной версии) до того, как агент пишет код,
|
|
1046
|
+
вместо устаревших знаний из обучения. Инструменты: `resolve_library_id`, затем
|
|
1047
|
+
`query_docs`. Проверено вживую на DeepSeek И Qwen: агент вызвал оба инструмента
|
|
1048
|
+
и получил актуальный API Zod. Именно его стоит подключить первым.
|
|
1049
|
+
|
|
1050
|
+
```json
|
|
1051
|
+
{ "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"] } }
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
- **Sequential Thinking** (`@modelcontextprotocol/server-sequential-thinking`)
|
|
1055
|
+
— один инструмент `sequentialthinking`, дающий модели явный пересматриваемый
|
|
1056
|
+
план перед действием; полезно для длинных многошаговых задач.
|
|
1057
|
+
|
|
1058
|
+
- **Memory** (`@modelcontextprotocol/server-memory`) — локальный граф знаний
|
|
1059
|
+
(9 инструментов) для долговременных фактов между чатами. ВНИМАНИЕ: у zames уже
|
|
1060
|
+
есть `MEMORY.md` + `AGENTS.md`; подключайте, только если нужен граф с запросами.
|
|
1061
|
+
|
|
1062
|
+
Держите набор МАЛЕНЬКИМ: каждый сервер грузит описания своих инструментов в
|
|
1063
|
+
контекст модели. Добавляйте, когда почувствовали нехватку, а не заранее.
|
|
1064
|
+
|
|
1013
1065
|
## Конфигурация
|
|
1014
1066
|
|
|
1015
1067
|
Глобальный конфиг: `~/.zames/config.json`
|
package/dist/cli-args.js
CHANGED
package/dist/config.js
CHANGED
|
@@ -21,9 +21,11 @@ export const DEFAULTS = {
|
|
|
21
21
|
qwen: {
|
|
22
22
|
model: '',
|
|
23
23
|
enabled: false,
|
|
24
|
-
// Qwen
|
|
25
|
-
//
|
|
26
|
-
|
|
24
|
+
// Qwen DOES rate-limit a rapid burst: measured live, the 5th back-to-back
|
|
25
|
+
// send at 0 ms gap hit "too frequent" and needed a 5-min back-off. A 1 s
|
|
26
|
+
// gap was too small for a real editing session, so the default is now 4 s
|
|
27
|
+
// — enough to stay under the limit while keeping the agent responsive.
|
|
28
|
+
minSendIntervalMs: 4000,
|
|
27
29
|
// The first send after a cold load can take minutes ("Model loading…" +
|
|
28
30
|
// thinking); a GUEST turn measured up to ~9 min live on a cold profile, so
|
|
29
31
|
// give it generous room. A signed-in session answers in seconds; this only
|
package/dist/extraTools.js
CHANGED
|
@@ -12,7 +12,15 @@ import { safePath } from './sandbox.js';
|
|
|
12
12
|
const NL = String.fromCharCode(10);
|
|
13
13
|
function decodeContent(content, contentBase64) {
|
|
14
14
|
if (typeof contentBase64 === 'string' && contentBase64.length) {
|
|
15
|
-
|
|
15
|
+
// Reject undecodable base64: Buffer.from(..., 'base64') silently drops
|
|
16
|
+
// invalid characters and returns garbage, which would patch with corrupt
|
|
17
|
+
// text and no error (same guard as tools.ts).
|
|
18
|
+
const compact = contentBase64.replace(/\s+/g, '');
|
|
19
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(compact)) {
|
|
20
|
+
throw new Error('base64 argument is not valid (expected only A-Za-z0-9+/=). ' +
|
|
21
|
+
'Re-send as plain text or as clean base64.');
|
|
22
|
+
}
|
|
23
|
+
return Buffer.from(compact, 'base64').toString('utf-8');
|
|
16
24
|
}
|
|
17
25
|
return String(content ?? '');
|
|
18
26
|
}
|
package/dist/i18n/help.js
CHANGED
|
@@ -38,6 +38,10 @@ export const helpMessages = {
|
|
|
38
38
|
ru: 'показать окно браузера (отладка/вход)',
|
|
39
39
|
en: 'show the browser window (debug/sign-in)',
|
|
40
40
|
},
|
|
41
|
+
'help.opt.provider': {
|
|
42
|
+
ru: 'выбрать провайдера чата (deepseek | qwen) на этот запуск',
|
|
43
|
+
en: 'select the chat provider (deepseek | qwen) for this run',
|
|
44
|
+
},
|
|
41
45
|
'help.opt.debug': { ru: 'подробный лог', en: 'verbose log' },
|
|
42
46
|
'help.opt.dev': {
|
|
43
47
|
ru: 'режим разработки: авто-перечитывание модулей',
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
import { theme } from './theme.js';
|
|
8
8
|
import { DeepSeekBrowser, headlessUACacheReady } from './browser.js';
|
|
9
9
|
import { QwenBrowser } from './qwen-browser.js';
|
|
10
|
-
import { isProviderId, asProviderId, providerChatUrlPrefix, providerLabel, } from './providers.js';
|
|
10
|
+
import { isProviderId, asProviderId, providerChatUrlPrefix, providerLabel, PROVIDER_IDS, } from './providers.js';
|
|
11
11
|
import { createTools, mergeTools, filterToolsForReadOnly, filterToolsByAllowList, } from './tools.js';
|
|
12
12
|
import { createTodoStore, getTodos, setTodos, resetTodos, renderTodos, } from './extraTools.js';
|
|
13
13
|
import { runAgentLoop } from './agent-loop.js';
|
|
@@ -69,6 +69,26 @@ const { getArg, hasFlag, getPositional } = createCliArgs(process.argv.slice(2));
|
|
|
69
69
|
// compaction).
|
|
70
70
|
const COMPACT_FALLBACK_LIMIT = 40;
|
|
71
71
|
const config = loadConfig();
|
|
72
|
+
// A `--provider <deepseek|qwen>` CLI flag selects the backend for THIS run,
|
|
73
|
+
// mirroring the /provider command (which changes it live). Without this an
|
|
74
|
+
// operator who typed `--provider qwen` got the DEFAULT backend — the flag was
|
|
75
|
+
// silently treated as free-form task text and DeepSeek opened instead of Qwen.
|
|
76
|
+
const providerArg = getArg('--provider', null);
|
|
77
|
+
if (providerArg !== null) {
|
|
78
|
+
if (isProviderId(providerArg)) {
|
|
79
|
+
config.provider = providerArg;
|
|
80
|
+
config.qwen.enabled = providerArg === 'qwen';
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.error(theme.warn('Unknown --provider "' +
|
|
84
|
+
providerArg +
|
|
85
|
+
'" (expected: ' +
|
|
86
|
+
PROVIDER_IDS.join(' | ') +
|
|
87
|
+
'). Using ' +
|
|
88
|
+
config.provider +
|
|
89
|
+
'.'));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
72
92
|
// Apply the configured answer width to the markdown renderer (0 = auto).
|
|
73
93
|
setAnswerWidth(config.ui?.answerWidth ?? 0);
|
|
74
94
|
// Current interface/agent language. Changed by the /config lang <ru|en> command.
|
|
@@ -383,6 +403,7 @@ ${theme.bold(t('help.options'))}
|
|
|
383
403
|
--max-iter <n> ${t('help.opt.max_iter', { n: config.maxIterations })}
|
|
384
404
|
--headless ${t('help.opt.headless')}
|
|
385
405
|
--headed ${t('help.opt.headed')}
|
|
406
|
+
--provider <id> ${t('help.opt.provider')}
|
|
386
407
|
--plan ${t('help.opt.plan')}
|
|
387
408
|
--output-format <f> ${t('help.opt.output_format')}
|
|
388
409
|
--debug ${t('help.opt.debug')}
|
package/dist/qwen-chats.js
CHANGED
|
@@ -132,26 +132,36 @@ export function parseQwenChatList(json) {
|
|
|
132
132
|
export function parseQwenHistory(json) {
|
|
133
133
|
const root = json;
|
|
134
134
|
const data = root && typeof root === 'object' ? root.data : null;
|
|
135
|
+
// The REAL shape (verified live) is a MAP, not an array:
|
|
136
|
+
// data.chat.history.messages = { <id>: { role, content, timestamp, ... } }
|
|
137
|
+
// An array is accepted too (older shape).
|
|
135
138
|
let arr = null;
|
|
139
|
+
const asArr = (v) => Array.isArray(v) ? v : v && typeof v === 'object' ? Object.values(v) : null;
|
|
136
140
|
if (Array.isArray(data))
|
|
137
141
|
arr = data;
|
|
138
142
|
else if (data && typeof data === 'object') {
|
|
139
143
|
const d = data;
|
|
140
|
-
if (
|
|
141
|
-
arr = d.messages;
|
|
144
|
+
if (d.messages)
|
|
145
|
+
arr = asArr(d.messages);
|
|
142
146
|
else if (d.chat && typeof d.chat === 'object') {
|
|
143
147
|
const chat = d.chat;
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
const hist = chat.history && typeof chat.history === 'object'
|
|
149
|
+
? chat.history
|
|
150
|
+
: null;
|
|
151
|
+
if (hist && hist.messages)
|
|
152
|
+
arr = asArr(hist.messages);
|
|
153
|
+
else if (chat.messages)
|
|
154
|
+
arr = asArr(chat.messages);
|
|
146
155
|
}
|
|
147
156
|
}
|
|
148
|
-
if (!
|
|
157
|
+
if (!arr)
|
|
149
158
|
return [];
|
|
159
|
+
// The map is UNORDERED; `timestamp` (seconds) restores the dialogue order.
|
|
160
|
+
const sorted = arr
|
|
161
|
+
.filter((x) => !!x && typeof x === 'object')
|
|
162
|
+
.sort((a, b) => (Number(a.timestamp) || 0) - (Number(b.timestamp) || 0));
|
|
150
163
|
const out = [];
|
|
151
|
-
for (const
|
|
152
|
-
if (!raw || typeof raw !== 'object')
|
|
153
|
-
continue;
|
|
154
|
-
const m = raw;
|
|
164
|
+
for (const m of sorted) {
|
|
155
165
|
// Role detection is DEFENSIVE: the field has been `role` (string,
|
|
156
166
|
// case-insensitive), a numeric flag, or `is_user`. Anything that is not
|
|
157
167
|
// clearly the user is treated as the assistant (the restored dialogue
|
|
@@ -175,6 +185,15 @@ export function parseQwenHistory(json) {
|
|
|
175
185
|
}
|
|
176
186
|
}
|
|
177
187
|
}
|
|
188
|
+
// `content_list` (phase-split output) is used when `content` is empty.
|
|
189
|
+
if (!text && Array.isArray(m.content_list)) {
|
|
190
|
+
for (const part of m.content_list) {
|
|
191
|
+
if (part && typeof part.content === 'string')
|
|
192
|
+
text += part.content;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// `reasoning_content` is the THINK phase and must never leak into the
|
|
196
|
+
// restored dialogue as the answer.
|
|
178
197
|
text = text.trim();
|
|
179
198
|
if (text)
|
|
180
199
|
out.push({ role, text });
|
package/dist/qwen-net-capture.js
CHANGED
|
@@ -199,7 +199,11 @@ export function isRateLimitText(body) {
|
|
|
199
199
|
const b = String(body || '');
|
|
200
200
|
if (!b)
|
|
201
201
|
return false;
|
|
202
|
-
|
|
202
|
+
// `quota_limit` is a SEPARATE code Qwen returns in the SSE error field
|
|
203
|
+
// ("The service is currently experiencing high demand. Please try again
|
|
204
|
+
// later.") — a TRANSIENT capacity limit, so it belongs with the rate-limit
|
|
205
|
+
// family (back off and retry), not with the guest DAILY limit.
|
|
206
|
+
return /too many requests|rate limit|too frequent|please try again later|quota_limit|high demand|слишком часто/i.test(b);
|
|
203
207
|
}
|
|
204
208
|
// The GUEST daily limit is a DIFFERENT failure from a transient rate limit:
|
|
205
209
|
// Qwen answers `RateLimited` with "You've reached the guest chat limit for
|
package/dist/shell.js
CHANGED
|
@@ -43,22 +43,15 @@ export function assertCommandInsideRoot(root, command) {
|
|
|
43
43
|
throw new Error('Sandbox: leaving ' + root + ' is forbidden (cd ' + raw + ')');
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
throw new Error('Sandbox: running a shell via -c is forbidden (cannot verify the root)');
|
|
53
|
-
}
|
|
46
|
+
// chroot switches the process root outright — always a barrier violation.
|
|
47
|
+
// NOTE: `sh -c '…'` and an in-process `chdir` are deliberately NOT blocked:
|
|
48
|
+
// `bash -c` / `nohup bash -c` is a legitimate way to run a background job,
|
|
49
|
+
// and a blanket ban broke real commands (the operator runs scripts that way).
|
|
50
|
+
// They remain a documented best-effort hole (a static scan cannot follow a
|
|
51
|
+
// runtime target); full isolation needs bwrap/containers.
|
|
54
52
|
if (/\bchroot\b/i.test(cmd)) {
|
|
55
53
|
throw new Error('Sandbox: chroot is forbidden');
|
|
56
54
|
}
|
|
57
|
-
// A chdir performed by an interpreter (Python/Perl/Ruby/Node) or by a raw
|
|
58
|
-
// syscall wrapper. Matched loosely on the common spellings.
|
|
59
|
-
if (/\bos\.chdir\s*\(|\bchdir\s*\(|\bProcess\.chdir\b|process\.chdir\s*\(|\bchdir\b\s*['"]?\//i.test(cmd)) {
|
|
60
|
-
throw new Error('Sandbox: an in-process directory change (chdir) is forbidden');
|
|
61
|
-
}
|
|
62
55
|
}
|
|
63
56
|
function formatExecResult(err, stdout, stderr, timeout, okExitCodes) {
|
|
64
57
|
const out = (stdout || '').toString();
|
package/dist/tools.js
CHANGED
|
@@ -19,7 +19,16 @@ export function createTools(workdir, { undo, todos, readOnly = false, subagents
|
|
|
19
19
|
// subject to corruption.
|
|
20
20
|
const decodeContent = (content, contentBase64) => {
|
|
21
21
|
if (typeof contentBase64 === 'string' && contentBase64.length) {
|
|
22
|
-
|
|
22
|
+
// Reject a base64 string that is not decodable: Buffer.from(..., 'base64')
|
|
23
|
+
// SILENTLY drops invalid characters and returns garbage (verified), which
|
|
24
|
+
// would write a corrupt file with no error. The alphabet check catches a
|
|
25
|
+
// truncated/garbled payload early with a clear message.
|
|
26
|
+
const compact = contentBase64.replace(/\s+/g, '');
|
|
27
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(compact)) {
|
|
28
|
+
throw new Error('content_base64 is not valid base64 (expected only A-Za-z0-9+/=). ' +
|
|
29
|
+
'Re-send the content as plain `content` or as clean base64.');
|
|
30
|
+
}
|
|
31
|
+
return Buffer.from(compact, 'base64').toString('utf-8');
|
|
23
32
|
}
|
|
24
33
|
return String(content ?? '');
|
|
25
34
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.73.
|
|
3
|
+
"version": "2.73.10",
|
|
4
4
|
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen and other web LLMs — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|