zames_pro 2.82.136 → 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 (61) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/dist/agent-loop.js +11 -14
  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 +15 -21
  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 +6 -10
  18. package/dist/compact.js +1 -1
  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 +6 -9
  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 +8 -19
  38. package/dist/input/suggest.js +4 -7
  39. package/dist/input.js +6 -11
  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 +11 -11
  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 +6 -10
  57. package/dist/subagent.js +1 -1
  58. package/dist/system-prompt.js +1 -1
  59. package/dist/tools.js +1 -1
  60. package/dist/tui/widgets.js +0 -1
  61. package/package.json +1 -1
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.
@@ -257,7 +254,7 @@ const RELOADABLE = [
257
254
  'config',
258
255
  'fsutil',
259
256
  'backlog',
260
- // C3 modules index.ts now calls through the mod bag (G2): without this a
257
+ // Modules index.ts now calls through the mod bag: without this a
261
258
  // /reload silently kept the OLD commands/run-task/attach-refs until a restart.
262
259
  // 'input/layout' is NOT listed: its only live consumers are input.ts (the
263
260
  // long-lived LineEditor, itself not reloadable) and spinner.ts, which already
@@ -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
@@ -594,7 +590,7 @@ async function refreshDynamicCommands(workdir) {
594
590
  }
595
591
  const cmds = await loadCommands(workdir);
596
592
  for (const c of cmds) {
597
- // B7: carry the declared argument hint as a separate field so the hint is
593
+ // Carry the declared argument hint as a separate field so the hint is
598
594
  // SHOWN in the suggest list but never inserted into the input line.
599
595
  out.push({
600
596
  name: '/' + c.name,
@@ -619,7 +615,7 @@ async function expandSlashTarget(workdir, name, rest) {
619
615
  const commands = await loadCommands(workdir);
620
616
  const cmd = commands.find((c) => c.name.toLowerCase() === name.toLowerCase());
621
617
  if (cmd) {
622
- // B7: refuse to run when declared positional arguments are missing.
618
+ // Refuse to run when declared positional arguments are missing.
623
619
  const missing = mod.commands.missingCommandArgs(cmd.arguments, rest);
624
620
  if (missing.length)
625
621
  return { missing };
@@ -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,14 +1397,13 @@ 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...".
1410
1403
  let reviewMode = null;
1411
1404
  process.on('SIGINT', async () => {
1412
1405
  running = false;
1413
- // Kill any background processes (N33) so no dev-server outlives the agent.
1406
+ // Kill any background processes so no dev-server outlives the agent.
1414
1407
  killAllBackground();
1415
1408
  await browser.close().catch(() => { });
1416
1409
  // Close the lazy headless browser from web.js, otherwise it would stay
@@ -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) —
@@ -1468,7 +1460,7 @@ async function main() {
1468
1460
  // swap. Assigned where the editor is built (see the login wiring below);
1469
1461
  // a no-op until then so /provider before setup is harmless.
1470
1462
  let wireLoginCallbacks = () => { };
1471
- // N37 statusLine hook timer (cleared on exit).
1463
+ // statusLine hook timer (cleared on exit).
1472
1464
  let statusLineTimer = null;
1473
1465
  let waiter = null;
1474
1466
  const takeInput = () => {
@@ -1619,7 +1611,7 @@ async function main() {
1619
1611
  };
1620
1612
  };
1621
1613
  wireLoginCallbacks(browser);
1622
- // N37 statusLine hook: run the configured command on its own timer (never
1614
+ // statusLine hook: run the configured command on its own timer (never
1623
1615
  // per-render — a spawn would block typing) and show its first stdout line
1624
1616
  // in the status line. Off when ui.statusLineCommand is empty.
1625
1617
  if (config.ui.statusLineCommand) {
@@ -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);
@@ -4065,7 +4054,7 @@ async function main() {
4065
4054
  }
4066
4055
  }
4067
4056
  }
4068
- // B7: a custom command with mandatory `arguments:` refuses to run until the
4057
+ // A custom command with mandatory `arguments:` refuses to run until the
4069
4058
  // operator supplies them (by position).
4070
4059
  if (missingArgs) {
4071
4060
  console.error(theme.error(t('msg.missing_args', { v: missingArgs.join(', ') })));
@@ -4081,7 +4070,7 @@ async function main() {
4081
4070
  : expandedTask !== null
4082
4071
  ? expandedTask
4083
4072
  : trimmed;
4084
- // B5: inline any `@path` references into the task so the agent does not
4073
+ // Inline any `@path` references into the task so the agent does not
4085
4074
  // spend a round-trip reading a file the operator already pointed at. Only
4086
4075
  // existing files are inlined; a bare `@name` in prose is left untouched.
4087
4076
  let finalTaskText = taskText;
@@ -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))
package/dist/input.js CHANGED
@@ -96,7 +96,7 @@ export class LineEditor {
96
96
  // is stored so it can be removed in dispose().
97
97
  _onResize;
98
98
  _resizeTimer;
99
- // ESC disambiguation (N29): a read may end on a bare ESC that is really the
99
+ // ESC disambiguation: a read may end on a bare ESC that is really the
100
100
  // head of a control sequence split across two reads. We hold it briefly and
101
101
  // fire onEscape only if no tail arrives.
102
102
  _escPending;
@@ -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();
@@ -644,7 +644,6 @@ export class LineEditor {
644
644
  this.pastes = [];
645
645
  this._render();
646
646
  }
647
- // ---------- rendering ----------
648
647
  _eraseBlock() {
649
648
  if (!this.rendered)
650
649
  return;
@@ -936,7 +935,6 @@ export class LineEditor {
936
935
  process.stdout.write(String(text) + NL);
937
936
  this._writeBlock();
938
937
  }
939
- // ---------- spinner-compatible interface ----------
940
938
  setStatus(text) {
941
939
  this.statusText = text || '';
942
940
  this._render();
@@ -1124,7 +1122,6 @@ export class LineEditor {
1124
1122
  const wrapped = wrapToWidth('⚠ ' + msg, maxW);
1125
1123
  this.printAbove(NL + theme.warn(wrapped));
1126
1124
  }
1127
- // ---------- input ----------
1128
1125
  _submit() {
1129
1126
  if (this.locked) {
1130
1127
  this._render();
@@ -1367,7 +1364,7 @@ export class LineEditor {
1367
1364
  }
1368
1365
  // Bare ESC: wait a moment for a possible control-sequence tail before firing
1369
1366
  // onEscape. Without this a sequence split across two reads (ESC, then "[A")
1370
- // looked like a lone Escape and aborted the current generation (N29).
1367
+ // looked like a lone Escape and aborted the current generation.
1371
1368
  _scheduleEscape() {
1372
1369
  this._escPending = true;
1373
1370
  if (this._escTimer)
@@ -1460,7 +1457,6 @@ export class LineEditor {
1460
1457
  chars.splice(this.cursor, i - this.cursor);
1461
1458
  this.buf = chars.join('');
1462
1459
  }
1463
- // ---------- history ----------
1464
1460
  _historyUp() {
1465
1461
  if (!this.history.length)
1466
1462
  return;
@@ -1485,7 +1481,6 @@ export class LineEditor {
1485
1481
  }
1486
1482
  this.cursor = Array.from(this.buf).length;
1487
1483
  }
1488
- // ---------- reverse-search (Ctrl+R) ----------
1489
1484
  // Start an incremental reverse search, keeping the current buffer aside so
1490
1485
  // Esc restores it (bash behavior). An empty history leaves the editor
1491
1486
  // untouched.
@@ -1596,7 +1591,7 @@ export class LineEditor {
1596
1591
  // A previous read ended on a bare ESC. That is ambiguous: the Escape key
1597
1592
  // or the head of a control sequence split across reads (arrows, Ctrl+Del).
1598
1593
  // Reattach the ESC and parse the whole sequence, so a split arrow does not
1599
- // abort the generation (N29).
1594
+ // abort the generation.
1600
1595
  if (this._escPending) {
1601
1596
  this._escPending = false;
1602
1597
  if (this._escTimer) {
package/dist/markdown.js CHANGED
@@ -15,7 +15,6 @@ const theme = {
15
15
  tableHeader: { color: '#9fb8d8', bold: true },
16
16
  tableCell: { color: '#d0d0d0' },
17
17
  };
18
- // Code highlighting via cli-highlight (already present transitively).
19
18
  function highlighter(code, lang) {
20
19
  try {
21
20
  return highlight(code, {
package/dist/mcp.js CHANGED
@@ -477,11 +477,8 @@ export async function createMcpPool(opts) {
477
477
  }
478
478
  }
479
479
  // Distinct servers can produce the SAME qualified name (server 'a' + tool
480
- // 'b__c' vs server 'a__b' + tool 'c'), and agent-loop resolves a call with
481
- // tools.find(name) — it takes the FIRST. A duplicate would make the second
482
- // tool silently unreachable (the model calls "the right name", a DIFFERENT
483
- // tool runs). Rename the later one with a numeric suffix so every name is
484
- // unique.
480
+ // 'b__c' vs server 'a__b' + tool 'c'); dedupeToolNames below explains why
481
+ // that has to be fixed rather than left to tools.find().
485
482
  dedupeToolNames(tools);
486
483
  let closed = false;
487
484
  return {
@@ -259,7 +259,7 @@ export function isFinishedWithAnswer(body) {
259
259
  return false;
260
260
  // A truncated RESPONSE head (a lone `{`) is NOT a complete answer: the
261
261
  // finish loop would return it as the model's text and the tool-call parser
262
- // would fail on it. Require real content (see MIN_FINISHED_ANSWER_CHARS).
262
+ // would fail on it. Require real content (see responseTextLength).
263
263
  return responseContentLength(b) >= MIN_REAL_ANSWER_CHARS;
264
264
  }
265
265
  // The CONTEXT size (in tokens) DeepSeek reports for the current answer.
@@ -118,15 +118,11 @@ export function extractAtFileRefs(text) {
118
118
  }
119
119
  let j = i + 1;
120
120
  let tok = '';
121
- // The token is rejected WHOLE when it contains an at-sign that is NOT a
122
- // list separator — `@Component` / `user@host` are a decorator and an
123
- // email. Trimming the prefix at that at-sign would otherwise inline a
124
- // bogus path.
125
- let hadInnerAt = false;
126
121
  // A token that contains an at-sign (`@Component`, `user@host`) is a
127
- // decorator / an email, NOT a file reference. The scan used to stop at
128
- // that at-sign and hand the PREFIX (`Component` … no) or, worse, the part
129
- // BEFORE it to the inliner. Now the whole token is rejected instead.
122
+ // decorator or an email, NOT a file reference, and the WHOLE token is
123
+ // rejected: the scan used to stop at that at-sign and hand the part
124
+ // BEFORE it to the inliner, which inlined a bogus path.
125
+ let hadInnerAt = false;
130
126
  const isRef = (t) => t.length > 0 && !t.includes('@');
131
127
  while (j < n && !isRefEnd(chars[j])) {
132
128
  // A COMMA directly followed by another `@` starts the NEXT reference:
@@ -154,12 +150,9 @@ export function extractAtFileRefs(text) {
154
150
  seen.add(cleaned);
155
151
  out.push(cleaned);
156
152
  }
157
- // The break above leaves `j` ON the comma; step past it so the next `@`
158
- // is seen as the start of a reference. When the scan ended on a
159
- // terminator instead, continue AFTER it.
160
- // The scan stopped ON a separator that split the token (`,`, `;` or a
161
- // second at-sign): resume AT it so the next part is scanned as its own
162
- // reference. Continuing past it dropped the second file of a list.
153
+ // The scan may have stopped ON a separator that split the token (`,`, `;`
154
+ // or a second at-sign): resume AT it so the next part is scanned as its
155
+ // own reference. Continuing past it dropped the second file of a list.
163
156
  const sep = j < n ? chars[j] : '';
164
157
  if (j > i + 1 && (sep === ',' || sep === ';' || sep === '@'))
165
158
  i = j;
package/dist/prompt.js CHANGED
@@ -31,7 +31,8 @@ export async function promptOnce(question) {
31
31
  const wasRaw = stdin.isRaw;
32
32
  stdin.setRawMode(true);
33
33
  stdin.resume();
34
- // We enable/disable bracketed paste in pairs.
34
+ // Bracketed paste is enabled/disabled in pairs, so the terminal is left in
35
+ // normal mode however the prompt exits.
35
36
  stdout.write('\x1b[?2004h');
36
37
  stdout.write(question);
37
38
  let line = '';
@@ -40,11 +41,11 @@ export async function promptOnce(question) {
40
41
  const PASTE_START = '\x1b[200~';
41
42
  const PASTE_END = '\x1b[201~';
42
43
  const redraw = () => {
43
- // Return to the start of the line, erase and print again.
44
+ // Redraw in place: CR + erase-to-end, then the prompt and the line again.
44
45
  stdout.write(String.fromCharCode(13));
45
46
  stdout.write('\x1b[K');
46
47
  stdout.write(question + line);
47
- // Put the cursor in the right position.
48
+ // Step back to the cursor, measured in UTF-16 units.
48
49
  const back = line.length - cursor;
49
50
  if (back > 0)
50
51
  stdout.write('\x1b[' + back + 'D');
@@ -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
@@ -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,
@@ -330,9 +330,9 @@ 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
- // 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;
@@ -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
@@ -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'
@@ -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)
@@ -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,
@@ -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 = [];