zames_pro 2.20.0 → 2.21.1

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/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { DeepSeekBrowser } from './browser.js';
8
8
  import { createTools } from './tools.js';
9
9
  import { runAgentLoop } from './agent-loop.js';
10
10
  import { createSpinner } from './spinner.js';
11
- import { LineEditor, expandPastes, pasteReplacement } from './input.js';
11
+ import { LineEditor, expandPastes, pasteReplacement, } from './input.js';
12
12
  import { parseImagePaste, extForMime, saveToTemp, guessMime, isImageName, formatSize, looksLikeFilePath, readClipboardImageDetailed, sniffMime, readWindowsClipboardFiles, hasClipboardTool, } from './attachments.js';
13
13
  import { loadConfig, DEFAULTS, CONFIG_PATHS, ZAMES_HOME, CONFIG_SCHEMA, getByPath, validateConfigValue, writeConfigValue, resetConfigValue, } from './config.js';
14
14
  import { runConfigMenu } from './config-menu.js';
@@ -370,7 +370,9 @@ async function expandSlashTarget(workdir, name, rest) {
370
370
  body = '';
371
371
  }
372
372
  const header = 'Follow the skill "' + skill.name + '" (from ' + skill.path + ').';
373
- const extra = rest ? '\n\nAdditional instructions from the operator: ' + rest : '';
373
+ const extra = rest
374
+ ? '\n\nAdditional instructions from the operator: ' + rest
375
+ : '';
374
376
  return header + '\n\n' + body + extra;
375
377
  }
376
378
  }
