zames_pro 2.29.16 → 2.32.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/agent-loop.js +138 -12
- package/dist/browser.js +422 -145
- package/dist/commands.js +78 -0
- package/dist/compact.js +178 -0
- package/dist/config-menu.js +22 -5
- package/dist/config.js +43 -0
- package/dist/confirm.js +30 -6
- package/dist/context.js +3 -2
- package/dist/gitTools.js +3 -2
- package/dist/i18n.js +94 -8
- package/dist/index.js +222 -174
- package/dist/input.js +104 -11
- package/package.json +1 -1
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
|
|
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
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
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,34 @@ 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);
|
|
504
|
+
// Pin the input block to the BOTTOM of the terminal. A fresh terminal
|
|
505
|
+
// starts the cursor at the top, so without this the input line sits at
|
|
506
|
+
// the top and only "descends" as output accumulates. Filling the viewport
|
|
507
|
+
// with newlines first makes the block start at the bottom, and every
|
|
508
|
+
// later printAbove() pushes content UP while the input stays put.
|
|
509
|
+
this._padToBottom();
|
|
469
510
|
this._writeBlock();
|
|
470
511
|
return true;
|
|
471
512
|
}
|
|
513
|
+
// Fill the current viewport with empty lines so the next _writeBlock()
|
|
514
|
+
// lands at the BOTTOM of the terminal. Used at start and after clearScreen.
|
|
515
|
+
// We deliberately emit real newlines (not a cursor-position escape) because
|
|
516
|
+
// the block height is not known yet and scrolling keeps the block's own
|
|
517
|
+
// cursor math (`cursorRowFromTop`) correct.
|
|
518
|
+
_padToBottom() {
|
|
519
|
+
const rows = process.stdout.rows || 24;
|
|
520
|
+
if (rows > 1)
|
|
521
|
+
process.stdout.write(NL.repeat(rows - 1));
|
|
522
|
+
}
|
|
472
523
|
dispose() {
|
|
473
524
|
const stdin = process.stdin;
|
|
474
525
|
stdin.removeListener('data', this._onData);
|
|
526
|
+
process.stdout.removeListener('resize', this._onResize);
|
|
527
|
+
if (this._resizeTimer)
|
|
528
|
+
clearTimeout(this._resizeTimer);
|
|
475
529
|
process.stdout.write(ESC + '[?2004l');
|
|
476
530
|
this._stopDots();
|
|
477
531
|
if (this.rendered)
|
|
@@ -529,9 +583,16 @@ export class LineEditor {
|
|
|
529
583
|
setContextStatus(tokens) {
|
|
530
584
|
this.contextTokens =
|
|
531
585
|
typeof tokens === 'number' && Number.isFinite(tokens) ? tokens : null;
|
|
532
|
-
this.contextStatus = formatTokenStatus(tokens) || null;
|
|
586
|
+
this.contextStatus = formatTokenStatus(tokens, this.contextLimit) || null;
|
|
533
587
|
this._render();
|
|
534
588
|
}
|
|
589
|
+
// Update the context window size (from config.ui.contextLimit). The next
|
|
590
|
+
// render recomputes the percentage and its color with the new limit.
|
|
591
|
+
setContextLimit(limit) {
|
|
592
|
+
if (typeof limit === 'number' && Number.isFinite(limit) && limit > 0) {
|
|
593
|
+
this.contextLimit = limit;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
535
596
|
// Update the interface language (labels: hint, answer marker, pause).
|
|
536
597
|
setLocale(locale) {
|
|
537
598
|
this.locale = locale;
|
|
@@ -735,6 +796,19 @@ export class LineEditor {
|
|
|
735
796
|
this._eraseBlock();
|
|
736
797
|
this._writeBlock();
|
|
737
798
|
}
|
|
799
|
+
// Full-screen clear (like /clear in a shell) followed by a fresh repaint of
|
|
800
|
+
// the status + input block. Used by /new and /resume so a new/resumed chat
|
|
801
|
+
// starts on a clean terminal instead of under pages of the previous chat.
|
|
802
|
+
// We do NOT clear the scrollback here: the operator may still want it.
|
|
803
|
+
clearScreen() {
|
|
804
|
+
this._eraseBlock();
|
|
805
|
+
process.stdout.write(ESC + '[2J' + ESC + '[H');
|
|
806
|
+
this.rendered = false;
|
|
807
|
+
this._lastStatusBlock = '';
|
|
808
|
+
// Re-pin the block to the bottom after the screen was cleared.
|
|
809
|
+
this._padToBottom();
|
|
810
|
+
this._writeBlock();
|
|
811
|
+
}
|
|
738
812
|
// Repaint ONLY the input rows, leaving the status/spinner block untouched.
|
|
739
813
|
// Used for buffer/cursor changes (typing, arrows, backspace, …): the status
|
|
740
814
|
// line has its OWN animation timer, and repainting it on every keystroke
|
|
@@ -874,7 +948,8 @@ export class LineEditor {
|
|
|
874
948
|
this.setStatus('');
|
|
875
949
|
}
|
|
876
950
|
toolCall(name, args) {
|
|
877
|
-
const
|
|
951
|
+
const full = safeJson(args);
|
|
952
|
+
const preview = full.slice(0, 120) + (full.length > 120 ? ' …' : '');
|
|
878
953
|
this.printAbove(theme.tool('🔧 ' + name) + ' ' + theme.dim(preview));
|
|
879
954
|
// A tool may run for a long time (Bash, npm test, MCP). Without an active
|
|
880
955
|
// animation the operator sees a frozen screen and cannot tell work is in
|
|
@@ -887,18 +962,36 @@ export class LineEditor {
|
|
|
887
962
|
toolResult(result) {
|
|
888
963
|
this.stop();
|
|
889
964
|
const text = typeof result === 'string' ? result : safeJson(result);
|
|
890
|
-
const preview = text.slice(0, 200).split(NL).join(' ↵ ');
|
|
965
|
+
const preview = text.slice(0, 200).split(NL).join(' ↵ ') + (text.length > 200 ? ' …' : '');
|
|
891
966
|
this.printAbove(theme.toolResult(' → ' + preview));
|
|
892
967
|
}
|
|
893
968
|
assistant(msg) {
|
|
894
969
|
this.stop();
|
|
895
970
|
const rendered = renderMarkdown(msg);
|
|
971
|
+
// Show the used context size at the END of the answer too. During
|
|
972
|
+
// generation it lives in the status line, but ui.stop() clears that right
|
|
973
|
+
// before this call, so the operator lost the number exactly when the
|
|
974
|
+
// answer arrived. Read it fresh via onContextQuery and append it dimmed
|
|
975
|
+
// under the answer divider.
|
|
976
|
+
let ctxLine = '';
|
|
977
|
+
if (this.onContextQuery) {
|
|
978
|
+
try {
|
|
979
|
+
const n = this.onContextQuery();
|
|
980
|
+
const txt = formatTokenStatus(n, this.contextLimit);
|
|
981
|
+
if (txt)
|
|
982
|
+
ctxLine = NL + theme.dim(txt);
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
// never break the render
|
|
986
|
+
}
|
|
987
|
+
}
|
|
896
988
|
this.printAbove(NL +
|
|
897
989
|
theme.assistant(translate(this.locale)('editor.answer')) +
|
|
898
990
|
NL +
|
|
899
991
|
rendered +
|
|
900
992
|
NL +
|
|
901
|
-
theme.dim('─'.repeat(60))
|
|
993
|
+
theme.dim('─'.repeat(60)) +
|
|
994
|
+
ctxLine);
|
|
902
995
|
}
|
|
903
996
|
// Warning to the operator (e.g. the agent stopped suspiciously).
|
|
904
997
|
// Printed above the input line without overwriting it.
|