zames_pro 2.82.135 → 2.82.137

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,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.82.137] - 2026-10-24
11
+
12
+ ### Changed
13
+
14
+ - docs: drop backlog-id references from source comments
15
+
16
+ ## [2.82.136] - 2026-10-24
17
+
18
+ ### Fixed
19
+
20
+ - **spinner:** a live language switch reaches the non-TTY spinner
21
+
10
22
  ## [2.82.135] - 2026-10-24
11
23
 
12
24
  ### Changed
@@ -14,7 +14,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
14
14
  // `undefined` means "read .zames/hooks.json"; an explicit null disables
15
15
  // hooks entirely.
16
16
  const hookConfig = hooks === undefined ? loadHooks(workdir) : (hooks ?? {});
17
- // Same for the approval policy (C1): read `.zames/permissions.json` once.
17
+ // Same for the approval policy: read `.zames/permissions.json` once.
18
18
  const permissionPolicy = permissions === undefined ? loadPermissions(workdir) : permissions;
19
19
  // UI callbacks must NEVER break the agent loop. A rendering error (a huge
20
20
  // tool result, a broken markdown frame, a closed terminal) used to throw
@@ -81,7 +81,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
81
81
  if (freshChat) {
82
82
  await browser.newChat();
83
83
  transcript?.log('new_chat');
84
- // SessionStart hook (N32): a fresh chat is the start of a session, so a
84
+ // SessionStart hook: a fresh chat is the start of a session, so a
85
85
  // hook can inject context or set up state. Best-effort, output is logged.
86
86
  try {
87
87
  const out = await runLifecycleHooks(hookConfig, 'SessionStart', workdir);
@@ -118,7 +118,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
118
118
  let context;
119
119
  try {
120
120
  // Pass the task text so nested AGENTS.md/MEMORY.md for the directories
121
- // this task actually touches are pulled in (B6, path-scoped rules).
121
+ // this task actually touches are pulled in (path-scoped rules).
122
122
  context = await loadProjectContext(workdir, [task]);
123
123
  }
124
124
  catch {
@@ -280,7 +280,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
280
280
  let lastAutoCompactTokens = -1;
281
281
  // Set when an auto-compact returned the SAME chat id it started in — i.e.
282
282
  // the "fresh chat" never opened and the compaction did nothing. Without this
283
- // guard a broken newChat() (see B2) made the loop compact on EVERY tool call
283
+ // guard a broken newChat() made the loop compact on EVERY tool call
284
284
  // while the context kept growing. Defense in depth: the root cause is fixed,
285
285
  // but a future regression must not turn into an endless "compacting" loop.
286
286
  let autoCompactDisabled = false;
@@ -314,7 +314,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
314
314
  // 300s rate-limit wait or the finish loop) while the next iteration
315
315
  // started a SECOND ask() against the same page — two sends / two Continue
316
316
  // clicks.
317
- // UserPromptSubmit hook (N32): runs right before the prompt is sent. Its
317
+ // UserPromptSubmit hook: runs right before the prompt is sent. Its
318
318
  // stdout is appended to the outgoing message as extra context, mirroring
319
319
  // Claude Code. Best-effort — a hook failure never blocks the send.
320
320
  try {
@@ -787,7 +787,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
787
787
  'repeat the identical call.';
788
788
  continue;
789
789
  }
790
- // N34: several READ-ONLY calls in one batch are independent, so run them
790
+ // Several READ-ONLY calls in one batch are independent, so run them
791
791
  // concurrently instead of one-by-one (the model often emits 4-6 Reads in a
792
792
  // single answer and each awaited round-trip added latency). The fast path
793
793
  // is deliberately NARROW: it is taken only when EVERY call is a known
@@ -1016,7 +1016,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
1016
1016
  results.push({ tool: call.tool, result: blocked });
1017
1017
  continue;
1018
1018
  }
1019
- // Approval policy (C1): a `deny` rule blocks the call (like a PreToolUse
1019
+ // Approval policy: a `deny` rule blocks the call (like a PreToolUse
1020
1020
  // denial); an `ask` rule prompts the operator. Hooks stay authoritative
1021
1021
  // for programmatic guards — this is the human-in-the-loop layer.
1022
1022
  const decision = decidePermission(permissionPolicy, call.tool, call.args);
@@ -1065,7 +1065,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
1065
1065
  const poll = setInterval(syncToolAbort, 100);
1066
1066
  if (typeof poll.unref === 'function')
1067
1067
  poll.unref();
1068
- // Time the tool (T-D3): the transcript carries durationMs so /cost can
1068
+ // Time the tool: the transcript carries durationMs so /cost can
1069
1069
  // show where the time goes (frequent Read→Edit cycles vs slow Bash).
1070
1070
  const toolStart = Date.now();
1071
1071
  try {
@@ -1089,7 +1089,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
1089
1089
  [PostToolUse hook]
1090
1090
  ${post}`;
1091
1091
  }
1092
- // N38: after a file-mutating tool, run the project's own check and
1092
+ // After a file-mutating tool, run the project's own check and
1093
1093
  // append its summary, so a type error reaches the model immediately
1094
1094
  // instead of costing a whole round-trip. Best-effort and capped.
1095
1095
  if (shouldRunDiagnostics(diagnostics, call.tool)) {
package/dist/changelog.js CHANGED
@@ -80,7 +80,7 @@ export function renderChangelogDraft(groups) {
80
80
  }
81
81
  /**
82
82
  * Replace the body between `## [Unreleased]` and the next `## [` heading with
83
- * a freshly drafted body (D2). PURE so a test can pin the exact behavior.
83
+ * a freshly drafted body. PURE so a test can pin the exact behavior.
84
84
  *
85
85
  * When `body` is empty the existing Unreleased section is CLEARED to just the
86
86
  * heading (no stray blank content). A changelog without an Unreleased heading
package/dist/commands.js CHANGED
@@ -238,7 +238,7 @@ export function renderCost(stats, transcriptFile, tokenUsage = null, t = transla
238
238
  for (const [name, n] of top)
239
239
  lines.push(' ' + nameLine(name) + ': ' + n);
240
240
  }
241
- // T-D3: where the time went. Sort by total ms, show the top tools with the
241
+ // Where the time went. Sort by total ms, show the top tools with the
242
242
  // count and a human duration, so Read→Edit cycles and slow Bash calls are
243
243
  // visible at a glance.
244
244
  const byTime = Object.entries(stats.toolTime || {})
package/dist/compact.js CHANGED
@@ -43,7 +43,7 @@ export async function performCompact(opts) {
43
43
  try {
44
44
  if (!quiet)
45
45
  log(t('compact.start'));
46
- // PreCompact hook (N32): runs BEFORE the chat is summarized/rewritten, so
46
+ // PreCompact hook: runs BEFORE the chat is summarized/rewritten, so
47
47
  // a hook can persist state or notify. Best-effort.
48
48
  try {
49
49
  const out = await runLifecycleHooks(loadHooks(workdir), 'PreCompact', workdir, { chatId: currentChatId });
package/dist/config.js CHANGED
@@ -135,7 +135,7 @@ export const DEFAULTS = {
135
135
  contextLimit: 1_000_000,
136
136
  // 0 = auto: terminal width, capped at 100 columns (the historical width).
137
137
  answerWidth: 0,
138
- // N37 statusLine hook. Off by default; edit config.json to set a command.
138
+ // statusLine hook. Off by default; edit config.json to set a command.
139
139
  statusLineCommand: '',
140
140
  statusLineIntervalMs: 10_000,
141
141
  // Use the Ink/React TUI for the input area (the rich editor). Set to false
package/dist/index.js CHANGED
@@ -257,7 +257,7 @@ const RELOADABLE = [
257
257
  'config',
258
258
  'fsutil',
259
259
  'backlog',
260
- // C3 modules index.ts now calls through the mod bag (G2): without this a
260
+ // Modules index.ts now calls through the mod bag: without this a
261
261
  // /reload silently kept the OLD commands/run-task/attach-refs until a restart.
262
262
  // 'input/layout' is NOT listed: its only live consumers are input.ts (the
263
263
  // long-lived LineEditor, itself not reloadable) and spinner.ts, which already
@@ -594,7 +594,7 @@ async function refreshDynamicCommands(workdir) {
594
594
  }
595
595
  const cmds = await loadCommands(workdir);
596
596
  for (const c of cmds) {
597
- // B7: carry the declared argument hint as a separate field so the hint is
597
+ // Carry the declared argument hint as a separate field so the hint is
598
598
  // SHOWN in the suggest list but never inserted into the input line.
599
599
  out.push({
600
600
  name: '/' + c.name,
@@ -619,7 +619,7 @@ async function expandSlashTarget(workdir, name, rest) {
619
619
  const commands = await loadCommands(workdir);
620
620
  const cmd = commands.find((c) => c.name.toLowerCase() === name.toLowerCase());
621
621
  if (cmd) {
622
- // B7: refuse to run when declared positional arguments are missing.
622
+ // Refuse to run when declared positional arguments are missing.
623
623
  const missing = mod.commands.missingCommandArgs(cmd.arguments, rest);
624
624
  if (missing.length)
625
625
  return { missing };
@@ -1410,7 +1410,7 @@ async function main() {
1410
1410
  let reviewMode = null;
1411
1411
  process.on('SIGINT', async () => {
1412
1412
  running = false;
1413
- // Kill any background processes (N33) so no dev-server outlives the agent.
1413
+ // Kill any background processes so no dev-server outlives the agent.
1414
1414
  killAllBackground();
1415
1415
  await browser.close().catch(() => { });
1416
1416
  // Close the lazy headless browser from web.js, otherwise it would stay
@@ -1468,7 +1468,7 @@ async function main() {
1468
1468
  // swap. Assigned where the editor is built (see the login wiring below);
1469
1469
  // a no-op until then so /provider before setup is harmless.
1470
1470
  let wireLoginCallbacks = () => { };
1471
- // N37 statusLine hook timer (cleared on exit).
1471
+ // statusLine hook timer (cleared on exit).
1472
1472
  let statusLineTimer = null;
1473
1473
  let waiter = null;
1474
1474
  const takeInput = () => {
@@ -1619,7 +1619,7 @@ async function main() {
1619
1619
  };
1620
1620
  };
1621
1621
  wireLoginCallbacks(browser);
1622
- // N37 statusLine hook: run the configured command on its own timer (never
1622
+ // statusLine hook: run the configured command on its own timer (never
1623
1623
  // per-render — a spawn would block typing) and show its first stdout line
1624
1624
  // in the status line. Off when ui.statusLineCommand is empty.
1625
1625
  if (config.ui.statusLineCommand) {
@@ -1903,6 +1903,9 @@ async function main() {
1903
1903
  // DeepSeek-side message printed later would still come out in the OLD
1904
1904
  // language until a restart.
1905
1905
  browser.setLocale(value);
1906
+ // A NON-TTY run has no editor: its only UI is the spinner, which must
1907
+ // follow the switch too (it is created per command from the locale).
1908
+ mod.createSpinner(value).setLocale?.(value);
1906
1909
  if (editor) {
1907
1910
  editor.setLocale(value);
1908
1911
  editor.setCommands(buildSlashCommands());
@@ -4062,7 +4065,7 @@ async function main() {
4062
4065
  }
4063
4066
  }
4064
4067
  }
4065
- // B7: a custom command with mandatory `arguments:` refuses to run until the
4068
+ // A custom command with mandatory `arguments:` refuses to run until the
4066
4069
  // operator supplies them (by position).
4067
4070
  if (missingArgs) {
4068
4071
  console.error(theme.error(t('msg.missing_args', { v: missingArgs.join(', ') })));
@@ -4078,7 +4081,7 @@ async function main() {
4078
4081
  : expandedTask !== null
4079
4082
  ? expandedTask
4080
4083
  : trimmed;
4081
- // B5: inline any `@path` references into the task so the agent does not
4084
+ // Inline any `@path` references into the task so the agent does not
4082
4085
  // spend a round-trip reading a file the operator already pointed at. Only
4083
4086
  // existing files are inlined; a bare `@name` in prose is left untouched.
4084
4087
  let finalTaskText = taskText;
package/dist/input.js CHANGED
@@ -173,7 +173,7 @@ export class LineEditor {
173
173
  // A callback the editor calls before every render to read the agent's
174
174
  // current task-list summary ("tasks: 2/5"), empty when there is no list.
175
175
  onTasksQuery;
176
- // Extra status text produced by an external command (N37 statusLine hook).
176
+ // Extra status text produced by an external command (the statusLine hook).
177
177
  // Set via setStatusLine(); shown before the queue/tasks badges. Kept as a
178
178
  // plain field because the CALLER runs the command on its own timer (a
179
179
  // per-render spawn would block typing).
@@ -361,7 +361,7 @@ export class LineEditor {
361
361
  return '';
362
362
  }
363
363
  }
364
- // The statusLine hook text (N37), shown before the queue/tasks badges.
364
+ // The statusLine hook text, shown before the queue/tasks badges.
365
365
  // A trailing space separates it from whatever follows; empty hides it.
366
366
  _statusLineBadge() {
367
367
  return this.statusLine ? theme.system(this.statusLine) + ' ' : '';
@@ -605,7 +605,7 @@ export class LineEditor {
605
605
  }
606
606
  // Update the context window size (from config.ui.contextLimit). The next
607
607
  // render recomputes the percentage and its color with the new limit.
608
- // Set the statusLine hook text (N37). The caller runs the command on its
608
+ // Set the statusLine hook text. The caller runs the command on its
609
609
  // own timer; this only stores + repaints. An empty string hides the badge.
610
610
  setStatusLine(text) {
611
611
  const t = String(text || '').trim();
package/dist/run-task.js CHANGED
@@ -5,7 +5,7 @@ import { formatCompactTokens, pasteReplacement, expandPastes } from './input.js'
5
5
  import { DEFAULT_LOCALE } from './i18n.js';
6
6
  import { providerAskDeadlineMs } from './config.js';
7
7
  // Ask the operator to approve a tool call flagged by an `ask` permission rule
8
- // (C1). While the LineEditor owns the terminal it must be paused first, or the
8
+ // While the LineEditor owns the terminal it must be paused first, or the
9
9
  // confirmation prompt and the input line fight for the same keys. A non-TTY run
10
10
  // has no way to answer, so it DENIES (never silently allows a guarded call).
11
11
  export async function askOperatorConfirm(editor, question, t,
@@ -332,7 +332,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
332
332
  // Set when the loop ends without a real answer (iteration limit / watchdog)
333
333
  // so the one-shot mode can exit non-zero. See N5.
334
334
  let failure = null;
335
- // N31: the machine-readable reason for the last task's outcome.
335
+ // The machine-readable reason for the last task's outcome.
336
336
  let outcomeKind = 'ok';
337
337
  // How many auto-continue turns a completion goal has already spent.
338
338
  let goalTurns = 0;
@@ -389,7 +389,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
389
389
  },
390
390
  onWarning: (msg) => ui.warning(msg),
391
391
  onAskPermission: async (info) => {
392
- // C1: a rule with action "ask". Show the reason and wait for the
392
+ // A rule with action "ask". Show the reason and wait for the
393
393
  // operator. In non-TTY this denies.
394
394
  ui.warning(t('perm.ask', { tool: info.tool, reason: info.reason }));
395
395
  const ok = await askOperatorConfirm(editor || null, t('perm.confirm'), t, locale);
@@ -440,7 +440,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
440
440
  }
441
441
  }
442
442
  : onAskUser,
443
- // N38: the check command appended to a tool result after an edit
443
+ // The check command appended to a tool result after an edit
444
444
  // (off by default; config.diagnostics).
445
445
  diagnostics: deps.config.diagnostics,
446
446
  });
@@ -452,7 +452,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
452
452
  ui.warning(outcome);
453
453
  transcript?.log('agent_no_answer', { outcome });
454
454
  failure = outcome;
455
- // N31: a machine-readable reason so `--output-format jsonl` consumers
455
+ // A machine-readable reason so `--output-format jsonl` consumers
456
456
  // (CI) can tell a real error from an exhausted limit / watchdog.
457
457
  outcomeKind = outcome.startsWith('Iteration limit reached')
458
458
  ? 'iteration_limit'
@@ -570,7 +570,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
570
570
  sendSystemPrompt: false,
571
571
  };
572
572
  }
573
- // Stop hook (N32): the whole task (including drained queue messages) is
573
+ // Stop hook: the whole task (including drained queue messages) is
574
574
  // done. A cleanup/notification hook runs here. Best-effort.
575
575
  try {
576
576
  // LOAD the config: `undefined` here made every Stop hook a no-op — the
@@ -584,7 +584,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
584
584
  ui.warning(out);
585
585
  }
586
586
  catch { }
587
- // N31: a single terminal event carrying the reason, so a jsonl consumer
587
+ // A single terminal event carrying the reason, so a jsonl consumer
588
588
  // does not have to guess from the exit code alone.
589
589
  transcript?.log('task_result', {
590
590
  ok: !failure,
package/dist/spinner.js CHANGED
@@ -63,7 +63,11 @@ export function createSpinner(locale = 'ru') {
63
63
  };
64
64
  // Hint in the status line: while the agent works the terminal is live,
65
65
  // so you can type the next message. Without it this is not obvious.
66
- const HINT = theme.dim(' · ' + translate(locale)('spinner.hint'));
66
+ // RE-DERIVED on a locale switch: `/config lang` changes the language live,
67
+ // and a constant built once at construction kept the OLD language (and even
68
+ // the old locale's separator) until a restart.
69
+ let localeNow = locale;
70
+ let HINT = theme.dim(' · ' + translate(localeNow)('spinner.hint'));
67
71
  // Run the animated status line: a brown base text plus a growing/shrinking
68
72
  // "running" dot sequence. Shared by the thinking spinner and the send-pause
69
73
  // indicator, so the pause is animated too (before, it was a static line and
@@ -128,6 +132,13 @@ export function createSpinner(locale = 'ru') {
128
132
  spinner.text = theme.prompt('✎ ') + pending + HINT;
129
133
  };
130
134
  return {
135
+ // `/config lang` switches the language live; the legacy LineEditor and the
136
+ // TUI adapter both expose setLocale, and index.ts calls it on EVERY editor.
137
+ // Without it the non-TTY spinner kept its old-language hint until restart.
138
+ setLocale: (l) => {
139
+ localeNow = l;
140
+ HINT = theme.dim(' · ' + translate(localeNow)('spinner.hint'));
141
+ },
131
142
  get onTasksQuery() {
132
143
  return onTasksQuery;
133
144
  },
@@ -148,7 +159,7 @@ export function createSpinner(locale = 'ru') {
148
159
  sendPause: (seconds) => {
149
160
  if (pending)
150
161
  return;
151
- const label = translate(locale)('spinner.pause', { n: seconds });
162
+ const label = translate(localeNow)('spinner.pause', { n: seconds });
152
163
  // Update only the base text while the dot animation is already running,
153
164
  // so the countdown refreshes without resetting the dots.
154
165
  if (animating && dotTimer && spinner) {
@@ -179,7 +190,7 @@ export function createSpinner(locale = 'ru') {
179
190
  // long time): the operator must see that work is in progress.
180
191
  if (pending)
181
192
  return;
182
- startAnimated(translate(locale)('spinner.running_tool', { name }));
193
+ startAnimated(translate(localeNow)('spinner.running_tool', { name }));
183
194
  },
184
195
  toolResult: (result) => {
185
196
  stop();
@@ -194,7 +205,7 @@ export function createSpinner(locale = 'ru') {
194
205
  const rendered = renderMarkdown(msg);
195
206
  // Model answer marker: helps visually separate it from the user's input
196
207
  // (which is highlighted by the prompt with a golden arrow).
197
- console.log(NL + theme.assistant(translate(locale)('editor.answer')) + NL);
208
+ console.log(NL + theme.assistant(translate(localeNow)('editor.answer')) + NL);
198
209
  console.log(rendered);
199
210
  console.log(theme.dim(divider()) + NL);
200
211
  },
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- // N37 statusLine hook. When `ui.statusLineCommand` is set, zames runs it on a
2
+ // statusLine hook. When `ui.statusLineCommand` is set, zames runs it on a
3
3
  // timer with a JSON status object on stdin and shows the FIRST line of its
4
4
  // stdout in the status line (like Claude Code). The command is deliberately
5
5
  // run OUT of the render path: a per-render spawn would block typing, so the
package/dist/subagent.js CHANGED
@@ -178,7 +178,7 @@ export function createSubagentRunner(opts) {
178
178
  browser.onSendState = savedHooks.onSendState;
179
179
  browser.onNotice = savedHooks.onNotice;
180
180
  }
181
- // SubagentStop hook (N32): fires after the nested loop finishes, so a hook
181
+ // SubagentStop hook: fires after the nested loop finishes, so a hook
182
182
  // can clean up or notify. Best-effort; its stdout is appended to the report.
183
183
  let hookOut = '';
184
184
  try {
package/dist/tools.js CHANGED
@@ -104,7 +104,7 @@ export function createTools(workdir, { undo, todos, readOnly = false, subagents
104
104
  },
105
105
  fn: async ({ path: p, offset, limit, numbered }) => {
106
106
  const file = safe(req(p, 'path'));
107
- // Binary guard (N35): reading an image/archive as utf-8 produced
107
+ // Binary guard: reading an image/archive as utf-8 produced
108
108
  // mojibake the model tried to reason about. Detect a binary buffer
109
109
  // BEFORE decoding and answer with useful metadata instead.
110
110
  // A read FAILURE (a missing file, a directory, EACCES) is answered in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.82.135",
3
+ "version": "2.82.137",
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": {