zames_pro 2.82.137 → 2.82.139

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +86 -63
  3. package/dist/agent-loop.js +3 -6
  4. package/dist/answer-parse.js +10 -16
  5. package/dist/attach-refs.js +3 -3
  6. package/dist/attachments.js +7 -8
  7. package/dist/backlog.js +2 -5
  8. package/dist/banner.js +4 -5
  9. package/dist/browser-chats.js +4 -4
  10. package/dist/browser-dom.js +16 -15
  11. package/dist/browser-login.js +9 -5
  12. package/dist/browser-pure.js +5 -5
  13. package/dist/browser.js +10 -20
  14. package/dist/changelog.js +14 -20
  15. package/dist/checkpoint.js +6 -10
  16. package/dist/cli-args.js +4 -6
  17. package/dist/code-blocks.js +3 -6
  18. package/dist/commands.js +5 -9
  19. package/dist/config-command.js +6 -4
  20. package/dist/config-commands.js +2 -3
  21. package/dist/config-live.js +7 -13
  22. package/dist/config-menu.js +5 -4
  23. package/dist/config.js +5 -8
  24. package/dist/deepseek-ui.js +7 -7
  25. package/dist/diagnostics.js +5 -7
  26. package/dist/diff.js +8 -10
  27. package/dist/extraTools.js +3 -3
  28. package/dist/fsutil.js +0 -4
  29. package/dist/gitTools.js +5 -4
  30. package/dist/glm-browser.js +2 -10
  31. package/dist/glm-chats.js +0 -1
  32. package/dist/glm-net-capture.js +1 -1
  33. package/dist/glm-ui.js +2 -2
  34. package/dist/goal.js +1 -4
  35. package/dist/i18n/common.js +2 -2
  36. package/dist/i18n.js +1 -2
  37. package/dist/index.js +0 -11
  38. package/dist/input/suggest.js +4 -7
  39. package/dist/input.js +3 -8
  40. package/dist/markdown.js +0 -1
  41. package/dist/mcp.js +2 -5
  42. package/dist/net-capture.js +1 -1
  43. package/dist/path-token.js +7 -14
  44. package/dist/prompt.js +4 -3
  45. package/dist/qwen-browser.js +1 -8
  46. package/dist/qwen-chats.js +0 -1
  47. package/dist/qwen-net-capture.js +4 -7
  48. package/dist/qwen-ui.js +0 -3
  49. package/dist/run-task.js +4 -4
  50. package/dist/self-review.js +0 -5
  51. package/dist/send-runtime.js +6 -10
  52. package/dist/send-telemetry.js +5 -7
  53. package/dist/send-throttle.js +4 -5
  54. package/dist/sessions.js +6 -7
  55. package/dist/spinner.js +3 -4
  56. package/dist/statusline.js +5 -9
  57. package/dist/system-prompt.js +1 -1
  58. package/dist/tui/widgets.js +0 -1
  59. package/llms-full.txt +44 -32
  60. package/llms.txt +1 -1
  61. package/package.json +1 -1
  62. package/scripts/postinstall.mjs +5 -0
@@ -6,7 +6,7 @@
6
6
  // and the chat-id scheme. DeepSeekBrowser is already large; folding a second
7
7
  // site into it would make both harder to audit. The shared surface is the
8
8
  // ChatProvider interface (types.ts); the agent loop only needs BrowserLike and
9
- // never sees either concrete class.
9
+ // never sees a concrete class.
10
10
  //
11
11
  // Verified against a live session (headless Chromium, guest + logged in):
12
12
  // input -> textarea.message-input-textarea
