zames_pro 2.29.15 → 2.31.0

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/input.js CHANGED
@@ -260,6 +260,11 @@ export class LineEditor {
260
260
  _lastStatusBlock;
261
261
  _wasRaw;
262
262
  _onData;
263
+ // On SIGWINCH we redraw the whole block so the layout follows the new
264
+ // terminal width. Debounced: a drag fires many resize events. `_onResize`
265
+ // is stored so it can be removed in dispose().
266
+ _onResize;
267
+ _resizeTimer;
263
268
  // History of sent messages (for the up/down arrows).
264
269
  history;
265
270
  _histIndex;
@@ -304,6 +309,10 @@ export class LineEditor {
304
309
  // A callback the editor calls to fetch the CURRENT token count before every
305
310
  // status render, so the status line stays fresh without the caller polling.
306
311
  onContextQuery;
312
+ // The context window size used for the fill percentage and its color. Kept
313
+ // as a mutable field (set from config.ui.contextLimit) so the status bar
314
+ // reflects the real limit instead of a hardcoded constant.
315
+ contextLimit;
307
316
  constructor({ prompt = '> ', commands = [], locale = 'ru', } = {}) {
308
317
  this.locale = locale;
309
318
  this.promptStr = prompt;
@@ -327,6 +336,26 @@ export class LineEditor {
327
336
  this._lastStatusBlock = '';
328
337
  this._wasRaw = false;
329
338
  this._onData = (b) => this._handle(b);
339
+ this._resizeTimer = null;
340
+ this._onResize = () => {
341
+ // Debounce: a window drag fires a burst of SIGWINCH events. We wait a
342
+ // short moment, then redraw the block at the NEW width.
343
+ if (this._resizeTimer)
344
+ clearTimeout(this._resizeTimer);
345
+ this._resizeTimer = setTimeout(() => {
346
+ this._resizeTimer = null;
347
+ // Repaint ONLY the status + input block. We deliberately do NOT clear
348
+ // the whole viewport here: the operator's scrollback (the tool-call
349
+ // lines and answers printed above the input) must stay on screen.
350
+ // `_eraseBlock` erases from the top of the block DOWN (CR + ESC[J),
351
+ // so everything above it is preserved; then _writeBlock redraws the
352
+ // block at the new width. A slight leftover is possible at extreme
353
+ // width changes, but losing the history is far worse.
354
+ this._render();
355
+ }, 120);
356
+ if (this._resizeTimer.unref)
357
+ this._resizeTimer.unref();
358
+ };
330
359
  this.history = [];
331
360
  this._histIndex = 0;
332
361
  this._histDraft = '';
@@ -343,6 +372,7 @@ export class LineEditor {
343
372
  this.contextTokens = null;
344
373
  this.thinkingEnabled = false;
345
374
  this.searchEnabled = false;
375
+ this.contextLimit = CONTEXT_LIMIT;
346
376
  this.onToggleQuery = null;
347
377
  this.onContextQuery = null;
348
378
  }
@@ -370,7 +400,9 @@ export class LineEditor {
370
400
  const n = this.onContextQuery();
371
401
  this.contextTokens = typeof n === 'number' ? n : null;
372
402
  this.contextStatus =
373
- n === null || n === undefined ? null : formatTokenStatus(n) || null;
403
+ n === null || n === undefined
404
+ ? null
405
+ : formatTokenStatus(n, this.contextLimit) || null;
374
406
  }
375
407
  catch {
376
408
  // A broken callback must never break the render.
@@ -384,7 +416,7 @@ export class LineEditor {
384
416
  const ctx = this._contextForRender();
385
417
  if (!ctx)
386
418
  return '';
387
- const level = tokenStatusLevel(this.contextTokens);
419
+ const level = tokenStatusLevel(this.contextTokens, this.contextLimit);
388
420
  if (level === 'high')
389
421
  return theme.error(ctx);
390
422
  if (level === 'warn')
@@ -392,11 +424,11 @@ export class LineEditor {
392
424
  return theme.success(ctx);
393
425
  }
394
426
  // Icons for the DeepSeek toggles: 🧠 "Deep thinking" and 🌐 "Smart search",
395
- // shown before the context counter. Only the ENABLED toggle is shown, in
396
- // green. A hidden icon means OFF — this does not rely on the terminal
397
- // honoring the ANSI color (it ignores it for emoji), so there is no
398
- // ambiguity. U+FE0E forces the monochrome TEXT presentation so the green is
399
- // actually visible.
427
+ // shown before the context counter. Only the ENABLED toggles are shown, so
428
+ // the state is unambiguous WITHOUT relying on the terminal coloring the
429
+ // emoji: an icon present = ON, absent = OFF. Emoji are (almost) never
430
+ // colored by ANSI, so a "dim" vs "green" pair of the SAME emoji would look
431
+ // identical — that is why the disabled icon is hidden instead.
400
432
  _toggleIcons() {
401
433
  const TEXT = '\uFE0E';
402
434
  const parts = [];
@@ -466,12 +498,18 @@ export class LineEditor {
466
498
  stdin.resume();
467
499
  process.stdout.write(ESC + '[?2004h');
468
500
  stdin.on('data', this._onData);
501
+ // Redraw on terminal resize so the layout follows the new width instead
502
+ // of leaving artifacts from the old one.
503
+ process.stdout.on('resize', this._onResize);
469
504
  this._writeBlock();
470
505
  return true;
471
506
  }
472
507
  dispose() {
473
508
  const stdin = process.stdin;
474
509
  stdin.removeListener('data', this._onData);
510
+ process.stdout.removeListener('resize', this._onResize);
511
+ if (this._resizeTimer)
512
+ clearTimeout(this._resizeTimer);
475
513
  process.stdout.write(ESC + '[?2004l');
476
514
  this._stopDots();
477
515
  if (this.rendered)
@@ -529,9 +567,16 @@ export class LineEditor {
529
567
  setContextStatus(tokens) {
530
568
  this.contextTokens =
531
569
  typeof tokens === 'number' && Number.isFinite(tokens) ? tokens : null;
532
- this.contextStatus = formatTokenStatus(tokens) || null;
570
+ this.contextStatus = formatTokenStatus(tokens, this.contextLimit) || null;
533
571
  this._render();
534
572
  }
573
+ // Update the context window size (from config.ui.contextLimit). The next
574
+ // render recomputes the percentage and its color with the new limit.
575
+ setContextLimit(limit) {
576
+ if (typeof limit === 'number' && Number.isFinite(limit) && limit > 0) {
577
+ this.contextLimit = limit;
578
+ }
579
+ }
535
580
  // Update the interface language (labels: hint, answer marker, pause).
536
581
  setLocale(locale) {
537
582
  this.locale = locale;
@@ -735,6 +780,17 @@ export class LineEditor {
735
780
  this._eraseBlock();
736
781
  this._writeBlock();
737
782
  }
783
+ // Full-screen clear (like /clear in a shell) followed by a fresh repaint of
784
+ // the status + input block. Used by /new and /resume so a new/resumed chat
785
+ // starts on a clean terminal instead of under pages of the previous chat.
786
+ // We do NOT clear the scrollback here: the operator may still want it.
787
+ clearScreen() {
788
+ this._eraseBlock();
789
+ process.stdout.write(ESC + '[2J' + ESC + '[H');
790
+ this.rendered = false;
791
+ this._lastStatusBlock = '';
792
+ this._writeBlock();
793
+ }
738
794
  // Repaint ONLY the input rows, leaving the status/spinner block untouched.
739
795
  // Used for buffer/cursor changes (typing, arrows, backspace, …): the status
740
796
  // line has its OWN animation timer, and repainting it on every keystroke
@@ -874,7 +930,8 @@ export class LineEditor {
874
930
  this.setStatus('');
875
931
  }
876
932
  toolCall(name, args) {
877
- const preview = safeJson(args).slice(0, 120);
933
+ const full = safeJson(args);
934
+ const preview = full.slice(0, 120) + (full.length > 120 ? ' …' : '');
878
935
  this.printAbove(theme.tool('🔧 ' + name) + ' ' + theme.dim(preview));
879
936
  // A tool may run for a long time (Bash, npm test, MCP). Without an active
880
937
  // animation the operator sees a frozen screen and cannot tell work is in
@@ -887,18 +944,36 @@ export class LineEditor {
887
944
  toolResult(result) {
888
945
  this.stop();
889
946
  const text = typeof result === 'string' ? result : safeJson(result);
890
- const preview = text.slice(0, 200).split(NL).join(' ↵ ');
947
+ const preview = text.slice(0, 200).split(NL).join(' ↵ ') + (text.length > 200 ? ' …' : '');
891
948
  this.printAbove(theme.toolResult(' → ' + preview));
892
949
  }
893
950
  assistant(msg) {
894
951
  this.stop();
895
952
  const rendered = renderMarkdown(msg);
953
+ // Show the used context size at the END of the answer too. During
954
+ // generation it lives in the status line, but ui.stop() clears that right
955
+ // before this call, so the operator lost the number exactly when the
956
+ // answer arrived. Read it fresh via onContextQuery and append it dimmed
957
+ // under the answer divider.
958
+ let ctxLine = '';
959
+ if (this.onContextQuery) {
960
+ try {
961
+ const n = this.onContextQuery();
962
+ const txt = formatTokenStatus(n, this.contextLimit);
963
+ if (txt)
964
+ ctxLine = NL + theme.dim(txt);
965
+ }
966
+ catch {
967
+ // never break the render
968
+ }
969
+ }
896
970
  this.printAbove(NL +
897
971
  theme.assistant(translate(this.locale)('editor.answer')) +
898
972
  NL +
899
973
  rendered +
900
974
  NL +
901
- theme.dim('─'.repeat(60)));
975
+ theme.dim('─'.repeat(60)) +
976
+ ctxLine);
902
977
  }
903
978
  // Warning to the operator (e.g. the agent stopped suspiciously).
904
979
  // Printed above the input line without overwriting it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.29.15",
3
+ "version": "2.31.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {