zames_pro 2.50.0 → 2.50.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/config-menu.js +18 -2
- package/dist/input.js +77 -20
- package/dist/self-review.js +2 -2
- package/dist/spinner.js +5 -10
- package/dist/theme.js +10 -1
- package/package.json +1 -1
package/dist/config-menu.js
CHANGED
|
@@ -9,11 +9,20 @@
|
|
|
9
9
|
// types): the caller reads and writes values through callbacks. This makes the
|
|
10
10
|
// menu easy to test and reuse.
|
|
11
11
|
import { theme } from './theme.js';
|
|
12
|
+
import { visLen } from './input.js';
|
|
12
13
|
const ESC = String.fromCharCode(27);
|
|
13
14
|
// Strip ANSI sequences to compute the visible length of a line.
|
|
14
15
|
function stripAnsi(s) {
|
|
15
16
|
return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
16
17
|
}
|
|
18
|
+
// Visible COLUMNS of a line (ANSI removed, wide chars counted as 2). Using the
|
|
19
|
+
// code-point count made the block height disagree with the real screen on lines
|
|
20
|
+
// with emoji/wide chars, so the erase moved to the wrong row and the config list
|
|
21
|
+
// duplicated. A row that fills the terminal exactly also triggers autowrap on
|
|
22
|
+
// some terminals, so we keep one column free here as the editor does.
|
|
23
|
+
function visCols(s) {
|
|
24
|
+
return visLen(stripAnsi(s));
|
|
25
|
+
}
|
|
17
26
|
function displayValue(field, value, t) {
|
|
18
27
|
if (value === undefined)
|
|
19
28
|
return t('cfg.menu.default');
|
|
@@ -90,10 +99,17 @@ export function runConfigMenu(opts) {
|
|
|
90
99
|
// Erase the previous block and print the new one. We count VISUAL lines
|
|
91
100
|
// (accounting for wrapping by terminal width), otherwise on narrow
|
|
92
101
|
// terminals you get "garbage" from unerased tails.
|
|
102
|
+
// Reserve the last column. A line whose visible width reaches exactly the
|
|
103
|
+
// terminal width hits the autowrap edge: depending on the terminal it
|
|
104
|
+
// either consumes two rows for the following `\n` or keeps a pending wrap,
|
|
105
|
+
// and in both cases a full-width divisor UNDERCOUNTS the block height — the
|
|
106
|
+
// erase then misses the tail and the config lines DUPLICATE on screen (the
|
|
107
|
+
// reported bug). Overcounting by one is a harmless blank row; undercounting
|
|
108
|
+
// is not, so we bias to the safe side here.
|
|
93
109
|
const cols = output.columns || 80;
|
|
110
|
+
const usable = Math.max(1, cols - 1);
|
|
94
111
|
const rowsOf = (s) => {
|
|
95
|
-
|
|
96
|
-
return Math.max(1, Math.ceil(len / Math.max(1, cols)));
|
|
112
|
+
return Math.max(1, Math.ceil(visCols(s) / usable));
|
|
97
113
|
};
|
|
98
114
|
if (lastRows > 0) {
|
|
99
115
|
output.write(ESC + '[' + lastRows + 'A');
|
package/dist/input.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { theme } from './theme.js';
|
|
1
|
+
import { theme, divider } from './theme.js';
|
|
2
2
|
import { renderMarkdown } from './markdown.js';
|
|
3
3
|
import { randomThinkingPhrase, stripEllipsis, DOTS, renderDots, } from './spinner.js';
|
|
4
4
|
import { translate } from './i18n.js';
|
|
@@ -157,7 +157,16 @@ export function charWidth(cp) {
|
|
|
157
157
|
return 1;
|
|
158
158
|
}
|
|
159
159
|
export function layoutInput(promptStr, buf, cursor, cols) {
|
|
160
|
-
|
|
160
|
+
// Reserve the LAST column. If a printed row reaches exactly `cols` columns,
|
|
161
|
+
// terminals with autowrap (Tabby, iTerm, Windows Terminal, xterm's default
|
|
162
|
+
// deferred wrap) either wrap the cursor to the next line or leave it in the
|
|
163
|
+
// last cell with a "pending wrap". In both cases the editor's row count
|
|
164
|
+
// disagrees with the real screen by one, so the cursor/erase arithmetic in
|
|
165
|
+
// `_renderInputOnly`/`_writeBlock` walks one row too far and the input line
|
|
166
|
+
// climbs. This was most visible in an EMPTY chat with a single long,
|
|
167
|
+
// space-less line (which fills the row exactly) and when moving the cursor
|
|
168
|
+
// with the arrow keys.
|
|
169
|
+
const width = Math.max(2, (Number(cols) || 80) - 1);
|
|
161
170
|
const promptW = visLen(promptStr);
|
|
162
171
|
const pad = ' '.repeat(promptW);
|
|
163
172
|
const chars = Array.from(String(buf));
|
|
@@ -306,9 +315,6 @@ export class LineEditor {
|
|
|
306
315
|
// True while the dot-animation timer is running. Lets sendPause() update
|
|
307
316
|
// only the base text (remaining seconds) without restarting the timer.
|
|
308
317
|
_animating;
|
|
309
|
-
// Whether the animated status shows the trailing "Esc — стоп" hint. Off
|
|
310
|
-
// while a tool runs (its process cannot be cancelled).
|
|
311
|
-
_showHint;
|
|
312
318
|
_dotPhase;
|
|
313
319
|
_thinkBase;
|
|
314
320
|
// Explicit lifecycle state of the send ('generating' | 'paused' |
|
|
@@ -324,6 +330,13 @@ export class LineEditor {
|
|
|
324
330
|
// keeping the status untouched removes that flicker. Cleared whenever the
|
|
325
331
|
// status genuinely changes so the cache can never go stale.
|
|
326
332
|
_lastStatusBlock;
|
|
333
|
+
// Rows occupied by the WHOLE block (status + input + hints) at the last
|
|
334
|
+
// write. When the block SHRINKS (the pause status disappears, the input
|
|
335
|
+
// unwraps) the erase leaves the cursor at the old block top; printing a
|
|
336
|
+
// shorter block then ends above the screen bottom, so the footer floated with
|
|
337
|
+
// blank rows below it. Padding the redraw with (prev - new) newlines pushes
|
|
338
|
+
// it back down to the bottom (see _padShrink).
|
|
339
|
+
_blockRows;
|
|
327
340
|
_wasRaw;
|
|
328
341
|
_onData;
|
|
329
342
|
// On SIGWINCH we redraw the whole block so the layout follows the new
|
|
@@ -406,11 +419,11 @@ export class LineEditor {
|
|
|
406
419
|
this._inPaste = false;
|
|
407
420
|
this._dotTimer = null;
|
|
408
421
|
this._animating = false;
|
|
409
|
-
this._showHint = true;
|
|
410
422
|
this._dotPhase = 0;
|
|
411
423
|
this._thinkBase = '';
|
|
412
424
|
this._sendState = '';
|
|
413
425
|
this._lastStatusBlock = '';
|
|
426
|
+
this._blockRows = 0;
|
|
414
427
|
this._wasRaw = false;
|
|
415
428
|
this._onData = (b) => this._handle(b);
|
|
416
429
|
this._resizeTimer = null;
|
|
@@ -665,8 +678,20 @@ export class LineEditor {
|
|
|
665
678
|
stdin.resume();
|
|
666
679
|
process.stdout.write(ESC + '[?2004h');
|
|
667
680
|
stdin.on('data', this._onData);
|
|
681
|
+
// `pause()` erased our block and handed the terminal to a foreign UI (the
|
|
682
|
+
// /config menu), which may leave the cursor ANYWHERE — after Esc the menu
|
|
683
|
+
// clears its block and parks the cursor at the TOP of the screen. Without
|
|
684
|
+
// re-pinning, the block would be redrawn up there and every later repaint
|
|
685
|
+
// would keep it glued to the top. Re-fill the viewport and forget the
|
|
686
|
+
// stale block height so the editor lands at the bottom again.
|
|
687
|
+
this._lastStatusBlock = '';
|
|
688
|
+
this._blockRows = 0;
|
|
689
|
+
// Start from a KNOWN row: the foreign UI may have parked the cursor
|
|
690
|
+
// anywhere (the /config menu leaves it near the top). From home, padding
|
|
691
|
+
// lands exactly on the last screen row.
|
|
692
|
+
process.stdout.write(ESC + '[H');
|
|
693
|
+
this._padToBottom();
|
|
668
694
|
this._writeBlock();
|
|
669
|
-
this._render();
|
|
670
695
|
}
|
|
671
696
|
// Lock input while a long operation runs (chat resume/open, /new,
|
|
672
697
|
// self-review). The line stays visible but text input and Enter are ignored,
|
|
@@ -865,6 +890,12 @@ export class LineEditor {
|
|
|
865
890
|
NL +
|
|
866
891
|
theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
|
|
867
892
|
}
|
|
893
|
+
// The input may have UNWRAPPED (fewer rows than before) — push the block
|
|
894
|
+
// back to the bottom BEFORE the new content is printed (the padding is
|
|
895
|
+
// plain newlines at the top of the block, which scroll the screen).
|
|
896
|
+
const blockRowsOnly = top + layOnly.rows.length + linesBelowOnly;
|
|
897
|
+
this._padShrink(blockRowsOnly, this._blockRows);
|
|
898
|
+
this._blockRows = blockRowsOnly;
|
|
868
899
|
process.stdout.write(outOnly);
|
|
869
900
|
const upOnly = layOnly.rows.length - 1 - layOnly.cursorRow + linesBelowOnly;
|
|
870
901
|
if (upOnly > 0)
|
|
@@ -899,13 +930,20 @@ export class LineEditor {
|
|
|
899
930
|
NL +
|
|
900
931
|
theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
|
|
901
932
|
}
|
|
933
|
+
const linesBelow = shown.length
|
|
934
|
+
? shown.length + (sugg.length > shown.length ? 1 : 0)
|
|
935
|
+
: 0;
|
|
936
|
+
// If the block SHRANK (the pause status went away, the input unwrapped),
|
|
937
|
+
// the erase left the cursor at the old block top; printing a shorter block
|
|
938
|
+
// would end above the screen bottom and the footer would float with blank
|
|
939
|
+
// rows below it. Padding with the difference pushes it back to the bottom.
|
|
940
|
+
const blockRows = top + lay.rows.length + linesBelow;
|
|
941
|
+
this._padShrink(blockRows, this._blockRows);
|
|
942
|
+
this._blockRows = blockRows;
|
|
902
943
|
process.stdout.write(out);
|
|
903
944
|
const lastRow = lay.rows.length - 1;
|
|
904
945
|
// Move the cursor up: first to the input line within lay, then further by
|
|
905
946
|
// the hint lines (if any) — the cursor must sit on the input.
|
|
906
|
-
const linesBelow = shown.length
|
|
907
|
-
? shown.length + (sugg.length > shown.length ? 1 : 0)
|
|
908
|
-
: 0;
|
|
909
947
|
const up = lastRow - lay.cursorRow + linesBelow;
|
|
910
948
|
if (up > 0)
|
|
911
949
|
process.stdout.write(ESC + '[' + up + 'A');
|
|
@@ -915,6 +953,19 @@ export class LineEditor {
|
|
|
915
953
|
this.rendered = true;
|
|
916
954
|
this.cursorRowFromTop = top + lay.cursorRow;
|
|
917
955
|
}
|
|
956
|
+
// Push the redrawn block back down to the bottom when it got SHORTER than
|
|
957
|
+
// the previous one. `_eraseBlock`/`_eraseInputOnly` put the cursor at the old
|
|
958
|
+
// block top, so emitting (prev - next) newlines scrolls the screen just
|
|
959
|
+
// enough for the new, shorter block to end at the screen bottom again. See
|
|
960
|
+
// _blockRows.
|
|
961
|
+
_padShrink(nextRows, prevRows) {
|
|
962
|
+
if (prevRows <= nextRows)
|
|
963
|
+
return;
|
|
964
|
+
const rows = process.stdout.rows || 24;
|
|
965
|
+
const pad = Math.min(prevRows - nextRows, Math.max(0, rows - 1));
|
|
966
|
+
if (pad > 0)
|
|
967
|
+
process.stdout.write(NL.repeat(pad));
|
|
968
|
+
}
|
|
918
969
|
// Public repaint hook: called when an external state that the status line
|
|
919
970
|
// reads (e.g. the toggle icons) changed, so it can be redrawn immediately.
|
|
920
971
|
refreshStatus() {
|
|
@@ -933,6 +984,10 @@ export class LineEditor {
|
|
|
933
984
|
process.stdout.write(ESC + '[2J' + ESC + '[H');
|
|
934
985
|
this.rendered = false;
|
|
935
986
|
this._lastStatusBlock = '';
|
|
987
|
+
// We are about to re-pin the block to the bottom ourselves, so forget the
|
|
988
|
+
// previous block height — otherwise the shrink padding would add extra
|
|
989
|
+
// rows on top of the padding below.
|
|
990
|
+
this._blockRows = 0;
|
|
936
991
|
// Re-pin the block to the bottom after the screen was cleared.
|
|
937
992
|
this._padToBottom();
|
|
938
993
|
this._writeBlock();
|
|
@@ -973,6 +1028,13 @@ export class LineEditor {
|
|
|
973
1028
|
NL +
|
|
974
1029
|
theme.dim(' ' + translate(this.locale)('editor.more', { n: hidden }));
|
|
975
1030
|
}
|
|
1031
|
+
// A buffer change can UNWRAP the input (backspace on a wrapped line), so
|
|
1032
|
+
// the block can get shorter here too — push it back to the bottom BEFORE
|
|
1033
|
+
// the new content is printed (the padding is plain newlines at the top of
|
|
1034
|
+
// the block, which scroll the screen).
|
|
1035
|
+
const blockRows = this._statusTop + lay.rows.length + linesBelow;
|
|
1036
|
+
this._padShrink(blockRows, this._blockRows);
|
|
1037
|
+
this._blockRows = blockRows;
|
|
976
1038
|
process.stdout.write(out);
|
|
977
1039
|
const up = lay.rows.length - 1 - lay.cursorRow + linesBelow;
|
|
978
1040
|
if (up > 0)
|
|
@@ -1005,10 +1067,10 @@ export class LineEditor {
|
|
|
1005
1067
|
_dots(n) {
|
|
1006
1068
|
return renderDots(n);
|
|
1007
1069
|
}
|
|
1008
|
-
// Status-line hint ("Esc — stop"), localized.
|
|
1070
|
+
// Status-line hint ("Esc — stop"), localized. Shown for every animated
|
|
1071
|
+
// state: Esc really aborts both a generation and a running tool (the Bash
|
|
1072
|
+
// child is killed via AbortSignal), so the promise is accurate.
|
|
1009
1073
|
_hint() {
|
|
1010
|
-
if (!this._showHint)
|
|
1011
|
-
return '';
|
|
1012
1074
|
return theme.dim(' · ' + translate(this.locale)('spinner.hint'));
|
|
1013
1075
|
}
|
|
1014
1076
|
// The lifecycle phase prefix for the status line, colored by meaning:
|
|
@@ -1044,13 +1106,8 @@ export class LineEditor {
|
|
|
1044
1106
|
// Shared by the thinking spinner and the send-pause indicator, so the pause
|
|
1045
1107
|
// is animated too (previously the pause was a static console line and the
|
|
1046
1108
|
// dots stayed frozen).
|
|
1047
|
-
|
|
1048
|
-
// `showHint` (default true) controls the trailing "· Esc — стоп". While a
|
|
1049
|
-
// TOOL runs we hide it: the tool's child process cannot be killed, so
|
|
1050
|
-
// promising "Esc to stop" would be misleading.
|
|
1051
|
-
_startAnimated(baseText, showHint = true) {
|
|
1109
|
+
_startAnimated(baseText) {
|
|
1052
1110
|
this._thinkBase = theme.brown(stripEllipsis(baseText));
|
|
1053
|
-
this._showHint = showHint;
|
|
1054
1111
|
this._dotPhase = 0;
|
|
1055
1112
|
this.setStatus(this._thinkBase + this._dots(0) + this._hint());
|
|
1056
1113
|
this._stopDots();
|
|
@@ -1128,7 +1185,7 @@ export class LineEditor {
|
|
|
1128
1185
|
NL +
|
|
1129
1186
|
rendered +
|
|
1130
1187
|
NL +
|
|
1131
|
-
theme.dim(
|
|
1188
|
+
theme.dim(divider()));
|
|
1132
1189
|
}
|
|
1133
1190
|
// Warning to the operator (e.g. the agent stopped suspiciously).
|
|
1134
1191
|
// Printed above the input line without overwriting it.
|
package/dist/self-review.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
|
-
import { theme } from './theme.js';
|
|
3
|
+
import { theme, divider } from './theme.js';
|
|
4
4
|
import { translate, DEFAULT_LOCALE } from './i18n.js';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { ZAMES_HOME } from './config.js';
|
|
@@ -116,7 +116,7 @@ export async function selfReview({ browser, focus, transcript, locale, }) {
|
|
|
116
116
|
await fs.writeFile(reportPath, finalMessage || t('self.report_empty'), 'utf-8');
|
|
117
117
|
// Compute what changed
|
|
118
118
|
const changed = await diffFiles(SRC_DIR, snapDir);
|
|
119
|
-
console.log(theme.system(
|
|
119
|
+
console.log(theme.system(divider()));
|
|
120
120
|
console.log(theme.system(t('self.report_path', { v: reportPath })));
|
|
121
121
|
console.log(theme.system(t('self.snapshot_path', { v: snapDir })));
|
|
122
122
|
if (changed.length) {
|
package/dist/spinner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
|
-
import { theme } from './theme.js';
|
|
2
|
+
import { theme, divider } from './theme.js';
|
|
3
3
|
import { renderMarkdown } from './markdown.js';
|
|
4
4
|
import { translate } from './i18n.js';
|
|
5
5
|
// Phrases for the "agent is working" spinner. Chosen in random order.
|
|
@@ -59,14 +59,9 @@ export function createSpinner(locale = 'ru') {
|
|
|
59
59
|
// "running" dot sequence. Shared by the thinking spinner and the send-pause
|
|
60
60
|
// indicator, so the pause is animated too (before, it was a static line and
|
|
61
61
|
// the dots did not move — the operator saw a frozen spinner).
|
|
62
|
-
|
|
63
|
-
// `showHint` (default true) controls the trailing "· Esc — стоп". While a
|
|
64
|
-
// tool runs we hide it: its process cannot be cancelled, so promising a stop
|
|
65
|
-
// would be misleading.
|
|
66
|
-
const startAnimated = (baseText, showHint = true) => {
|
|
62
|
+
const startAnimated = (baseText) => {
|
|
67
63
|
animBase = theme.brown(stripEllipsis(baseText));
|
|
68
|
-
|
|
69
|
-
start(animBase + renderDots(0) + hint);
|
|
64
|
+
start(animBase + renderDots(0) + HINT);
|
|
70
65
|
dotPhase = 0;
|
|
71
66
|
if (dotTimer)
|
|
72
67
|
clearInterval(dotTimer);
|
|
@@ -74,7 +69,7 @@ export function createSpinner(locale = 'ru') {
|
|
|
74
69
|
if (!spinner)
|
|
75
70
|
return;
|
|
76
71
|
dotPhase = (dotPhase + 1) % DOTS.length;
|
|
77
|
-
spinner.text = animBase + renderDots(dotPhase) +
|
|
72
|
+
spinner.text = animBase + renderDots(dotPhase) + HINT;
|
|
78
73
|
}, 400);
|
|
79
74
|
animating = true;
|
|
80
75
|
if (dotTimer.unref)
|
|
@@ -158,7 +153,7 @@ export function createSpinner(locale = 'ru') {
|
|
|
158
153
|
// (which is highlighted by the prompt with a golden arrow).
|
|
159
154
|
console.log(NL + theme.assistant(translate(locale)('editor.answer')) + NL);
|
|
160
155
|
console.log(rendered);
|
|
161
|
-
console.log(theme.dim(
|
|
156
|
+
console.log(theme.dim(divider()) + NL);
|
|
162
157
|
},
|
|
163
158
|
warning: (msg) => {
|
|
164
159
|
stop();
|
package/dist/theme.js
CHANGED
|
@@ -29,7 +29,16 @@ export const theme = {
|
|
|
29
29
|
success: chalk.hex('#a9c08c'), // soft sage
|
|
30
30
|
brown: chalk.hex('#a1723f'), // brown (status/spinner)
|
|
31
31
|
toggleOn: chalk.hex('#6fd0b0'), // teal-green — a toggle is ON
|
|
32
|
-
toggleOff: chalk.hex('#4a4f5a'), // dim gray — a toggle is OFF
|
|
33
32
|
bold: chalk.bold,
|
|
34
33
|
};
|
|
34
|
+
// Horizontal rule that separates the model's answer from the next block. It
|
|
35
|
+
// spans the terminal width (capped at `max`) so it does not look stubby on a
|
|
36
|
+
// wide terminal. Falls back to 60 columns when the width is unknown (non-TTY).
|
|
37
|
+
// One column is kept free, like everywhere else in the status/input layout, so
|
|
38
|
+
// a full-width rule cannot trigger autowrap.
|
|
39
|
+
export function divider(max = 100) {
|
|
40
|
+
const cols = process.stdout.columns || 0;
|
|
41
|
+
const width = Math.max(20, Math.min(max, cols ? cols - 1 : 60));
|
|
42
|
+
return '─'.repeat(width);
|
|
43
|
+
}
|
|
35
44
|
export default theme;
|