@@ -403,7 +405,8 @@ async function resolveAttachPath(workdir, raw) {
403
405
  let s = String(raw || '').trim();
404
406
  if (!s)
405
407
  return null;
406
- if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
408
+ if ((s.startsWith('"') && s.endsWith('"')) ||
409
+ (s.startsWith("'") && s.endsWith("'"))) {
407
410
  s = s.slice(1, -1);
408
411
  }
409
412
  s = s.replace(/\\ /g, ' ');
@@ -1016,7 +1019,10 @@ async function main() {
1016
1019
  mcpPool = await mod.createMcpPool({ workdir: currentWorkdir });
1017
1020
  const st = mcpPool.status();
1018
1021
  if (st.toolCount > 0) {
1019
- const names = st.servers.filter((x) => !x.error).map((x) => x.name).join(', ');
1022
+ const names = st.servers
1023
+ .filter((x) => !x.error)
1024
+ .map((x) => x.name)
1025
+ .join(', ');
1020
1026
  console.log(theme.system(t('mcp.loaded', { n: String(st.toolCount), servers: names })));
1021
1027
  }
1022
1028
  for (const srv of st.servers) {
@@ -1117,8 +1123,6 @@ async function main() {
1117
1123
  // Messages the user typed while the agent worked. runTask takes them one
1118
1124
  // by one after the current task finishes.
1119
1125
  const pendingQueue = [];
1120
- // Attachments for the next submit (filled by the editor's onAttachments).
1121
- let pendingAttachments = [];
1122
1126
  // Show the "no clipboard image" hint only once per session.
1123
1127
  let clipboardWarned = false;
1124
1128
  // ---------- review mode state ----------
@@ -1185,7 +1189,9 @@ async function main() {
1185
1189
  let tail;
1186
1190
  if (reviewMode) {
1187
1191
  tail =
1188
- theme.warn(t('prompt.review')) + theme.dim(':') + theme.dir(reviewMode.snapName);
1192
+ theme.warn(t('prompt.review')) +
1193
+ theme.dim(':') +
1194
+ theme.dir(reviewMode.snapName);
1189
1195
  }
1190
1196
  else {
1191
1197
  tail = theme.dir(dirLabel(currentWorkdir));
@@ -1217,7 +1223,10 @@ async function main() {
1217
1223
  mime: image.mime,
1218
1224
  size: image.data.length,
1219
1225
  });
1220
- ed.printAbove(theme.system(t('msg.attached_image', { marker: att.marker, size: formatSize(image.data.length) })));
1226
+ ed.printAbove(theme.system(t('msg.attached_image', {
1227
+ marker: att.marker,
1228
+ size: formatSize(image.data.length),
1229
+ })));
1221
1230
  return att;
1222
1231
  }
1223
1232
  // Case 2: the paste is a path to a local file (drag&drop or copy path).
@@ -1235,7 +1244,11 @@ async function main() {
1235
1244
  mime,
1236
1245
  size: data.length,
1237
1246
  });
1238
- ed.printAbove(theme.system(t('msg.attached_file', { marker: att.marker, name, size: formatSize(data.length) })));
1247
+ ed.printAbove(theme.system(t('msg.attached_file', {
1248
+ marker: att.marker,
1249
+ name,
1250
+ size: formatSize(data.length),
1251
+ })));
1239
1252
  return att;
1240
1253
  };
1241
1254
  ed.onClipboard = async () => {
@@ -1932,10 +1945,16 @@ async function main() {
1932
1945
  console.log(theme.system(t('mcp.title', { n: String(st.toolCount) })));
1933
1946
  for (const srv of st.servers) {
1934
1947
  if (srv.error) {
1935
- console.log(' ' + theme.user(srv.name) + ' ' + theme.dim(t('mcp.status_error', { v: srv.error })));
1948
+ console.log(' ' +
1949
+ theme.user(srv.name) +
1950
+ ' ' +
1951
+ theme.dim(t('mcp.status_error', { v: srv.error })));
1936
1952
  }
1937
1953
  else {
1938
- console.log(' ' + theme.user(srv.name) + ' ' + theme.dim('(' + String(srv.tools.length) + ')'));
1954
+ console.log(' ' +
1955
+ theme.user(srv.name) +
1956
+ ' ' +
1957
+ theme.dim('(' + String(srv.tools.length) + ')'));
1939
1958
  }
1940
1959
  }
1941
1960
  continue;
@@ -2132,7 +2151,7 @@ async function main() {
2132
2151
  continue;
2133
2152
  }
2134
2153
  if (lower === '/diff' || lower.startsWith('/diff ')) {
2135
- const staged = lower.indexOf("--staged") !== -1;
2154
+ const staged = lower.indexOf('--staged') !== -1;
2136
2155
  const { runGit } = await import('./gitTools.js');
2137
2156
  const probe = await runGit('git rev-parse --is-inside-work-tree', currentWorkdir, 5000);
2138
2157
  if (probe.trim() !== 'true') {
@@ -2196,14 +2215,14 @@ async function main() {
2196
2215
  }
2197
2216
  }
2198
2217
  catch {
2199
- gitOk = false;
2218
+ // git is not available here; gitOk stays false.
2200
2219
  }
2201
2220
  let clipboardTool = null;
2202
2221
  try {
2203
2222
  clipboardTool = hasClipboardTool() ? 'available' : null;
2204
2223
  }
2205
2224
  catch {
2206
- clipboardTool = null;
2225
+ // not available; clipboardTool stays null
2207
2226
  }
2208
2227
  const mcpStatus = mcpPool
2209
2228
  ? mcpPool.status()
@@ -2283,7 +2302,9 @@ async function main() {
2283
2302
  // A model "answer" that is actually an error/abort sentinel is not a
2284
2303
  // summary - do not carry it over.
2285
2304
  if (!summary || /^\(прервано пользователем\)$/.test(summary)) {
2286
- console.error(theme.error(t('compact.summary_failed', { v: summary || t('common.unknown') })));
2305
+ console.error(theme.error(t('compact.summary_failed', {
2306
+ v: summary || t('common.unknown'),
2307
+ })));
2287
2308
  continue;
2288
2309
  }
2289
2310
  transcript.log('compact_summary', {
@@ -2335,7 +2356,7 @@ async function main() {
2335
2356
  }
2336
2357
  if (lower === '/review' || lower.startsWith('/review ')) {
2337
2358
  const rest = trimmed.slice('/review'.length).trim();
2338
- const staged = rest.indexOf("--staged") !== -1;
2359
+ const staged = rest.indexOf('--staged') !== -1;
2339
2360
  const focus = rest.replace(/--staged/g, '').trim();
2340
2361
  const reviewTask = buildReviewPrompt(focus, staged);
2341
2362
  const reviewTools = mod.createTools(currentWorkdir, { undo });
package/dist/input.js CHANGED
@@ -36,7 +36,11 @@ const PASTE_END = ESC + '[201~';
36
36
  export const PASTE_MIN_LINES = 3;
37
37
  // Number of lines in a pasted block (the trailing newline doesn't add a line).
38
38
  export function countPasteLines(raw) {
39
- const normalized = String(raw).split(CR + NL).join(NL).split(CR).join(NL);
39
+ const normalized = String(raw)
40
+ .split(CR + NL)
41
+ .join(NL)
42
+ .split(CR)
43
+ .join(NL);
40
44
  const trimmed = normalized.replace(/\n+$/, '');
41
45
  if (trimmed === '')
42
46
  return 1;
@@ -49,7 +53,11 @@ export function formatPasteMarker(lines) {
49
53
  // otherwise { marker, text }: the marker goes into the input line, and the
50
54
  // original text is expanded back on submit.
51
55
  export function pasteReplacement(raw) {
52
- const text = String(raw).split(CR + NL).join(NL).split(CR).join(NL);
56
+ const text = String(raw)
57
+ .split(CR + NL)
58
+ .join(NL)
59
+ .split(CR)
60
+ .join(NL);
53
61
  const lines = countPasteLines(text);
54
62
  if (lines < PASTE_MIN_LINES)
55
63
  return null;
@@ -204,7 +212,9 @@ export function formatTokenStatus(tokens, limit = CONTEXT_LIMIT) {
204
212
  }
205
213
  else if (n >= 1_000) {
206
214
  const k = n / 1_000;
207
- compact = (k >= 100 ? String(Math.round(k)) : k.toFixed(1).replace(/\.0$/, '')) + 'k';
215
+ compact =
216
+ (k >= 100 ? String(Math.round(k)) : k.toFixed(1).replace(/\.0$/, '')) +
217
+ 'k';
208
218
  }
209
219
  else {
210
220
  compact = String(n);
@@ -232,6 +242,13 @@ export class LineEditor {
232
242
  _animating;
233
243
  _dotPhase;
234
244
  _thinkBase;
245
+ // The last rendered status row(s) (status + right-aligned context). Cached so
246
+ // a re-render driven by TYPING (the buffer changed, the status did not) can
247
+ // skip erasing and re-printing the status line entirely. On terminals like
248
+ // Tabby a full erase+rewrite on every keystroke makes the whole screen blink;
249
+ // keeping the status untouched removes that flicker. Cleared whenever the
250
+ // status genuinely changes so the cache can never go stale.
251
+ _lastStatusBlock;
235
252
  _wasRaw;
236
253
  _onData;
237
254
  // History of sent messages (for the up/down arrows).
@@ -270,7 +287,7 @@ export class LineEditor {
270
287
  // A callback the editor calls to fetch the CURRENT token count before every
271
288
  // status render, so the status line stays fresh without the caller polling.
272
289
  onContextQuery;
273
- constructor({ prompt = '> ', commands = [], locale = 'ru' } = {}) {
290
+ constructor({ prompt = '> ', commands = [], locale = 'ru', } = {}) {
274
291
  this.locale = locale;
275
292
  this.promptStr = prompt;
276
293
  this.buf = '';
@@ -288,6 +305,7 @@ export class LineEditor {
288
305
  this._animating = false;
289
306
  this._dotPhase = 0;
290
307
  this._thinkBase = '';
308
+ this._lastStatusBlock = '';
291
309
  this._wasRaw = false;
292
310
  this._onData = (b) => this._handle(b);
293
311
  this.history = [];
@@ -488,16 +506,42 @@ export class LineEditor {
488
506
  }
489
507
  process.stdout.write(CR + ESC + '[J');
490
508
  this.rendered = false;
509
+ this._lastStatusBlock = '';
510
+ }
511
+ // Erase ONLY the input rows (leave the status block untouched). Used when a
512
+ // keystroke re-renders the line but the status/context did not change: the
513
+ // status stays on screen, only the input area is redrawn. This removes the
514
+ // full-screen flicker on terminals like Tabby, where erasing the whole block
515
+ // on every character made the display blink.
516
+ _eraseInputOnly(inputRows) {
517
+ if (!this.rendered)
518
+ return;
519
+ const rows = process.stdout.rows || 24;
520
+ const up = Math.min(Math.max(0, inputRows - 1), Math.max(0, rows - 1));
521
+ if (up > 0)
522
+ process.stdout.write(ESC + '[' + up + 'A');
523
+ process.stdout.write(CR + ESC + '[J');
524
+ this.rendered = false;
491
525
  }
492
526
  _writeBlock() {
493
527
  const cols = process.stdout.columns || 80;
494
- let out = '';
495
- let top = 0;
496
528
  // Status line above the input: the spinner/answer text on the left and the
497
529
  // token context right-aligned on the SAME row (10k · 12%). The context is
498
530
  // refreshed from onContextQuery() on every render, so it follows the live
499
531
  // DeepSeek counter without a polling timer of its own.
532
+ //
533
+ // `top` MUST count EXACTLY the rows we print, or the next _eraseBlock()
534
+ // moves the cursor to the wrong row and the block (spinner + input) is
535
+ // re-drawn on top of the previous one instead of replacing it — the
536
+ // operator sees the status line duplicated/stacking, most visibly during
537
+ // the send-pause countdown (which re-renders about once per second).
538
+ // The earlier code computed `top = visRows(status + ' ' + ctxText)` even
539
+ // though it PRINTS `status + ' '.repeat(space) + ctxText` with a DIFFERENT
540
+ // total width, so at certain widths `top` disagreed with the printed rows.
541
+ // Both branches below now derive `top` from the exact string they print.
500
542
  const ctxText = this._contextText();
543
+ let statusOut = '';
544
+ let top = 0;
501
545
  if (this.statusText) {
502
546
  if (ctxText) {
503
547
  // Right-align the context on the SAME row as the status. When the
@@ -506,18 +550,20 @@ export class LineEditor {
506
550
  // put the context on its own line instead.
507
551
  const space = cols - visLen(this.statusText) - visLen(ctxText);
508
552
  if (space >= 2) {
509
- out += this.statusText + ' '.repeat(space) + ctxText + NL;
510
- top = visRows(this.statusText + ' '.repeat(2) + ctxText, cols);
553
+ const row = this.statusText + ' '.repeat(space) + ctxText;
554
+ statusOut = row + NL;
555
+ top = visRows(row, cols);
511
556
  }
512
557
  else {
513
- out += this.statusText + NL;
558
+ statusOut = this.statusText + NL;
514
559
  top = visRows(this.statusText, cols);
515
- out += ' '.repeat(Math.max(0, cols - visLen(ctxText))) + ctxText + NL;
516
- top += visRows(ctxText, cols);
560
+ const ctxRow = ' '.repeat(Math.max(0, cols - visLen(ctxText))) + ctxText;
561
+ statusOut += ctxRow + NL;
562
+ top += visRows(ctxRow, cols);
517
563
  }
518
564
  }
519
565
  else {
520
- out += this.statusText + NL;
566
+ statusOut = this.statusText + NL;
521
567
  // The status may wrap onto several lines — we account for this,
522
568
  // otherwise the block erase misses and statuses pile up.
523
569
  top = visRows(this.statusText, cols);
@@ -526,9 +572,57 @@ export class LineEditor {
526
572
  else if (ctxText) {
527
573
  // Idle: no spinner, but the context still belongs on its own line just
528
574
  // above the input, right-aligned.
529
- out += ' '.repeat(Math.max(0, cols - visLen(ctxText))) + ctxText + NL;
530
- top = visRows(ctxText, cols);
575
+ const ctxRow = ' '.repeat(Math.max(0, cols - visLen(ctxText))) + ctxText;
576
+ statusOut = ctxRow + NL;
577
+ top = visRows(ctxRow, cols);
578
+ }
579
+ // Incremental re-render: when the status block is byte-identical to what is
580
+ // already on screen (the common case while typing — the buffer changes, the
581
+ // spinner/context does not) we redraw ONLY the input rows. Erasing and
582
+ // re-printing the whole block on every keystroke is what made the display
583
+ // flicker on terminals like Tabby. `top` is recovered from the cache so the
584
+ // cursor math stays correct.
585
+ const statusBlock = statusOut;
586
+ const statusUnchanged = this.rendered && statusBlock === this._lastStatusBlock;
587
+ if (statusUnchanged) {
588
+ // Count the real input rows via layout (wrapping aware).
589
+ const layOnly = layoutInput(this.promptStr, this.buf, this.cursor, cols);
590
+ const inputLineRows = layOnly.rows.length;
591
+ const suggOnly = this._suggestions();
592
+ const shownOnly = suggOnly.slice(0, 8);
593
+ const linesBelowOnly = shownOnly.length
594
+ ? shownOnly.length + (suggOnly.length > shownOnly.length ? 1 : 0)
595
+ : 0;
596
+ this._eraseInputOnly(inputLineRows + linesBelowOnly);
597
+ let outOnly = '';
598
+ outOnly += layOnly.rows.map((r) => r.prefix + r.text).join(NL);
599
+ if (shownOnly.length) {
600
+ const maxName = Math.max(...shownOnly.map((c) => c.name.length));
601
+ const lines = shownOnly.map((c) => {
602
+ const name = theme.prompt(c.name.padEnd(maxName));
603
+ const desc = theme.dim(' ' + c.description);
604
+ return ' ' + name + desc;
605
+ });
606
+ outOnly += NL + lines.join(NL);
607
+ const hidden = suggOnly.length - shownOnly.length;
608
+ if (hidden > 0)
609
+ outOnly +=
610
+ NL +
611
+ theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
612
+ }
613
+ process.stdout.write(outOnly);
614
+ const upOnly = layOnly.rows.length - 1 - layOnly.cursorRow + linesBelowOnly;
615
+ if (upOnly > 0)
616
+ process.stdout.write(ESC + '[' + upOnly + 'A');
617
+ process.stdout.write(CR);
618
+ if (layOnly.cursorCol > 0)
619
+ process.stdout.write(ESC + '[' + layOnly.cursorCol + 'C');
620
+ this.rendered = true;
621
+ this.cursorRowFromTop = top + layOnly.cursorRow;
622
+ return;
531
623
  }
624
+ let out = statusOut;
625
+ this._lastStatusBlock = statusBlock;
532
626
  const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
533
627
  out += lay.rows.map((r) => r.prefix + r.text).join(NL);
534
628
  // We draw the slash-command hints BELOW the input line. We then move the
@@ -546,13 +640,17 @@ export class LineEditor {
546
640
  out += NL + lines.join(NL);
547
641
  const hidden = sugg.length - shown.length;
548
642
  if (hidden > 0)
549
- out += NL + theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
643
+ out +=
644
+ NL +
645
+ theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
550
646
  }
551
647
  process.stdout.write(out);
552
648
  const lastRow = lay.rows.length - 1;
553
649
  // Move the cursor up: first to the input line within lay, then further by
554
650
  // the hint lines (if any) — the cursor must sit on the input.
555
- const linesBelow = shown.length ? shown.length + (sugg.length > shown.length ? 1 : 0) : 0;
651
+ const linesBelow = shown.length
652
+ ? shown.length + (sugg.length > shown.length ? 1 : 0)
653
+ : 0;
556
654
  const up = lastRow - lay.cursorRow + linesBelow;
557
655
  if (up > 0)
558
656
  process.stdout.write(ESC + '[' + up + 'A');
@@ -663,7 +761,12 @@ export class LineEditor {
663
761
  assistant(msg) {
664
762
  this.stop();
665
763
  const rendered = renderMarkdown(msg);
666
- this.printAbove(NL + theme.assistant(translate(this.locale)('editor.answer')) + NL + rendered + NL + theme.dim('─'.repeat(60)));
764
+ this.printAbove(NL +
765
+ theme.assistant(translate(this.locale)('editor.answer')) +
766
+ NL +
767
+ rendered +
768
+ NL +
769
+ theme.dim('─'.repeat(60)));
667
770
  }
668
771
  // Warning to the operator (e.g. the agent stopped suspiciously).
669
772
  // Printed above the input line without overwriting it.
@@ -707,7 +810,9 @@ export class LineEditor {
707
810
  _insert(text) {
708
811
  const chars = Array.from(this.buf);
709
812
  const ins = Array.from(String(text));
710
- const next = chars.slice(0, this.cursor).concat(ins, chars.slice(this.cursor));
813
+ const next = chars
814
+ .slice(0, this.cursor)
815
+ .concat(ins, chars.slice(this.cursor));
711
816
  this.buf = next.join('');
712
817
  this.cursor += ins.length;
713
818
  }
@@ -751,7 +856,11 @@ export class LineEditor {
751
856
  _insertFallback(raw) {
752
857
  const rep = pasteReplacement(raw);
753
858
  if (!rep) {
754
- const text = String(raw).split(CR + NL).join(NL).split(CR).join(NL);
859
+ const text = String(raw)
860
+ .split(CR + NL)
861
+ .join(NL)
862
+ .split(CR)
863
+ .join(NL);
755
864
  this._insert(text);
756
865
  return;
757
866
  }
@@ -1186,9 +1295,15 @@ export function selftest() {
1186
1295
  ];
1187
1296
  for (const t of cases) {
1188
1297
  const r = layoutInput(t.p, t.b, t.c, t.w);
1189
- console.log(JSON.stringify({ rows: r.rows.length, row: r.cursorRow, col: r.cursorCol }));
1298
+ console.log(JSON.stringify({
1299
+ rows: r.rows.length,
1300
+ row: r.cursorRow,
1301
+ col: r.cursorCol,
1302
+ }));
1190
1303
  }
1191
1304
  }
1192
- if (process.argv[1] && process.argv[1].endsWith('input.js') && process.argv.includes('--selftest')) {
1305
+ if (process.argv[1] &&
1306
+ process.argv[1].endsWith('input.js') &&
1307
+ process.argv.includes('--selftest')) {
1193
1308
  selftest();
1194
1309
  }
package/dist/mcp.js CHANGED
@@ -15,7 +15,10 @@ export function hardenPlaywrightArgs(command, args) {
15
15
  const hay = (String(command || '') + ' ' + args.join(' ')).toLowerCase();
16
16
  if (!hay.includes('playwright') || !hay.includes('mcp'))
17
17
  return args;
18
- if (args.some((a) => a === '--isolated' || a.startsWith('--user-data-dir') || a === '--extension' || a.startsWith('--cdp-endpoint'))) {
18
+ if (args.some((a) => a === '--isolated' ||
19
+ a.startsWith('--user-data-dir') ||
20
+ a === '--extension' ||
21
+ a.startsWith('--cdp-endpoint'))) {
19
22
  return args;
20
23
  }
21
24
  return [...args, '--isolated'];
@@ -161,7 +164,9 @@ export function renderMcpResult(result) {
161
164
  parts.push(String(b.text ?? ''));
162
165
  }
163
166
  else if (type === 'image') {
164
- parts.push('[image ' + String(b.mimeType ?? 'unknown') + ', written to disk by the MCP server]');
167
+ parts.push('[image ' +
168
+ String(b.mimeType ?? 'unknown') +
169
+ ', written to disk by the MCP server]');
165
170
  }
166
171
  else if (type === 'resource') {
167
172
  const res = (b.resource || {});
@@ -169,7 +174,11 @@ export function renderMcpResult(result) {
169
174
  parts.push(String(res.uri ?? '') + String.fromCharCode(10) + res.text);
170
175
  }
171
176
  else if (typeof res.blob === 'string') {
172
- parts.push('[binary resource ' + String(res.uri ?? '') + ', ' + String(res.mimeType ?? '') + ']');
177
+ parts.push('[binary resource ' +
178
+ String(res.uri ?? '') +
179
+ ', ' +
180
+ String(res.mimeType ?? '') +
181
+ ']');
173
182
  }
174
183
  else {
175
184
  parts.push(String(res.uri ?? ''));
@@ -187,7 +196,10 @@ export function renderMcpResult(result) {
187
196
  }
188
197
  }
189
198
  }
190
- const text = parts.filter((p) => p !== '').join(String.fromCharCode(10) + String.fromCharCode(10)).trim();
199
+ const text = parts
200
+ .filter((p) => p !== '')
201
+ .join(String.fromCharCode(10) + String.fromCharCode(10))
202
+ .trim();
191
203
  return formatResultString(text || '(MCP tool returned no content)', r.isError);
192
204
  }
193
205
  function formatResultString(result, isError) {
@@ -13,10 +13,9 @@ export function extractPathToken(text) {
13
13
  i++;
14
14
  continue;
15
15
  }
16
- let quote = '';
17
16
  let start = i;
18
17
  if (c === String.fromCharCode(39) || c === String.fromCharCode(34)) {
19
- quote = c;
18
+ const quote = c;
20
19
  start = i + 1;
21
20
  i = start;
22
21
  while (i < n && chars[i] !== quote)
@@ -63,7 +62,9 @@ function looksLikeToken(tok) {
63
62
  if (ext.length > 8)
64
63
  return false;
65
64
  for (const ch of ext) {
66
- const ok = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
65
+ const ok = (ch >= 'a' && ch <= 'z') ||
66
+ (ch >= 'A' && ch <= 'Z') ||
67
+ (ch >= '0' && ch <= '9');
67
68
  if (!ok)
68
69
  return false;
69
70
  }
@@ -54,7 +54,7 @@ async function copyDirJsFiles(from, to) {
54
54
  return copied;
55
55
  }
56
56
  // ---------- /self-review ----------
57
- export async function selfReview({ browser, focus, transcript }) {
57
+ export async function selfReview({ browser, focus, transcript, }) {
58
58
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
59
59
  const snapRoot = path.join(SNAP_ROOT);
60
60
  const snapDir = path.join(snapRoot, `run-${stamp}`);
@@ -125,7 +125,7 @@ export async function selfReview({ browser, focus, transcript }) {
125
125
  return { snapDir, reportPath, changed };
126
126
  }
127
127
  // ---------- /self-diff ----------
128
- export async function selfDiff({ name }) {
128
+ export async function selfDiff({ name, }) {
129
129
  const snapDir = path.join(SNAP_ROOT, name);
130
130
  const snapStat = await fs.stat(snapDir).catch(() => null);
131
131
  if (!snapStat)
@@ -154,7 +154,7 @@ export async function selfDiff({ name }) {
154
154
  }
155
155
  }
156
156
  // ---------- /self-apply ----------
157
- export async function selfApply({ name }) {
157
+ export async function selfApply({ name, }) {
158
158
  const snapDir = path.join(SNAP_ROOT, name);
159
159
  const snapStat = await fs.stat(snapDir).catch(() => null);
160
160
  if (!snapStat)
@@ -180,9 +180,9 @@ export async function selfApply({ name }) {
180
180
  console.log(theme.warn(`\nПерезапусти агента, чтобы изменения вступили в силу.\n`));
181
181
  }
182
182
  // ---------- /self-list ----------
183
- export async function selfList({ config } = {}) {
183
+ export async function selfList({ config: _config, } = {}) {
184
184
  const snapRoot = path.join(SNAP_ROOT);
185
- let entries = [];
185
+ let entries;
186
186
  try {
187
187
  entries = await fs.readdir(snapRoot);
188
188
  }
@@ -230,7 +230,7 @@ async function diffFiles(origDir, newDir) {
230
230
  }
231
231
  return changed;
232
232
  }
233
- function buildReviewPrompt({ focus, snapDir }) {
233
+ function buildReviewPrompt({ focus, snapDir, }) {
234
234
  const focusLine = focus
235
235
  ? `\nThe user asked you to focus especially on: ${focus}\n`
236
236
  : '';
package/dist/spinner.js CHANGED
@@ -108,7 +108,10 @@ export function createSpinner(locale = 'ru') {
108
108
  // so the countdown refreshes without resetting the dots.
109
109
  if (animating && dotTimer && spinner) {
110
110
  animBase = theme.brown(stripEllipsis(label));
111
- spinner.text = animBase + theme.brown(DOTS[dotPhase] + DOTS_PAD.slice(DOTS[dotPhase].length)) + HINT;
111
+ spinner.text =
112
+ animBase +
113
+ theme.brown(DOTS[dotPhase] + DOTS_PAD.slice(DOTS[dotPhase].length)) +
114
+ HINT;
112
115
  return;
113
116
  }
114
117
  startAnimated(label);
@@ -130,7 +133,10 @@ export function createSpinner(locale = 'ru') {
130
133
  toolResult: (result) => {
131
134
  stop();
132
135
  const text = typeof result === 'string' ? result : JSON.stringify(result);
133
- const preview = text.slice(0, 200).split(String.fromCharCode(10)).join(' ↵ ');
136
+ const preview = text
137
+ .slice(0, 200)
138
+ .split(String.fromCharCode(10))
139
+ .join(' ↵ ');
134
140
  console.log(theme.toolResult(' → ' + preview + String.fromCharCode(10)));
135
141
  },
136
142
  assistant: (msg) => {
@@ -6,14 +6,20 @@ export function renderContextSection(context) {
6
6
  let out = '';
7
7
  for (const f of context.agents) {
8
8
  out +=
9
- '\n## Project instructions (' + f.path + ')\n\n' +
10
- 'The following come from ' + f.path + '. Follow them for this project unless the operator explicitly overrides them.\n\n' +
9
+ '\n## Project instructions (' +
10
+ f.path +
11
+ ')\n\n' +
12
+ 'The following come from ' +
13
+ f.path +
14
+ '. Follow them for this project unless the operator explicitly overrides them.\n\n' +
11
15
  f.content +
12
16
  '\n';
13
17
  }
14
18
  for (const f of context.memory) {
15
19
  out +=
16
- '\n## Memory (' + f.path + ')\n\n' +
20
+ '\n## Memory (' +
21
+ f.path +
22
+ ')\n\n' +
17
23
  'Durable notes accumulated across sessions. Treat as background knowledge; update this file when you learn something worth remembering.\n\n' +
18
24
  f.content +
19
25
  '\n';
@@ -21,7 +27,9 @@ export function renderContextSection(context) {
21
27
  if (context.memory.length) {
22
28
  out +=
23
29
  '\nWhen you learn a durable fact about this project or the operator preferences (build quirks, ' +
24
- 'conventions, gotchas), append a short bullet to ' + context.memory[0].path + ' via Edit/Write so it survives ' +
30
+ 'conventions, gotchas), append a short bullet to ' +
31
+ context.memory[0].path +
32
+ ' via Edit/Write so it survives ' +
25
33
  'into future sessions. Keep entries concise.\n';
26
34
  }
27
35
  if (context.skills.length) {
@@ -29,7 +37,13 @@ export function renderContextSection(context) {
29
37
  const tools = s.allowedTools && s.allowedTools.length
30
38
  ? ' [tools: ' + s.allowedTools.join(', ') + ']'
31
39
  : '';
32
- return '- ' + s.name + tools + ': ' + (s.description || '(no description)') + '\n file: ' + s.path;
40
+ return ('- ' +
41
+ s.name +
42
+ tools +
43
+ ': ' +
44
+ (s.description || '(no description)') +
45
+ '\n file: ' +
46
+ s.path);
33
47
  });
34
48
  out +=
35
49
  '\n## Skills\n\n' +
package/dist/tools.js CHANGED
@@ -166,7 +166,7 @@ export function createTools(workdir, { undo } = {}) {
166
166
  old_base64: 'string?',
167
167
  new_base64: 'string?',
168
168
  },
169
- fn: async ({ path: p, old_string, new_string, old_base64, new_base64 }) => {
169
+ fn: async ({ path: p, old_string, new_string, old_base64, new_base64, }) => {
170
170
  const file = safe(req(p, 'path'));
171
171
  const oldStr = decodeContent(old_string, old_base64);
172
172
  const newStr = decodeContent(new_string, new_base64);
@@ -241,11 +241,7 @@ export function createTools(workdir, { undo } = {}) {
241
241
  const scope = searchPath ? '"' + target + '\\*' : '*';
242
242
  return runShell(`findstr /s /n /r /c:"${escaped}" ` + scope);
243
243
  }
244
- const flags = mode === 'files_only'
245
- ? '-rlE'
246
- : mode === 'count'
247
- ? '-rcE'
248
- : '-rnE';
244
+ const flags = mode === 'files_only' ? '-rlE' : mode === 'count' ? '-rcE' : '-rnE';
249
245
  const includeArg = inc ? ' --include=' + JSON.stringify(inc) : '';
250
246
  return runShell(`grep ${flags}${includeArg} ${JSON.stringify(pat)} ` +
251
247
  `${JSON.stringify(target)} || true`);
package/dist/web.js CHANGED
@@ -43,7 +43,7 @@ function htmlToText(html) {
43
43
  return s;
44
44
  }
45
45
  // fetch with redirects and a timeout
46
- async function httpFetch(url, { timeout = DEFAULT_TIMEOUT, headers = {} } = {}) {
46
+ async function httpFetch(url, { timeout = DEFAULT_TIMEOUT, headers = {}, } = {}) {
47
47
  const ctrl = new AbortController();
48
48
  const t = setTimeout(() => ctrl.abort(), timeout);
49
49
  try {