zames_pro 2.73.8 → 2.73.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.73.9] - 2026-10-12
11
+
12
+ ### Fixed
13
+
14
+ - **qwen:** raise the default send pause to 4s (rate limit is real)
15
+ - **qwen:** read the real history shape (map) + handle quota_limit
16
+ - **cli:** honor --provider <id>; stop blocking bash -c in the sandbox
17
+
10
18
  ## [2.73.8] - 2026-10-12
11
19
 
12
20
  ### Fixed
package/dist/cli-args.js CHANGED
@@ -17,6 +17,7 @@ export const SKIP_VALUE_FLAGS = [
17
17
  '--max-iter',
18
18
  '--chat',
19
19
  '--output-format',
20
+ '--provider',
20
21
  ];
21
22
  export function createCliArgs(argv) {
22
23
  const args = Array.isArray(argv) ? argv : [];
package/dist/config.js CHANGED
@@ -21,9 +21,11 @@ export const DEFAULTS = {
21
21
  qwen: {
22
22
  model: '',
23
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,
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/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')}
@@ -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 (Array.isArray(d.messages))
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
- if (Array.isArray(chat.messages))
145
- arr = chat.messages;
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 (!Array.isArray(arr))
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 raw of arr) {
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 });
@@ -199,7 +199,11 @@ export function isRateLimitText(body) {
199
199
  const b = String(body || '');
200
200
  if (!b)
201
201
  return false;
202
- return /too many requests|rate limit|too frequent|please try again later|слишком часто/i.test(b);
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
- // Indirect re-parse / chdir that a static `cd` scan cannot follow. These are
47
- // the documented escape hatches (`sh -c 'cd /'`, `python -c
48
- // "os.chdir('/')"`): they execute a fresh interpreter which reads the target
49
- // only at RUNTIME, so no static scan can prove it stays inside the root.
50
- // Refusing them is the honest barrier — best-effort, not OS isolation.
51
- if (/\b(?:sh|bash|dash|zsh|ksh)\b[^\n]*\s-[a-z]*c\b/i.test(cmd)) {
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.73.8",
3
+ "version": "2.73.9",
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": {