zames_pro 2.82.137 → 2.82.138

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 (58) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/agent-loop.js +3 -6
  3. package/dist/answer-parse.js +10 -16
  4. package/dist/attach-refs.js +3 -3
  5. package/dist/attachments.js +7 -8
  6. package/dist/backlog.js +2 -5
  7. package/dist/banner.js +4 -5
  8. package/dist/browser-chats.js +4 -4
  9. package/dist/browser-dom.js +16 -15
  10. package/dist/browser-login.js +9 -5
  11. package/dist/browser-pure.js +5 -5
  12. package/dist/browser.js +10 -20
  13. package/dist/changelog.js +14 -20
  14. package/dist/checkpoint.js +6 -10
  15. package/dist/cli-args.js +4 -6
  16. package/dist/code-blocks.js +3 -6
  17. package/dist/commands.js +5 -9
  18. package/dist/config-command.js +6 -4
  19. package/dist/config-commands.js +2 -3
  20. package/dist/config-live.js +7 -13
  21. package/dist/config-menu.js +5 -4
  22. package/dist/config.js +5 -8
  23. package/dist/deepseek-ui.js +7 -7
  24. package/dist/diagnostics.js +5 -7
  25. package/dist/diff.js +8 -10
  26. package/dist/extraTools.js +3 -3
  27. package/dist/fsutil.js +0 -4
  28. package/dist/gitTools.js +5 -4
  29. package/dist/glm-browser.js +2 -10
  30. package/dist/glm-chats.js +0 -1
  31. package/dist/glm-net-capture.js +1 -1
  32. package/dist/glm-ui.js +2 -2
  33. package/dist/goal.js +1 -4
  34. package/dist/i18n/common.js +2 -2
  35. package/dist/i18n.js +1 -2
  36. package/dist/index.js +0 -11
  37. package/dist/input/suggest.js +4 -7
  38. package/dist/input.js +3 -8
  39. package/dist/markdown.js +0 -1
  40. package/dist/mcp.js +2 -5
  41. package/dist/net-capture.js +1 -1
  42. package/dist/path-token.js +7 -14
  43. package/dist/prompt.js +4 -3
  44. package/dist/qwen-browser.js +1 -8
  45. package/dist/qwen-chats.js +0 -1
  46. package/dist/qwen-net-capture.js +4 -7
  47. package/dist/qwen-ui.js +0 -3
  48. package/dist/run-task.js +4 -4
  49. package/dist/self-review.js +0 -5
  50. package/dist/send-runtime.js +6 -10
  51. package/dist/send-telemetry.js +5 -7
  52. package/dist/send-throttle.js +4 -5
  53. package/dist/sessions.js +6 -7
  54. package/dist/spinner.js +3 -4
  55. package/dist/statusline.js +5 -9
  56. package/dist/system-prompt.js +1 -1
  57. package/dist/tui/widgets.js +0 -1
  58. package/package.json +1 -1
package/dist/cli-args.js CHANGED
@@ -1,4 +1,4 @@
1
- // PURE CLI argument parsing . index.ts used to hold `args` as a
1
+ // PURE CLI argument parsing. index.ts used to hold `args` as a
2
2
  // module-level constant plus three helpers closing over it, which made the
3
3
  // parsing impossible to unit-test in isolation (index.ts is the entry point and
4
4
  // is not covered). `createCliArgs(argv)` binds the argv ONCE and returns the