@@ -233,7 +233,6 @@ export class QwenBrowser {
233
233
  sendIntervalMs() {
234
234
  return this.minSendIntervalMs;
235
235
  }
236
- // ---------- lifecycle ----------
237
236
  async launch() {
238
237
  if (await profileLooksLocked(QWEN_USER_DATA_DIR)) {
239
238
  if (this.debug)
@@ -487,7 +486,6 @@ export class QwenBrowser {
487
486
  }
488
487
  catch { }
489
488
  }
490
- // ---------- login ----------
491
489
  // Are we signed in? Detected from the PAGE, the same way a human sees it: a
492
490
  // signed-out visitor has the header "Log in" / "Sign up" buttons, a signed-in
493
491
  // one shows the account menu instead. The `auths/refresh` API was the first
@@ -1005,7 +1003,6 @@ export class QwenBrowser {
1005
1003
  }
1006
1004
  return false;
1007
1005
  }
1008
- // ---------- chat management ----------
1009
1006
  async newChat() {
1010
1007
  const before = await this.getCurrentChatId();
1011
1008
  const btn = this.page.locator(NEW_CHAT_SELECTOR).first();
@@ -1272,7 +1269,6 @@ export class QwenBrowser {
1272
1269
  getLastTokenUsage() {
1273
1270
  return this._lastTokenUsage;
1274
1271
  }
1275
- // ---------- answer reading ----------
1276
1272
  async _readLastAnswerText() {
1277
1273
  if (this._netCapture && this._netCaptureAt >= this._lastSentAt) {
1278
1274
  return this._netCapture;
@@ -1346,7 +1342,6 @@ export class QwenBrowser {
1346
1342
  catch { }
1347
1343
  return false;
1348
1344
  }
1349
- // ---------- toggles (model thinking / search) ----------
1350
1345
  getToggleStatesSync() {
1351
1346
  return this._toggles;
1352
1347
  }
@@ -1523,7 +1518,6 @@ export class QwenBrowser {
1523
1518
  }
1524
1519
  catch { }
1525
1520
  }
1526
- // ---------- diagnostics ----------
1527
1521
  async dumpDom(filePath) {
1528
1522
  if (!this.page)
1529
1523
  throw new Error('qwen: browser not launched');
@@ -1566,7 +1560,6 @@ export class QwenBrowser {
1566
1560
  chatUrl(id) {
1567
1561
  return chatUrl(id);
1568
1562
  }
1569
- // ---------- ask ----------
1570
1563
  async _waitForSendSlot(agent) {
1571
1564
  if (!agent)
1572
1565
  return;
@@ -84,7 +84,6 @@ export function scrapeQwenChatMessages(opts) {
84
84
  }
85
85
  return out;
86
86
  }
87
- // ---------- API payload parsers (pure, unit-tested) ----------
88
87
  /**
89
88
  * Parse the chat list from `GET /api/v2/chats/?page=1&exclude_project=true`.
90
89
  * The shape has drifted between builds, so accept several:
@@ -223,13 +223,10 @@ export function isFinishedWithoutAnswer(body) {
223
223
  return extractFromSse(b) === '';
224
224
  }
225
225
  // True when the body carries Qwen's rate-limit / busy notice, so ask() can
226
- // back off instead of waiting out the deadline. Matched against the RAW body.
227
- // The classifier runs against the RAW body, so a phrase like `please try again
228
- // later` would also match a NORMAL answer (the model writing about retry
229
- // logic) and make ask() back off on a good turn. A real notice arrives in an
230
- // ERROR payload (`event: error` / `"type":"error"`) or as a code; answer text
231
- // lives in `choices[].delta.content`. We therefore only inspect the body when
232
- // it is NOT a plain answer stream, or when it carries no answer content.
226
+ // back off instead of waiting out the deadline. The classifier runs against the
227
+ // RAW body, where a phrase like `please try again later` also matches a NORMAL
228
+ // answer (the model writing about retry logic) and made ask() back off on a
229
+ // good turn. So it only counts an ERROR payload or a body with NO answer text.
233
230
  export function isRateLimitText(body) {
234
231
  const b = String(body || '');
235
232
  if (!b)
package/dist/qwen-ui.js CHANGED
@@ -174,9 +174,6 @@ export const STATUS_RE = /^(thinking|searching|generating|thinking completed|о
174
174
  // Qwen's rate-limit / busy notices (best-effort; the toast may be short-lived).
175
175
  export const RATE_LIMIT_RE = /too many requests|rate limit|too frequent|please try again later|слишком часто/i;
176
176
  export const SERVER_BUSY_RE = /server is busy|service unavailable|try again later/i;
177
- // ---------- toggles ----------
178
- // The "Auto" thinking/search toggle and the mode selector. Read-only probing
179
- // support; the model's thinking mode is set via config when supported.
180
177
  // ---------- toggles (thinking mode / web search) ----------
181
178
  // Qwen exposes thinking as a MODE dropdown (`.qwen-thinking-selector`, label
182
179
  // "Auto" by default) with options Auto / Thinking / Fast — NOT a boolean
package/dist/run-task.js CHANGED
@@ -330,7 +330,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
330
330
  });
331
331
  try {
332
332
  // Set when the loop ends without a real answer (iteration limit / watchdog)
333
- // so the one-shot mode can exit non-zero. See N5.
333
+ // so the one-shot mode can exit non-zero.
334
334
  let failure = null;
335
335
  // The machine-readable reason for the last task's outcome.
336
336
  let outcomeKind = 'ok';
@@ -351,7 +351,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
351
351
  // Calling it here made the spinner run for the whole pre-send phase (chat
352
352
  // creation, throttle wait) with no generation in flight.
353
353
  while (true) {
354
- // T7: per-task summary (duration + number of tool calls + tokens).
354
+ // Per-task summary: duration, number of tool calls and tokens spent.
355
355
  const taskStart = Date.now();
356
356
  // Token delta for THIS task: accumulated_token_usage is cumulative for
357
357
  // the whole chat, so the per-task spend is after - before. Null when the
@@ -461,8 +461,8 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
461
461
  else {
462
462
  outcomeKind = 'ok';
463
463
  }
464
- // T7: one summary line per task (duration + tool calls + tokens spent).
465
- // Printed even on an abort, so the operator sees what happened.
464
+ // One summary line per task, printed even on an abort, so the operator
465
+ // sees what happened.
466
466
  const tokensAfter = getTokenUsage ? getTokenUsage() : null;
467
467
  const tokenDelta = typeof tokensBefore === 'number' && typeof tokensAfter === 'number'
468
468
  ? Math.max(0, tokensAfter - tokensBefore)
@@ -122,7 +122,6 @@ async function copyContextFiles(srcDir, snapDir) {
122
122
  }
123
123
  }
124
124
  }
125
- // ---------- /self-review ----------
126
125
  export async function selfReview({ browser, focus, transcript, locale, }) {
127
126
  const t = translator(locale);
128
127
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -190,7 +189,6 @@ export async function selfReview({ browser, focus, transcript, locale, }) {
190
189
  console.log();
191
190
  return { snapDir, reportPath, changed };
192
191
  }
193
- // ---------- /self-diff ----------
194
192
  export async function selfDiff({ name, locale, }) {
195
193
  const t = translator(locale);
196
194
  const snapDir = path.join(SNAP_ROOT, name);
@@ -218,7 +216,6 @@ export async function selfDiff({ name, locale, }) {
218
216
  console.log(theme.system(t('self.no_diff')));
219
217
  }
220
218
  }
221
- // ---------- /self-apply ----------
222
219
  export async function selfApply({ name, locale, }) {
223
220
  const t = translator(locale);
224
221
  const snapDir = path.join(SNAP_ROOT, name);
@@ -247,7 +244,6 @@ export async function selfApply({ name, locale, }) {
247
244
  console.log(theme.system(t('self.backup', { dir: backupDir })));
248
245
  console.log(theme.warn('\n' + t('self.restart_hint') + '\n'));
249
246
  }
250
- // ---------- /self-list ----------
251
247
  export async function selfList({ locale, } = {}) {
252
248
  const t = translator(locale);
253
249
  const snapRoot = path.join(SNAP_ROOT);
@@ -285,7 +281,6 @@ export async function selfList({ locale, } = {}) {
285
281
  console.log();
286
282
  console.log(theme.system(t('self.list_commands') + '\n'));
287
283
  }
288
- // ---------- helpers ----------
289
284
  async function diffFiles(origDir, newDir) {
290
285
  const files = await listTsFiles(newDir).catch(() => []);
291
286
  const changed = [];
@@ -78,11 +78,9 @@ export class SendRuntime {
78
78
  if (typeof extra.retryAfterMs === 'number') {
79
79
  e.retryAfterMs = extra.retryAfterMs;
80
80
  }
81
- // A SUCCESS after a rate limit IS the lift of that block. onBlockLifted()
82
- // was the only producer of `blockMs`, but no provider's ask() calls it (a
83
- // rate limit is retried IN PLACE and the retry simply succeeds), and the
84
- // fold below clears `blockedAt` first. The measured duration is therefore
85
- // taken HERE, before the state is folded; it is attached to the SAME event
81
+ // A SUCCESS after a rate limit IS the lift of that block: the retry happens
82
+ // IN PLACE and simply succeeds. The duration is therefore measured HERE,
83
+ // before the fold below clears `blockedAt`, and attached to the SAME event
86
84
  // line, so one block still yields exactly one sample.
87
85
  let blockMs;
88
86
  if (outcome === 'ok' &&
@@ -101,11 +99,9 @@ export class SendRuntime {
101
99
  this.state = { ...this.state, blocked: true, blockedAt: this.now() };
102
100
  }
103
101
  if (outcome === 'rate_limit') {
104
- // The block START is only known AFTER the fold above (the adaptive path
105
- // sets it there). The event itself carries NO duration: a rate-limit
106
- // send is not the END of the block, so any value measured here is a
107
- // lower bound that would then be averaged together with the real
108
- // duration written by onBlockLifted — the same block counted TWICE.
102
+ // The event carries NO duration: a rate-limit send is not the END of the
103
+ // block, so a value measured here would be averaged together with the
104
+ // real duration measured at the lift — the same block counted TWICE.
109
105
  if (!this.state.blockedAt) {
110
106
  this.state = { ...this.state, blocked: true, blockedAt: this.now() };
111
107
  }
@@ -51,14 +51,12 @@ export function summarizeSends(events, now = Date.now()) {
51
51
  const age = now - t;
52
52
  return age >= 0 && age <= ms;
53
53
  }).length;
54
- // `blockMs` may arrive as a NUMBER (the writer) or a string (an older
55
- // build, a hand-edited file): a numeric string is accepted so the average
56
- // is not silently 0 for a file that records the block.
57
54
  const blocks = events
58
- // `Number(null)`/`Number(undefined)` — 0/NaN, and 0 IS a finite
59
- // non-negative number: an event that carries no block duration (the
60
- // rate-limit event only records `blockedAt`) was averaged as a ZERO-length
61
- // block, halving the reported average. Only a REAL value counts.
55
+ // `blockMs` may arrive as a NUMBER (the writer) or a string (an older
56
+ // build, a hand-edited file), so it is coerced. A missing value must NOT
57
+ // become 0: 0 is a finite non-negative number, and an event without a
58
+ // block duration (the rate-limit event only records `blockedAt`) was
59
+ // averaged as a ZERO-length block, halving the reported average.
62
60
  .map((e) => e.blockMs === null || e.blockMs === undefined ? NaN : Number(e.blockMs))
63
61
  // A finite value is not enough: the SUM of many large durations overflows
64
62
  // a JS number (two `Number.MAX_VALUE` entries add up to Infinity), so the
@@ -39,11 +39,10 @@ function prune(recent, now, windowMs) {
39
39
  */
40
40
  export function nextWaitMs(state, lastSentAt, now) {
41
41
  if (state.forcedWaitMs > 0) {
42
- // A forced wait must EXPIRE: a long Retry-After used to block every
43
- // send forever (the check was `> 0` only), until another event cleared
44
- // it. With the request stamp the remaining time is exact; without one
45
- // (a caller-built or restored state) the wait is reported ONCE, from
46
- // the current call, and expires on the next.
42
+ // A forced wait must EXPIRE: with only `forcedWaitMs > 0` a long
43
+ // Retry-After blocked every send until another event cleared it. The
44
+ // request stamp makes the remaining time exact; without one (a caller-built
45
+ // or restored state) the wait is reported ONCE, from the current call.
47
46
  if (state.forcedWaitAt) {
48
47
  const left = state.forcedWaitMs - (now - state.forcedWaitAt);
49
48
  return left > 0 ? left : 0;
package/dist/sessions.js CHANGED
@@ -133,13 +133,12 @@ function writeIndex({ id, workdir }) {
133
133
  }
134
134
  catch { }
135
135
  }
136
- // The last session for the working directory. If there is none —
137
- // we return the last session overall (useful when launched from a new place).
138
- // The last session for the working directory, restricted to `provider` when
139
- // given. A chat id only exists on ITS backend, so restoring a DeepSeek chat
140
- // while the active provider is Qwen (or vice versa) would fail to open — the
141
- // filter makes auto-restore and /last pick the right one (and fall back to the
142
- // global last only within the same provider).
136
+ // The last session for the working directory (the last one overall when the
137
+ // directory has none — useful when launched from a new place), restricted to
138
+ // `provider` when given. A chat id only exists on ITS backend, so restoring a
139
+ // DeepSeek chat while the active provider is Qwen (or vice versa) would fail to
140
+ // open; the filter makes auto-restore and /last pick the right one, and fall
141
+ // back to the global last only within the same provider.
143
142
  export function loadLastSession(workdir = '', provider) {
144
143
  try {
145
144
  const index = JSON.parse(fs.readFileSync(INDEX_FILE, 'utf-8'));
package/dist/spinner.js CHANGED
@@ -152,10 +152,9 @@ export function createSpinner(locale = 'ru') {
152
152
  }
153
153
  startThinking();
154
154
  },
155
- // The agent is waiting out the send-interval pause before a real send.
156
- // Show it as an ANIMATED status line with the remaining seconds, so the
157
- // spinner keeps moving during the pause (previously a static console line
158
- // was printed and the dots stayed frozen).
155
+ // The agent is waiting out the send-interval pause before a real send: an
156
+ // ANIMATED status with the remaining seconds, so the spinner keeps moving
157
+ // (a static line made the pause look frozen).
159
158
  sendPause: (seconds) => {
160
159
  if (pending)
161
160
  return;
@@ -58,10 +58,6 @@ export function runStatusLineCommand(workdir, command, info, timeoutMs = 4000) {
58
58
  // (the row overflows and the cursor arithmetic drifts).
59
59
  resolve(clipBadge(first));
60
60
  };
61
- // Only the FIRST LINE is shown, so there is no reason to keep collecting a
62
- // big blob: a chatty command (a JSON dump, `ls -R`) would grow `out`
63
- // unboundedly and the status row then repainted megabytes. Stop appending
64
- // once a short line is buffered (the rest of that line does not matter).
65
61
  const timer = setTimeout(() => {
66
62
  try {
67
63
  if (process.platform !== 'win32' && child.pid) {
@@ -74,15 +70,15 @@ export function runStatusLineCommand(workdir, command, info, timeoutMs = 4000) {
74
70
  catch { }
75
71
  finish();
76
72
  }, timeoutMs);
77
- // A status line is a short badge: cap what is buffered so a chatty
78
- // command cannot grow it (and the rendered row) without bound.
73
+ // Only the FIRST line is shown and the status line is a short badge, so the
74
+ // buffer is capped: a chatty command (a JSON dump, `ls -R`) would otherwise
75
+ // grow `out` and make the status row repaint megabytes on every tick.
79
76
  const MAX_OUT = 400;
80
77
  child.stdout?.on('data', (c) => {
81
78
  if (out.length >= MAX_OUT)
82
79
  return;
83
- // A chunk can be huge (one 64 KB read); clip it to the remaining space
84
- // so the buffer never exceeds MAX_OUT. An unbounded `out` made the
85
- // status row repaint megabytes on every tick.
80
+ // A chunk can be huge (one 64 KB read): clip it to the remaining space so
81
+ // the buffer never exceeds MAX_OUT.
86
82
  out += c.toString().slice(0, MAX_OUT - out.length);
87
83
  });
88
84
  child.on('error', () => {
@@ -124,7 +124,7 @@ export function renderContextSection(context) {
124
124
  f.content +
125
125
  '\n';
126
126
  }
127
- // B6: nested AGENTS.md/MEMORY.md from the directories this task touches.
127
+ // Nested AGENTS.md/MEMORY.md from the directories this task touches.
128
128
  // Deliberately a SEPARATE section: these apply only to part of the tree and
129
129
  // must not be mistaken for project-wide rules.
130
130
  for (const f of context.scopedAgents || []) {
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- // ---- palette (kept in one place so every menu is consistent) ----
4
3
  export const UI = {
5
4
  accent: '#c8b06a',
6
5
  accentDim: '#7a6b45',
package/llms-full.txt CHANGED
@@ -80,8 +80,8 @@ Key pieces:
80
80
  - **A persistent per-provider profile** (`~/.zames/profile` for DeepSeek,
81
81
  `~/.zames/qwen-profile` for Qwen) keeps you signed in; the headless
82
82
  User-Agent is patched so the site's CDN does not 403 the login.
83
- - **A send throttle** (15 s on DeepSeek, ~1 s on Qwen, which tolerates rapid
84
- sends; GLM uses an adaptive pause that starts at 0) keeps the web chat's rate
83
+ - **A send throttle** (15 s on DeepSeek, 4 s on Qwen, which tolerates a faster
84
+ pace; GLM uses an adaptive pause that starts at 0) keeps the web chat's rate
85
85
  limit happy during long tool-heavy runs.
86
86
  - **The agent is sandboxed** to the directory it was started in — no tool can
87
87
  read or write above it.
@@ -99,7 +99,8 @@ Key pieces:
99
99
 
100
100
  - **Tools like Claude Code / Codex** — `Read`, `Write`, `Edit`, `Bash`,
101
101
  `Glob`, `Grep`, plus `MultiEdit`, `ApplyPatch`, `LS`, `TodoWrite`, git and web
102
- tools. Every edit is backed by `/undo`.
102
+ tools. Every edit is backed by `/undo`, and `/rewind` restores the whole
103
+ working tree from a checkpoint.
103
104
  - **Runs while you keep typing** — queue messages during a task; they run right
104
105
  after it, in the same chat (like typing during generation on the web).
105
106
  - **Plan mode** — `/plan` (or `--plan`) drops all mutating tools, so the agent
@@ -132,7 +133,7 @@ Key pieces:
132
133
 
133
134
  ## Requirements
134
135
 
135
- - Node.js >= 20
136
+ - Node.js >= 22
136
137
  - An account on the chat you want to use — **DeepSeek** (`chat.deepseek.com`,
137
138
  the default), **Qwen** (`chat.qwen.ai`) or **GLM** (`chat.z.ai`). Qwen also
138
139
  works in guest mode for a few messages. On first launch zames asks for your login in the terminal
@@ -178,8 +179,8 @@ Credentials and toggles can also be edited from `/config`
178
179
 
179
180
  ## Switching providers (DeepSeek / Qwen / GLM)
180
181
 
181
- zames can drive either **DeepSeek** (`chat.deepseek.com`, the default) or
182
- **Qwen** (`chat.qwen.ai`):
182
+ zames can drive **DeepSeek** (`chat.deepseek.com`, the default), **Qwen**
183
+ (`chat.qwen.ai`) or **GLM** (`chat.z.ai`):
183
184
 
184
185
  ```bash
185
186
  /provider qwen # switch to Qwen (persisted) — applied live, no restart
@@ -193,9 +194,10 @@ zames can drive either **DeepSeek** (`chat.deepseek.com`, the default) or
193
194
  Notes:
194
195
 
195
196
  - The switch is **live**: zames closes the current backend and opens the other
196
- one in place, so no restart is needed. The two backends use **separate
197
+ one in place, so no restart is needed. The backends use **separate
197
198
  persistent profiles** (`~/.zames/profile` for DeepSeek, `~/.zames/qwen-profile`
198
- for Qwen), so switching never signs the other one out.
199
+ for Qwen, `~/.zames/glm-profile` for GLM), so switching never signs the other
200
+ one out.
199
201
  - A chat id belongs to ONE backend, so the new provider opens a **fresh chat**.
200
202
  If the previous chat had messages they are summarized and posted as a
201
203
  handoff with the next task; an empty chat is switched silently.
@@ -214,10 +216,10 @@ Notes:
214
216
  solved programmatically). zames therefore launches GLM headed by default and
215
217
  prints a note about it; `--headless` still forces headless (sends will hit
216
218
  the captcha). `--headed` is accepted everywhere.
217
- - **Send telemetry**: every send is recorded in
219
+ - **Send telemetry** (off by default): when enabled, every send is recorded in
218
220
  `~/.zames/logs/sends-<provider>.jsonl` (timing, mode flags, the outcome and
219
- the server error — never the prompt or the answer). Turn it off with
220
- `/config set browser.sendTelemetry false`. `/limits` shows the recent sends
221
+ the server error — never the prompt or the answer). Turn it on with
222
+ `/config set browser.sendTelemetry true`. `/limits` shows the recent sends
221
223
  and the current pause. GLM additionally uses an **adaptive pause**: it starts
222
224
  at 0 and backs off from a REAL rate limit.
223
225
  - **Warning**: automating a web chat may violate the service's terms of use and
@@ -279,11 +281,11 @@ The prompt is a small line editor with persistent history:
279
281
  Full-screen mode adds dialog navigation, so a long answer can be read and
280
282
  selected without leaving the terminal:
281
283
 
282
- - `ui.mouseScroll=false` (default) — the terminal owns the mouse: normal text
283
- selection works and the wheel is inert (it never scrolls the input history —
284
- history is arrow keys only).
285
- - `ui.mouseScroll=true` — the app consumes the wheel and scrolls the dialog;
286
- selecting text then needs `Shift`+click (the TUI convention).
284
+ - `ui.mouseScroll=true` (default) — the app consumes the wheel and scrolls the
285
+ dialog; selecting text then needs `Shift`+click (the TUI convention). The
286
+ wheel never scrolls the input history — history is arrow keys only.
287
+ - `ui.mouseScroll=false` — the terminal owns the mouse again: normal text
288
+ selection works and the wheel is inert.
287
289
  - `PgUp` / `PgDn` — move up/down the dialog half a screen.
288
290
  - `Home` / `End` — jump to the very top / bottom of the dialog (only while the
289
291
  input line is empty; with text typed they move the caret as usual).
@@ -320,8 +322,11 @@ instead of staying silent.
320
322
  zames --task <task text>
321
323
  zames --chat <id>
322
324
  zames --new-chat
325
+ zames --resume-last
323
326
  zames --resend-prompt
324
327
  zames --dir <path>
328
+ zames --provider <deepseek|qwen|glm>
329
+ zames --max-iter <n>
325
330
  zames --headless
326
331
  zames --headed
327
332
  zames --plan
@@ -352,15 +357,19 @@ zames --task "..." --output-format jsonl \
352
357
 
353
358
  The agent has the same style of tools as Claude Code / Codex CLI:
354
359
 
355
- - **File tools** — `Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`. `Read`
356
- returns raw content by default; pass `numbered=true` to get `cat -n`-style
357
- line numbers (for reference only — do not paste them into `Edit`).
360
+ - **File tools** — `Read`, `Write`, `Edit`, `Bash`, `BashOutput`, `Glob`,
361
+ `Grep`. `Read` returns raw content by default; pass `numbered=true` to get
362
+ `cat -n`-style line numbers (for reference only — do not paste them into
363
+ `Edit`). `Bash` takes `run_in_background`; `BashOutput` then polls or kills
364
+ that process.
358
365
  - **Extra tools** — `LS` (list a directory), `MultiEdit` (several edits to one
359
366
  file applied atomically), `TodoWrite` (session task checklist), `ApplyPatch`
360
367
  (multi-file patch in Codex's V4A format: `*** Begin Patch` … `*** End Patch`).
361
368
  - **Git** — `GitStatus`, `GitDiff`, `GitLog`, `GitShow`, `GitBranchList`,
362
369
  `GitAdd`, `GitCommit`, `GitPush`.
363
370
  - **Web** — `WebFetch`, `WebSearch`.
371
+ - **Operator** — `AskUser` (a modal question with options; skipped in plan
372
+ mode so a plan is never interrupted).
364
373
  - **Service** — `respond` (final answer to the operator, ends the task).
365
374
 
366
375
  All file tools stay inside the working directory (sandbox). `Write`/`Edit` and
@@ -368,11 +377,12 @@ All file tools stay inside the working directory (sandbox). `Write`/`Edit` and
368
377
 
369
378
  ## Slash commands
370
379
 
371
- Type / in the prompt for hints (Tab completes). Besides the session and
372
- config commands (/new, /chats, /resume, /cd, /status, /config,
373
- /undo, /transcript, /mcp, /skills, /memory, /init, /reload,
374
- /debug-dom, /help, /exit) there are a few that mirror Claude Code /
375
- Codex CLI:
380
+ Type / in the prompt for hints (Tab completes) — that list is always complete,
381
+ `/help <command>` describes any single one. Besides the session and config
382
+ commands (/new or /clear, /sessions, /chats, /resume-id, /last, /chat, /cd,
383
+ /pwd, /status, /config, /undo, /undo-list, /rewind, /rewind-list, /transcript,
384
+ /tasks, /mcp, /skills, /memory, /remember, /init, /reload, /debug-dom, /help,
385
+ /exit) there are a few that mirror Claude Code / Codex CLI:
376
386
 
377
387
  - /diff [--staged] — show the working-tree git diff (--staged for the index).
378
388
  - /diffstat — a one-line change summary (`git diff --stat`).
@@ -434,11 +444,12 @@ tests`). /cron "<min> <hour> <dom> <month> <dow>" <task> — run on a schedule.
434
444
  current task.
435
445
 
436
446
  The token context is also shown live: the status line above the input has the
437
- spinner/text on the left and the context on the right (e.g. `125k · 13%`,
438
- percent of a 1M context). It is COLORED by fill level: green below 50%,
439
- yellow 50-80%, red above 80%. It comes from the provider's own token
440
- counter (DeepSeek's `accumulated_token_usage`, Qwen's `input_tokens`, GLM's
441
- `prompt_tokens`) and is
447
+ spinner/text on the left and the context on the right (e.g. `125k · 13%`). The
448
+ percentage is of the ACTIVE provider's window — 1M for DeepSeek, a per-model
449
+ value for Qwen and GLM (`qwen.contextLimit` / `glm.contextLimit`) — and it is
450
+ COLORED by fill level: green below 50%, yellow 50-80%, red above 80%.
451
+ It comes from the provider's own token counter (DeepSeek's
452
+ `accumulated_token_usage`, Qwen's `input_tokens`, GLM's `prompt_tokens`) and is
442
453
  hidden until the first answer delivers it.
443
454
 
444
455
  ## Project context, skills and memory
@@ -571,8 +582,9 @@ Examples:
571
582
  /config lang en
572
583
  ```
573
584
 
574
- Changes are written to the project `.zamesrc.json` and applied right away
575
- (where possible without a restart).
585
+ Changes are written to the HOME config (`~/.zames/config.json`), NOT to the
586
+ project `.zamesrc.json` — a personal toggle must not leak into git — and applied
587
+ right away (where possible without a restart).
576
588
 
577
589
  ### Language
578
590
 
@@ -629,7 +641,7 @@ signed-in browser profile.
629
641
  **How do I install a terminal AI coding agent?**
630
642
 
631
643
  ```bash
632
- npm install -g zames_pro # Node.js >= 20; Chromium is fetched automatically
644
+ npm install -g zames_pro # Node.js >= 22; Chromium is fetched automatically
633
645
  cd your-project
634
646
  zames
635
647
  ```
package/llms.txt CHANGED
@@ -20,7 +20,7 @@ LLM-friendly map of the project. Everything below is plain Markdown.
20
20
  - **Supported backends:** DeepSeek (`chat.deepseek.com`, default), Qwen
21
21
  (`chat.qwen.ai`), GLM (`chat.z.ai`). Live switch with
22
22
  `/provider <deepseek|qwen|glm>`; each backend keeps its own browser profile.
23
- - **Language / stack:** TypeScript (strict), Node.js >= 20, Playwright,
23
+ - **Language / stack:** TypeScript (strict), Node.js >= 22, Playwright,
24
24
  Ink/React TUI, MCP client.
25
25
  - **License:** MIT. **Not affiliated** with DeepSeek, Alibaba or Zhipu AI.
26
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.82.137",
3
+ "version": "2.82.139",
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": {
@@ -1,3 +1,8 @@
1
+ // Post-install step: fetch the Chromium build Playwright drives and, on a
2
+ // Linux box that needs it, its system libraries. It runs BEFORE tsx is
3
+ // guaranteed to be installed, so it stays a plain .mjs script. Every failure
4
+ // is a WARNING, never a non-zero exit: a missing browser must not break
5
+ // `npm install`, and the warning names the manual command to run instead.
1
6
  import { spawnSync } from 'node:child_process'
2
7
 
3
8
  const NL = String.fromCharCode(10)