zames_pro 2.20.0 → 2.21.2

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,36 +506,71 @@ 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;
545
+ // Never fill a row to the FULL terminal width: on terminals with
546
+ // autowrap (Tabby, iTerm, Windows Terminal) a row that reaches the last
547
+ // column wraps the cursor to the next line, so the following erase moves
548
+ // to the wrong row and the status line STACKS on screen. Keep one column
549
+ // free (effective width = cols - 1) for the right-aligned context and for
550
+ // the padded status row.
551
+ const usable = Math.max(1, cols - 1);
501
552
  if (this.statusText) {
502
553
  if (ctxText) {
503
554
  // Right-align the context on the SAME row as the status. When the
504
555
  // status is too long to leave room, do NOT cram them together (that
505
556
  // pushed the trailing `%` past the right edge and it got truncated):
506
557
  // put the context on its own line instead.
507
- const space = cols - visLen(this.statusText) - visLen(ctxText);
558
+ const space = usable - visLen(this.statusText) - visLen(ctxText);
508
559
  if (space >= 2) {
509
- out += this.statusText + ' '.repeat(space) + ctxText + NL;
510
- top = visRows(this.statusText + ' '.repeat(2) + ctxText, cols);
560
+ const row = this.statusText + ' '.repeat(space) + ctxText;
561
+ statusOut = row + NL;
562
+ top = visRows(row, cols);
511
563
  }
512
564
  else {
513
- out += this.statusText + NL;
565
+ statusOut = this.statusText + NL;
514
566
  top = visRows(this.statusText, cols);
515
- out += ' '.repeat(Math.max(0, cols - visLen(ctxText))) + ctxText + NL;
516
- top += visRows(ctxText, cols);
567
+ const ctxRow = ' '.repeat(Math.max(0, usable - visLen(ctxText))) + ctxText;
568
+ statusOut += ctxRow + NL;
569
+ top += visRows(ctxRow, cols);
517
570
  }
518
571
  }
519
572
  else {
520
- out += this.statusText + NL;
573
+ statusOut = this.statusText + NL;
521
574
  // The status may wrap onto several lines — we account for this,
522
575
  // otherwise the block erase misses and statuses pile up.
523
576
  top = visRows(this.statusText, cols);
@@ -526,9 +579,57 @@ export class LineEditor {
526
579
  else if (ctxText) {
527
580
  // Idle: no spinner, but the context still belongs on its own line just
528
581
  // above the input, right-aligned.
529
- out += ' '.repeat(Math.max(0, cols - visLen(ctxText))) + ctxText + NL;
530
- top = visRows(ctxText, cols);
582
+ const ctxRow = ' '.repeat(Math.max(0, usable - visLen(ctxText))) + ctxText;
583
+ statusOut = ctxRow + NL;
584
+ top = visRows(ctxRow, cols);
585
+ }
586
+ // Incremental re-render: when the status block is byte-identical to what is
587
+ // already on screen (the common case while typing — the buffer changes, the
588
+ // spinner/context does not) we redraw ONLY the input rows. Erasing and
589
+ // re-printing the whole block on every keystroke is what made the display
590
+ // flicker on terminals like Tabby. `top` is recovered from the cache so the
591
+ // cursor math stays correct.
592
+ const statusBlock = statusOut;
593
+ const statusUnchanged = this.rendered && statusBlock === this._lastStatusBlock;
594
+ if (statusUnchanged) {
595
+ // Count the real input rows via layout (wrapping aware).
596
+ const layOnly = layoutInput(this.promptStr, this.buf, this.cursor, cols);
597
+ const inputLineRows = layOnly.rows.length;
598
+ const suggOnly = this._suggestions();
599
+ const shownOnly = suggOnly.slice(0, 8);
600
+ const linesBelowOnly = shownOnly.length
601
+ ? shownOnly.length + (suggOnly.length > shownOnly.length ? 1 : 0)
602
+ : 0;
603
+ this._eraseInputOnly(inputLineRows + linesBelowOnly);
604
+ let outOnly = '';
605
+ outOnly += layOnly.rows.map((r) => r.prefix + r.text).join(NL);
606
+ if (shownOnly.length) {
607
+ const maxName = Math.max(...shownOnly.map((c) => c.name.length));
608
+ const lines = shownOnly.map((c) => {
609
+ const name = theme.prompt(c.name.padEnd(maxName));
610
+ const desc = theme.dim(' ' + c.description);
611
+ return ' ' + name + desc;
612
+ });
613
+ outOnly += NL + lines.join(NL);
614
+ const hidden = suggOnly.length - shownOnly.length;
615
+ if (hidden > 0)
616
+ outOnly +=
617
+ NL +
618
+ theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
619
+ }
620
+ process.stdout.write(outOnly);
621
+ const upOnly = layOnly.rows.length - 1 - layOnly.cursorRow + linesBelowOnly;
622
+ if (upOnly > 0)
623
+ process.stdout.write(ESC + '[' + upOnly + 'A');
624
+ process.stdout.write(CR);
625
+ if (layOnly.cursorCol > 0)
626
+ process.stdout.write(ESC + '[' + layOnly.cursorCol + 'C');
627
+ this.rendered = true;
628
+ this.cursorRowFromTop = top + layOnly.cursorRow;
629
+ return;
531
630
  }
631
+ let out = statusOut;
632
+ this._lastStatusBlock = statusBlock;
532
633
  const lay = layoutInput(this.promptStr, this.buf, this.cursor, cols);
533
634
  out += lay.rows.map((r) => r.prefix + r.text).join(NL);
534
635
  // We draw the slash-command hints BELOW the input line. We then move the
@@ -546,13 +647,17 @@ export class LineEditor {
546
647
  out += NL + lines.join(NL);
547
648
  const hidden = sugg.length - shown.length;
548
649
  if (hidden > 0)
549
- out += NL + theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
650
+ out +=
651
+ NL +
652
+ theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
550
653
  }
551
654
  process.stdout.write(out);
552
655
  const lastRow = lay.rows.length - 1;
553
656
  // Move the cursor up: first to the input line within lay, then further by
554
657
  // 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;
658
+ const linesBelow = shown.length
659
+ ? shown.length + (sugg.length > shown.length ? 1 : 0)
660
+ : 0;
556
661
  const up = lastRow - lay.cursorRow + linesBelow;
557
662
  if (up > 0)
558
663
  process.stdout.write(ESC + '[' + up + 'A');
@@ -663,7 +768,12 @@ export class LineEditor {
663
768
  assistant(msg) {
664
769
  this.stop();
665
770
  const rendered = renderMarkdown(msg);
666
- this.printAbove(NL + theme.assistant(translate(this.locale)('editor.answer')) + NL + rendered + NL + theme.dim('─'.repeat(60)));
771
+ this.printAbove(NL +
772
+ theme.assistant(translate(this.locale)('editor.answer')) +
773
+ NL +
774
+ rendered +
775
+ NL +
776
+ theme.dim('─'.repeat(60)));
667
777
  }
668
778
  // Warning to the operator (e.g. the agent stopped suspiciously).
669
779
  // Printed above the input line without overwriting it.
@@ -707,7 +817,9 @@ export class LineEditor {
707
817
  _insert(text) {
708
818
  const chars = Array.from(this.buf);
709
819
  const ins = Array.from(String(text));
710
- const next = chars.slice(0, this.cursor).concat(ins, chars.slice(this.cursor));
820
+ const next = chars
821
+ .slice(0, this.cursor)
822
+ .concat(ins, chars.slice(this.cursor));
711
823
  this.buf = next.join('');
712
824
  this.cursor += ins.length;
713
825
  }
@@ -751,7 +863,11 @@ export class LineEditor {
751
863
  _insertFallback(raw) {
752
864
  const rep = pasteReplacement(raw);
753
865
  if (!rep) {
754
- const text = String(raw).split(CR + NL).join(NL).split(CR).join(NL);
866
+ const text = String(raw)
867
+ .split(CR + NL)
868
+ .join(NL)
869
+ .split(CR)
870
+ .join(NL);
755
871
  this._insert(text);
756
872
  return;
757
873
  }
@@ -1186,9 +1302,15 @@ export function selftest() {
1186
1302
  ];
1187
1303
  for (const t of cases) {
1188
1304
  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 }));
1305
+ console.log(JSON.stringify({
1306
+ rows: r.rows.length,
1307
+ row: r.cursorRow,
1308
+ col: r.cursorCol,
1309
+ }));
1190
1310
  }
1191
1311
  }
1192
- if (process.argv[1] && process.argv[1].endsWith('input.js') && process.argv.includes('--selftest')) {
1312
+ if (process.argv[1] &&
1313
+ process.argv[1].endsWith('input.js') &&
1314
+ process.argv.includes('--selftest')) {
1193
1315
  selftest();
1194
1316
  }
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 {