@@ -23,11 +23,9 @@ export function createCliArgs(argv) {
23
23
  const args = Array.isArray(argv) ? argv : [];
24
24
  const getArg = (flag, fallback = null) => {
25
25
  const i = args.indexOf(flag);
26
- // `args[i + 1]` truthiness means an empty string or a missing value falls
27
- // back — the historical behavior (an empty `--task ''` is not a task).
28
- // NOTE: this does NOT special-case a following FLAG, so `--task --debug`
29
- // returns `--debug` (the original inline helper did the same); we preserve
30
- // it rather than "fix" a long-standing quirk in a refactor.
26
+ // Truthiness, not `!== undefined`: an EMPTY value (`--task ''`) must
27
+ // fall back too - an empty task is not a task. A following FLAG is not
28
+ // special-cased either (see the CliArgs doc above).
31
29
  return i !== -1 && args[i + 1] ? args[i + 1] : fallback;
32
30
  };
33
31
  const hasFlag = (flag) => args.includes(flag);
@@ -1,9 +1,6 @@
1
- // PURE extraction of fenced code blocks from a Markdown answer.
2
- //
3
- // The TUI cannot offer a real "copy button", so copying a code block is bound
4
- // to a key / command. That needs the RAW fenced text (the highlighted render is
5
- // full of ANSI and must NOT be copied), which is what this module returns.
6
- // Kept pure so it is unit-tested without a terminal.
1
+ // PURE extraction of fenced code blocks from a Markdown answer. It returns the
2
+ // RAW fenced text: the highlighted render is full of ANSI and must never be
3
+ // what gets copied out of the TUI.
7
4
  /**
8
5
  * Extract every fenced code block (``` or ~~~) from `md`, in order. Handles a
9
6
  * longer closing fence than the opening one and an unterminated final block
package/dist/commands.js CHANGED
@@ -3,9 +3,9 @@ import path from 'path';
3
3
  import os from 'os';
4
4
  import { isImageName } from './attachments.js';
5
5
  import { translate } from './i18n.js';
6
- // Helpers for the extra slash commands (/diff, /cost, /export, /doctor,
7
- // /review, /add-dir). Pure functions, unit-tested without a
8
- // live agent/browser. index.ts only renders their output.
6
+ // Helpers for the slash commands (/diff, /cost, /export, /doctor, /review,
7
+ // /add-dir, /queue, /goal, custom commands). Pure functions, unit-tested
8
+ // without a live agent/browser; index.ts only renders their output.
9
9
  const NL = String.fromCharCode(10);
10
10
  const CR = String.fromCharCode(13);
11
11
  // ---------- /diff ----------
@@ -27,8 +27,8 @@ export function formatDiff(diffText, opts = {}, t = translate('en')) {
27
27
  const rest = lines.length - maxLines;
28
28
  return head + NL + t('diff.more_lines', { n: String(rest) });
29
29
  }
30
- // Git arguments for the `/diff` command. An ARRAY (no shell): the caller runs
31
- // them through runGitArgs(), so nothing is interpreted by a shell. Pure.
30
+ // Git arguments for the `/diff` command. An ARRAY, not a shell string: the
31
+ // caller runs them through runGitArgs(), so nothing is interpreted by a shell.
32
32
  export function diffGitArgs(staged = false) {
33
33
  return staged ? ['diff', '--staged'] : ['diff'];
34
34
  }
@@ -1038,10 +1038,6 @@ export function mergeMessages(msgs) {
1038
1038
  list.length +
1039
1039
  ' messages while you were working. They are listed below in order — ' +
1040
1040
  'treat them as one instruction.';
1041
- // The parts are OPERATOR text: a line that reads `---` (a Markdown rule, a
1042
- // pasted diff) would spoof a message boundary and the model would see an
1043
- // extra message. Neutralize such a line inside a part; the separator below
1044
- // stays the only one.
1045
1041
  // The message text is OPERATOR input: a line that reads `---` (a Markdown
1046
1042
  // rule, a pasted diff) or repeats the header sentence spoofs a message
1047
1043
  // BOUNDARY / count — the model then sees an extra message that never
@@ -1,4 +1,4 @@
1
- // The /config subcommand DISPATCH . The PURE text rendering lives
1
+ // The /config subcommand DISPATCH. The PURE text rendering lives
2
2
  // in config-commands.ts; this module owns the parsing/routing of the text
3
3
  // subcommands (list/get/set/reset/lang/path) and prints through injected
4
4
  // callbacks. Everything that touches live state (the runtime config object,
@@ -26,7 +26,7 @@ export async function handleConfigCommand(input, deps) {
26
26
  ? all
27
27
  : ['/config', ...all];
28
28
  const sub = (parts[1] || '').toLowerCase();
29
- // With no subcommand - the menu (or a text list in non-TTY).
29
+ // Bare `/config` opens the menu (a text list in non-TTY, via openMenu).
30
30
  if (!sub || sub === 'menu') {
31
31
  await openMenu();
32
32
  return;
@@ -39,7 +39,8 @@ export async function handleConfigCommand(input, deps) {
39
39
  print(theme.system(t('cfg.paths', { global: homeConfigPath, project: projectConfigPath })));
40
40
  return;
41
41
  }
42
- // /config lang <ru|en> - quick access to ui.locale
42
+ // `/config lang <ru|en>` is a shortcut for `set ui.locale`; `язык` is an
43
+ // accepted alias because the operator types the command in their own language.
43
44
  if (sub === 'lang' || sub === 'language' || sub === 'язык') {
44
45
  const val = parts[2];
45
46
  if (!val) {
@@ -123,7 +124,8 @@ export async function handleConfigCommand(input, deps) {
123
124
  print(theme.assistant(t('cfg.reset', { v: key })));
124
125
  return;
125
126
  }
126
- // Unknown subcommand - show the list.
127
+ // An unknown subcommand reports the key AND shows the list, so the operator
128
+ // sees the valid keys without a second command.
127
129
  printErr(theme.error(t('cfg.unknown_key', { v: sub })));
128
130
  showList();
129
131
  }
@@ -1,4 +1,4 @@
1
- // PURE rendering for the /config command's TEXT output . These
1
+ // PURE rendering for the /config command's TEXT output. These
2
2
  // helpers format the schema into lines; they touch no browser/terminal state,
3
3
  // so they are unit-tested without a live agent (the /config dispatch in
4
4
  // index.ts only prints what these return). The interactive menu lives in
@@ -6,8 +6,7 @@
6
6
  import { theme } from './theme.js';
7
7
  /**
8
8
  * The value shown for a field in the LIST: the default marker, on/off for a
9
- * boolean, a fixed mask for a password, otherwise the JSON form. Mirrors the
10
- * long-used inline logic so the output is byte-for-byte identical.
9
+ * boolean, a fixed mask for a password, otherwise the JSON form.
11
10
  */
12
11
  export function configListValue(field, value, t) {
13
12
  if (value === undefined)
@@ -4,9 +4,9 @@
4
4
  // construction, so a change must be pushed onto the live instance too or it
5
5
  // is silently a no-op until a restart. The path->target mapping is PURE and
6
6
  // unit-tested here; index.ts wires it to the live instances.
7
- /** config path -> field name on the browser instance. Both backends
8
- * share these names; a field that is absent on the active backend
9
- * (e.g. DeepSeek-only thinkingExtraMs on Qwen) is skipped, not thrown. */
7
+ /** config path -> field name on the browser instance. All three backends
8
+ * share these names; a field that is absent on the active backend is skipped
9
+ * rather than throwing, so one map serves every provider. */
10
10
  export const BROWSER_LIVE_CONFIG = {
11
11
  'browser.answerTimeoutMs': 'answerTimeoutMs',
12
12
  'browser.askRetries': 'askRetries',
@@ -30,11 +30,9 @@ export function applyBrowserLiveConfig(path, value, browser) {
30
30
  const field = BROWSER_LIVE_CONFIG[path];
31
31
  if (!field || !(field in browser))
32
32
  return false;
33
- // A timing value must be a FINITE NON-NEGATIVE number. `applyBrowserLiveConfig`
34
- // wrote anything it was given, so a NaN/negative value reached a live field
35
- // (a timer with NaN fires immediately), and `answerTimeoutMs = 0` SKIPPED
36
- // the derived `askDeadlineMs` update, leaving a deadline that no longer
37
- // matches the timeout. Reject the bad value instead of corrupting the field.
33
+ // A timing value must be a FINITE NON-NEGATIVE number: a NaN reached a live
34
+ // field (a timer with NaN fires immediately) and a negative one is nonsense.
35
+ // Reject the value instead of corrupting the field.
38
36
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
39
37
  return false;
40
38
  }
@@ -45,11 +43,7 @@ export function applyBrowserLiveConfig(path, value, browser) {
45
43
  // give up before the browser's own timeout ever fired. Re-derive it the same
46
44
  // way the constructor does.
47
45
  if (field === 'answerTimeoutMs' && 'askDeadlineMs' in browser) {
48
- // ANY accepted number re-derives the deadline; the old `value > 0` guard
49
- // left the deadline at its stale value when the timeout was set to 0.
50
- if (typeof value === 'number' && Number.isFinite(value)) {
51
- browser.askDeadlineMs = value + 60_000;
52
- }
46
+ browser.askDeadlineMs = value + 60_000;
53
47
  }
54
48
  return true;
55
49
  }
@@ -11,9 +11,9 @@
11
11
  import { theme } from './theme.js';
12
12
  import { visLen } from './input.js';
13
13
  const ESC = String.fromCharCode(27);
14
- // Strip ANSI sequences to compute the visible length of a line. (A module-
15
- // level name distinct from input.ts's internal helper; the old regex had a
16
- // stray backtick and never matched a real CSI, so visCols was over-counted.)
14
+ // Strip ANSI sequences to compute the visible length of a line. A local copy
15
+ // (input.ts keeps its own private helper): the regex must match a real CSI,
16
+ // otherwise visCols over-counts and the erase moves to the wrong row.
17
17
  function stripAnsiLocal(s) {
18
18
  return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
19
19
  }
@@ -197,7 +197,8 @@ export function runConfigMenu(opts) {
197
197
  lines.push(...tail);
198
198
  // Erase the previous block and print the new one. We count VISUAL lines
199
199
  // (accounting for wrapping by terminal width), otherwise on narrow
200
- // terminals you get "garbage" from unerased tails.
200
+ // terminals the unerased tails leave garbage behind.
201
+ //
201
202
  // Reserve the last column. A line whose visible width reaches exactly the
202
203
  // terminal width hits the autowrap edge: depending on the terminal it
203
204
  // either consumes two rows for the following `\n` or keeps a pending wrap,
package/dist/config.js CHANGED
@@ -611,14 +611,11 @@ export function getConfigField(path) {
611
611
  }
612
612
  export function getByPath(obj, path) {
613
613
  const parts = path.split('.');
614
- // Same guard as setByPath: a dotted path must never reach the PROTOTYPE
615
- // chain. `__proto__` used to return Object.prototype (an object), and
616
- // `constructor` / `toString` the corresponding FUNCTION — the /config list
617
- // then rendered `{}` for a field whose path did not exist instead of the
618
- // default marker. Reading those keys is never meaningful for a config path.
619
- // `toString`, `valueOf`, `hasOwnProperty`, ... are inherited METHODS, not
620
- // config values: reading them through a dotted path returned a FUNCTION.
621
- // Only an OWN property counts.
614
+ // Two guards, both load-bearing. UNSAFE_PATH_KEYS keeps a dotted path off
615
+ // the PROTOTYPE chain (`__proto__` answered Object.prototype, `constructor`
616
+ // a function, and the /config list rendered `{}` instead of the default
617
+ // marker). The hasOwnProperty check keeps inherited METHODS out: `toString`
618
+ // / `valueOf` are not config values, and only an OWN property counts.
622
619
  if (parts.some((p) => UNSAFE_PATH_KEYS.has(p)))
623
620
  return undefined;
624
621
  let cur = obj;
@@ -45,9 +45,9 @@ export const SEND_SELECTORS = [
45
45
  'button[aria-label*="отправ" i]',
46
46
  ];
47
47
  // DeepSeek's own button classes. Used as a coarse "is this a DeepSeek button?"
48
- // filter in the raw-DOM fallbacks for Stop and for the send/stop icon
49
- // detection. Kept as a source string because the fallback runs INSIDE
50
- // page.evaluate, which cannot receive a RegExp object.
48
+ // filter in the raw-DOM fallbacks for Stop and the send/stop icon detection.
49
+ // Passed to page.evaluate as a source string (`.source`), because the in-page
50
+ // callback cannot receive a RegExp object.
51
51
  export const BUTTON_CLASS_RE = /ds-button--(circle|primary|filled)/i;
52
52
  // ---------- stop ----------
53
53
  export const STOP_SELECTORS = [
@@ -108,10 +108,10 @@ export const MESSAGE_CONTAINER_SELECTORS = [
108
108
  export const ASSISTANT_CLASS_RE = /ds-assistant-message|assistant-message/i;
109
109
  export const ASSISTANT_SELECTOR = '[class*="ds-assistant-message"]';
110
110
  export const ANSWER_MARKDOWN_SELECTOR = '[class*="ds-markdown"]';
111
- // A cheap "did the page grow?" signal (_chatSignal): count message nodes and
112
- // measure the LAST answer node's text length. The old code read
113
- // document.body.innerText.length every tick, which serializes the WHOLE page
114
- // (sidebar, history, menus) and forces a layout — expensive on a long chat.
111
+ // The node/markdown selectors for the cheap "did the page grow?" signal
112
+ // (chatSignalInDom): counting message nodes and measuring the LAST answer's
113
+ // length instead of document.body.innerText.length, which serializes the WHOLE
114
+ // page (sidebar, history, menus) and forces a layout on every tick.
115
115
  export const CHAT_SIGNAL_NODES_SELECTOR = '[class*="ds-message"], [class*="chat-message"], .ds-markdown';
116
116
  export const MARKDOWN_SELECTOR = '.ds-markdown';
117
117
  // ---------- new chat ----------
@@ -24,13 +24,11 @@ export function summarizeDiagnostics(raw, maxChars) {
24
24
  if (!text)
25
25
  return null;
26
26
  // tsc prints nothing on success, but with `--watch` it prints a banner
27
- // ("Found 0 errors. Watching for file changes.") that is longer than the
28
- // old 40-char cutoff — so every edit in watch mode appended a pointless
29
- // diagnostics block to the tool result. Treat a ZERO-count banner as clean
30
- // however long it is; a non-zero count still falls through to the lines.
31
- // Strip ANSI colour codes and a leading `[12:00:00]`-style timestamp before
32
- // the clean checks: a watch-mode banner is usually coloured and/or
33
- // prefixed, and the anchored regexes missed it — every edit then
27
+ // ("Found 0 errors. Watching for file changes."). Treat a ZERO-count banner
28
+ // as clean however long it is; a non-zero count still falls through to the
29
+ // lines. The banner is usually coloured and/or prefixed with a
30
+ // `[12:00:00]`-style timestamp, so both are stripped before the clean
31
+ // checks — with them in place the anchored regexes missed it and every edit
34
32
  // appended a pointless diagnostics block.
35
33
  const plain = text.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
36
34
  const cleanCheck = plain.replace(/^\[[^\]]*\]\s*/, '');
package/dist/diff.js CHANGED
@@ -5,12 +5,11 @@ export function unifiedDiff(a, b, { label = '' } = {}) {
5
5
  const out = [];
6
6
  if (label)
7
7
  out.push('... ' + label);
8
- // A LINE diff, not a positional compare: an insertion at the top of a
9
- // file shifts every following line, and comparing line i with line i
10
- // reported the WHOLE file as removed AND added (a 20-line file with one
11
- // new first line produced 41 diff lines). The undo/`/diff` preview became
12
- // unreadable and the model was told everything changed. A longest-common-
13
- // subsequence walk emits only the lines that really differ.
8
+ // A LINE diff, not a positional compare: an insertion at the top shifts
9
+ // every following line, and comparing line i with line i reported the WHOLE
10
+ // file as removed AND added (a 20-line file with one new first line produced
11
+ // 41 diff lines), which made the undo preview unreadable and told the model
12
+ // that everything changed.
14
13
  for (const op of diffOps(la, lb)) {
15
14
  if (op.kind === 'same')
16
15
  continue;
@@ -19,10 +18,9 @@ export function unifiedDiff(a, b, { label = '' } = {}) {
19
18
  return out.join(String.fromCharCode(10));
20
19
  }
21
20
  /**
22
- * Longest-common-subsequence line diff (Hunt–Szymanski style table, O(n*m)
23
- * memory). Files here are a handful of lines (a single edit preview), so the
24
- * simple dynamic-programming table is fine and keeps the module dependency-
25
- * free. A change pair is emitted as `-old` then `+new`, like a real diff.
21
+ * Longest-common-subsequence line diff (an O(n*m) dynamic-programming table,
22
+ * which keeps the module dependency-free). A change pair is emitted as
23
+ * `-old` then `+new`, like a real diff.
26
24
  */
27
25
  function diffOps(a, b) {
28
26
  const n = a.length;
@@ -1,14 +1,14 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { safePath } from './sandbox.js';
4
- // Extra tools that bring zames closer to Claude Code / Codex CLI:
4
+ // Extra tools borrowed from Claude Code / Codex CLI:
5
5
  // * LS - list a directory (Claude Code has LS; Codex has list_dir)
6
6
  // * MultiEdit - several edits to ONE file applied atomically (Claude Code)
7
7
  // * ApplyPatch - multi-file V4A-style patch (Codex apply_patch)
8
8
  // * TodoWrite - session task checklist (Claude Code TodoWrite)
9
9
  //
10
- // The core file tools (Read/Write/Edit/Bash/Glob/Grep) live in tools.ts.
11
- // These are additive: they do not change the existing tool behaviour.
10
+ // The core file tools (Read/Write/Edit/Bash/Glob/Grep) live in tools.ts; these
11
+ // are additive and do not change their behaviour.
12
12
  const NL = String.fromCharCode(10);
13
13
  function decodeContent(content, contentBase64) {
14
14
  if (typeof contentBase64 === 'string' && contentBase64.length) {
package/dist/fsutil.js CHANGED
@@ -6,10 +6,6 @@ import path from 'path';
6
6
  // truncated/broken file behind. The temp name embeds the pid so two
7
7
  // concurrent processes never collide on it.
8
8
  //
9
- // Kept in its own module because the pattern used to be copy-pasted in at
10
- // least three places; drift between the copies is exactly how one of them
11
- // silently loses its atomicity.
12
- //
13
9
  // The file is created 0o600: ~/.zames/config.json holds the
14
10
  // browser password in clear text alongside sessions/undo history, and the
15
11
  // default umask (022) would make them readable by other users on a
package/dist/gitTools.js CHANGED
@@ -89,9 +89,9 @@ export async function getGitContext(workdir) {
89
89
  const remoteR = await runGitArgs(['remote'], workdir, 5000);
90
90
  const aheadR = await runGitArgs(['rev-list', '--left-right', '--count', '@{upstream}...HEAD'], workdir, 5000);
91
91
  const branch = branchR.trim() || '(detached HEAD)';
92
- // The shared runner answers an EMPTY successful run with a sentinel. Storing
93
- // it verbatim told the model the tree had a modified "file" that is really
94
- // an internal string — every task started with a false dirty-tree premise.
92
+ // The sentinel is an internal string, not a path: kept verbatim it told the
93
+ // model the tree had a modified "file" by that name, so every task started
94
+ // with a false dirty-tree premise.
95
95
  const statusText = statusR.trim() === NO_OUTPUT_SENTINEL ? '' : statusR;
96
96
  const statusLines = statusText
97
97
  .split(String.fromCharCode(10))
@@ -286,7 +286,8 @@ export function createGitTools(workdir) {
286
286
  if (!msg.trim()) {
287
287
  return 'Error: message is empty.';
288
288
  }
289
- // We stage everything only if the index is empty (as the description promises).
289
+ // Stage everything only when the index is empty, which is what the tool
290
+ // description promises the model.
290
291
  const staged = await runGitArgs(['diff', '--cached', '--name-only'], workdir, 10_000);
291
292
  const stagedTrimmed = staged.trim();
292
293
  const hasStaged = stagedTrimmed !== '' && stagedTrimmed !== NO_OUTPUT_SENTINEL;
@@ -3,8 +3,8 @@
3
3
  // WHY a separate class (the same reason QwenBrowser exists): the backends
4
4
  // differ in EVERY layer we touch — selectors, the send flow, the SSE format and
5
5
  // the chat-id scheme. The shared surface is the ChatProvider interface
6
- // (types.ts); the agent loop only needs BrowserLike and never sees either
7
- // concrete class.
6
+ // (types.ts); the agent loop only needs BrowserLike and never sees a concrete
7
+ // class.
8
8
  //
9
9
  // Verified against a LIVE signed-in session (headless + headed Chromium):
10
10
  // input -> textarea#chat-input
@@ -233,7 +233,6 @@ export class GlmBrowser {
233
233
  sendIntervalMs() {
234
234
  return this.sendRuntime.intervalMs();
235
235
  }
236
- // ---------- lifecycle ----------
237
236
  async launch() {
238
237
  if (await profileLooksLocked(GLM_USER_DATA_DIR)) {
239
238
  if (this.debug)
@@ -398,7 +397,6 @@ export class GlmBrowser {
398
397
  }
399
398
  throw new Error('glm: the browser profile is still locked');
400
399
  }
401
- // ---------- login ----------
402
400
  /**
403
401
  * A FAST login probe with NO long waits (the old isLoggedIn polled up to
404
402
  * 20 s, which is a false "logged out" on a slow network). The signal is the
@@ -754,7 +752,6 @@ export class GlmBrowser {
754
752
  }
755
753
  catch { }
756
754
  }
757
- // ---------- network capture ----------
758
755
  _installNetHook() {
759
756
  const pg = this.page;
760
757
  if (!pg)
@@ -857,7 +854,6 @@ export class GlmBrowser {
857
854
  }
858
855
  catch { }
859
856
  }
860
- // ---------- chat management ----------
861
857
  async newChat() {
862
858
  const before = await this.getCurrentChatId();
863
859
  const btn = this.page.locator(NEW_CHAT_SELECTOR).first();
@@ -1081,7 +1077,6 @@ export class GlmBrowser {
1081
1077
  getLastTokenUsage() {
1082
1078
  return this._lastTokenUsage;
1083
1079
  }
1084
- // ---------- answer reading ----------
1085
1080
  async _readLastAnswerText() {
1086
1081
  if (this._netCapture && this._netCaptureAt >= this._lastSentAt) {
1087
1082
  return this._netCapture;
@@ -1239,7 +1234,6 @@ export class GlmBrowser {
1239
1234
  }
1240
1235
  catch { }
1241
1236
  }
1242
- // ---------- toggles ----------
1243
1237
  getToggleStatesSync() {
1244
1238
  return this._toggles;
1245
1239
  }
@@ -1295,7 +1289,6 @@ export class GlmBrowser {
1295
1289
  this._notice(this._t('glm.mode_mismatch', { v: mismatch.join(', ') }));
1296
1290
  }
1297
1291
  }
1298
- // ---------- diagnostics ----------
1299
1292
  async dumpDom(filePath) {
1300
1293
  if (!this.page)
1301
1294
  throw new Error('glm: browser not launched');
@@ -1332,7 +1325,6 @@ export class GlmBrowser {
1332
1325
  chatUrl(id) {
1333
1326
  return chatUrl(id);
1334
1327
  }
1335
- // ---------- ask ----------
1336
1328
  async _waitForSendSlot(agent) {
1337
1329
  if (!agent)
1338
1330
  return;
package/dist/glm-chats.js CHANGED
@@ -78,7 +78,6 @@ export function scrapeGlmChatMessages(opts) {
78
78
  }
79
79
  return out;
80
80
  }
81
- // ---------- API payload parsers (pure, unit-tested) ----------
82
81
  /**
83
82
  * Parse `GET /api/v1/chats/?page=1`. The live shape is a BARE ARRAY; older /
84
83
  * other builds wrap it in `{data:[...]}` or key it by id. Returns [] for
@@ -280,7 +280,6 @@ function hasAnswerContent(body) {
280
280
  // ERROR payload (`type: "error"` / a code) or as a bare notice with NO answer
281
281
  // content; answer text lives in `phase: "answer"` chunks.
282
282
  const ERROR_PAYLOAD_RE = /"type"\s*:\s*"error"|"error"\s*:|\{\s*"code"\s*:/i;
283
- /** True when the RAW body is z.ai's rate-limit notice. */
284
283
  /**
285
284
  * True when z.ai answers that the ACCOUNT quota is exhausted
286
285
  * (`insufficient_quota`, HTTP 429). Distinct from a transient rate limit:
@@ -299,6 +298,7 @@ export function isQuotaExhaustedText(body) {
299
298
  return false;
300
299
  return /insufficient_quota|quota (?:exceeded|exhausted)|exceeded your current quota/i.test(b);
301
300
  }
301
+ /** True when the RAW body is z.ai's rate-limit notice. */
302
302
  export function isRateLimitText(body) {
303
303
  const b = String(body || '');
304
304
  if (!b)
package/dist/glm-ui.js CHANGED
@@ -162,8 +162,8 @@ export const GUEST_LIMIT_RE = /guest.*limit|log ?in to continue|sign ?in to cont
162
162
  // The completion body carries the mode flags. `preview_mode` and the
163
163
  // `general_agent` flag change the ANSWER FORMAT (they enable z.ai's own agent
164
164
  // runtime, which emits its own tool calls), and that would break the zames
165
- // tool-call protocol. The provider asserts the safe values on every send; the
166
- // request body is verified per chat (see glm-net-capture.ts).
165
+ // tool-call protocol. The request body is rewritten before the send and then
166
+ // verified (glm-browser's _installRuntimeRewrite/_verifyModes + glm-flags).
167
167
  export const SAFE_PREVIEW_MODE = false;
168
168
  export const SAFE_ENABLE_THINKING = true;
169
169
  // ---------- attachments ----------
package/dist/goal.js CHANGED
@@ -69,13 +69,10 @@ export function buildContinuationDirective(reason) {
69
69
  // Collapse whitespace in the model-written reason: a NEWLINE started a new
70
70
  // line of the directive, letting the evaluator's text inject an extra
71
71
  // instruction into the next turn.
72
- (argumentReason(reason)
72
+ (String(reason ?? '').trim()
73
73
  ? 'Evaluator note: ' + String(reason).trim().replace(/\s+/g, ' ')
74
74
  : ''));
75
75
  }
76
- function argumentReason(reason) {
77
- return String(reason ?? '').trim().length > 0;
78
- }
79
76
  /**
80
77
  * The text a verdict may be read from. A plain answer is returned as-is; a
81
78
  * TOOL CALL (JSON or DSML/XML) yields only its `respond` message, because the
@@ -199,7 +199,7 @@ export const commonMessages = {
199
199
  ru: '📎 Вложен файл {marker} {name} ({size}) — {path}',
200
200
  en: '📎 Attached file {marker} {name} ({size}) — {path}',
201
201
  },
202
- // B5: shown when `@path` references in a task were inlined as file contents.
202
+ // Shown when `@path` references in a task were inlined as file contents.
203
203
  'msg.at_refs': {
204
204
  ru: '📄 Встроено файлов по @-ссылкам: {n} —',
205
205
  en: '📄 Inlined @-referenced files: {n} —',
@@ -303,7 +303,7 @@ export const commonMessages = {
303
303
  },
304
304
  'cost.auto_compacts': { ru: ' авто-сжатий: {n}', en: ' auto-compacts: {n}' },
305
305
  'cost.by_tool': { ru: ' по инструментам:', en: ' by tool:' },
306
- // T-D3: total time spent inside tools, then a top-N per-tool breakdown.
306
+ // Total time spent inside tools, then a top-N per-tool breakdown.
307
307
  'cost.by_time': {
308
308
  ru: ' время в инструментах: {dur}; топ по времени:',
309
309
  en: ' time in tools: {dur}; top by time:',
package/dist/i18n.js CHANGED
@@ -2,8 +2,7 @@
2
2
  //
3
3
  // Goals:
4
4
  // * change the interface language (help, service messages);
5
- // * the agent's answer language — via the system-prompt;
6
- // * the agent's answer language for a plain language.
5
+ // * the agent's answer language — via the system-prompt.
7
6
  //
8
7
  // Strategy: a single key->translation map. New keys are easy to add.
9
8
  // Russian texts (ru) are the default, English (en) is the alternative.
package/dist/index.js CHANGED
@@ -76,7 +76,6 @@ function authMarkerExists(provider = config.provider) {
76
76
  return false;
77
77
  }
78
78
  }
79
- // ---------- CLI parsing ----------
80
79
  // The pure helpers live in src/cli-args.ts and are bound to this process's
81
80
  // argv ONCE here. Call sites keep using getArg/hasFlag/getPositional.
82
81
  const { getArg, hasFlag, getPositional } = createCliArgs(process.argv.slice(2));
@@ -182,7 +181,6 @@ if (jsonMode) {
182
181
  console.error(...a);
183
182
  });
184
183
  }
185
- // ---------- session persistence ----------
186
184
  // We store sessions (DeepSeek chats) in ~/.zames/.sessions so they survive
187
185
  // a restart and are available for explicit restore (--resume-last,
188
186
  // --chat <id>, /resume-id <id>). They are NOT brought up automatically on launch.
@@ -231,7 +229,6 @@ async function saveGoal(workdir, goal) {
231
229
  // best-effort: a failed write must not break the command
232
230
  }
233
231
  }
234
- // ---------- hot reload ----------
235
232
  // We keep references to the logic modules in the mod object. The /reload
236
233
  // command re-reads them via a dynamic import with a timestamp query — Node
237
234
  // caches ESM by URL, so such an import returns a fresh version of the module.
@@ -406,7 +403,6 @@ function warnIfBrowserChanged() {
406
403
  }
407
404
  catch { }
408
405
  }
409
- // ---------- helpers ----------
410
406
  function printHelp() {
411
407
  // Self-development commands (backlog / self-review) are shown ONLY in dev
412
408
  // mode. A regular package install must not advertise them to the user — and
@@ -689,7 +685,6 @@ async function cleanTmpDir() {
689
685
  console.error('tmp: cleanup failed:', e.message);
690
686
  }
691
687
  }
692
- // ---------- workdir resolution ----------
693
688
  // The agent works in the directory it was launched from (process.cwd()).
694
689
  // This is the sandbox root: tools cannot go above it.
695
690
  async function resolveWorkdir() {
@@ -701,7 +696,6 @@ async function resolveWorkdir() {
701
696
  }
702
697
  return dir;
703
698
  }
704
- // ---------- restored dialogue ----------
705
699
  /**
706
700
  * The answer timeout for a one-off provider call (compact summary, etc.).
707
701
  * Qwen's cold guest turns are far slower than DeepSeek's, so using
@@ -1403,7 +1397,6 @@ async function main() {
1403
1397
  // Timestamp of the last Ctrl+C while the agent was busy. Two presses
1404
1398
  // within 2s escalate from "abort the running tool" to "stop the whole run".
1405
1399
  let lastCtrlCAt = 0;
1406
- // ---------- review mode state ----------
1407
1400
  // null — normal mode.
1408
1401
  // { snapDir, snapName, originalWorkdir } — we're inside a snapshot, the chat
1409
1402
  // is already initialized with the review prompt, the user can just type "fix...".
@@ -1458,7 +1451,6 @@ async function main() {
1458
1451
  console.error(theme.error(t('msg.open_chat_error', { v: e.message })));
1459
1452
  }
1460
1453
  }
1461
- // ---------- input: permanent line below + status above ----------
1462
1454
  // In TTY we use LineEditor: it owns the input all the time, shows the
1463
1455
  // status above the input line and prints the agent's answers ABOVE it, so
1464
1456
  // the typed text is never overwritten by output. In non-TTY (pipe) —
@@ -1878,7 +1870,6 @@ async function main() {
1878
1870
  edAny._origLog = origLog;
1879
1871
  edAny._origErr = origErr;
1880
1872
  }
1881
- // ---------- /config ----------
1882
1873
  // View and edit settings without a restart. Values are validated against
1883
1874
  // CONFIG_SCHEMA (src/config.ts) and written to the project .zamesrc.json.
1884
1875
  // The language (ui.locale) is applied immediately: we change currentLocale,
@@ -2407,7 +2398,6 @@ async function main() {
2407
2398
  }
2408
2399
  continue;
2409
2400
  }
2410
- // ---------- Self-review ----------
2411
2401
  if (lower === '/self-review' || lower.startsWith('/self-review ')) {
2412
2402
  const focus = trimmed.slice('/self-review'.length).trim();
2413
2403
  // Remember where to return
@@ -2571,7 +2561,6 @@ async function main() {
2571
2561
  }
2572
2562
  continue;
2573
2563
  }
2574
- // ---------- Regular commands ----------
2575
2564
  if (lower === '/chats' || lower.startsWith('/chats ')) {
2576
2565
  const filter = trimmed.slice('/chats'.length).trim().toLowerCase();
2577
2566
  const spin = editor || mod.createSpinner(currentLocale);
@@ -107,13 +107,10 @@ export function completeSlashCommand(buf, sugg, selected) {
107
107
  prefix = prefix.slice(0, -1);
108
108
  }
109
109
  }
110
- // Stop shrinking at the TYPED text: with an empty buffer the list matches
111
- // everything, and the shared prefix used to shrink down to a bare "/" (or a
112
- // common first letter like "/h") — Tab then inserted something the operator
113
- // never typed. A completion must EXTEND what is already there.
114
- // With an EMPTY buffer everything matches and the shared prefix collapses
115
- // to a bare "/" (or a common first letter like "/h"): Tab must not guess a
116
- // command the operator has not started typing.
110
+ // A completion must EXTEND what is already typed: with an empty buffer the
111
+ // list matches everything and the shared prefix used to collapse to a bare
112
+ // "/" (or a common first letter like "/h"), so Tab inserted something the
113
+ // operator never typed.
117
114
  if (typed.length < 2)
118
115
  return null;
119
116
  if (!prefix.toLowerCase().startsWith(typed))