zames_pro 2.65.0 → 2.65.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/CHANGELOG.md +22 -0
- package/README.md +3 -9
- package/dist/browser.js +4 -0
- package/dist/config-menu.js +19 -20
- package/dist/input/layout.js +22 -0
- package/dist/input.js +53 -4
- package/dist/spinner.js +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.65.1] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Длинные строки preview больше не создают горизонтальный скролл в
|
|
15
|
+
терминале: `toolCall`/`toolResult` (LineEditor и спиннер) теперь режут
|
|
16
|
+
превью по ВИДИМОЙ ШИРИНЕ терминала (`truncateToWidth`), а не по числу
|
|
17
|
+
символов. Раньше длинная команда/результат печатались шире экрана.
|
|
18
|
+
- Ctrl+Delete / Ctrl+Backspace (и Alt+Delete / Alt+Backspace) теперь удаляют
|
|
19
|
+
слово в строке ввода. Раньше эти escape-последовательности попадали в
|
|
20
|
+
общий пропуск клавиш и ничего не делали. Поддержаны варианты: `ESC[3;5~`
|
|
21
|
+
(xterm Ctrl+Delete), `ESC[3;3~` (Alt+Delete), `ESC DEL`/`ESC 8`
|
|
22
|
+
(Alt/Ctrl+Backspace) и kitty-протокол `ESC[127;5u` / `ESC[127;3u`.
|
|
23
|
+
Новый метод `_deleteWordRight()` + тест `test/editor-word-delete.test.ts`.
|
|
24
|
+
- Меню `/config` теперь держит ВЫБРАННЫЙ пункт на экране: окно строится
|
|
25
|
+
«назад» от курсора с учётом строк, которые занимают заголовки групп
|
|
26
|
+
(раньше индексный `scrollTop` мог вытолкнуть выбранную строку вниз за
|
|
27
|
+
пределы окна).
|
|
28
|
+
- `/new` сбрасывает счётчик контекста: `_lastTokenUsage` копится НА ЧАТ,
|
|
29
|
+
и без сброса в пустом чате оставался старый `ctx: 302k · 30%` до первой
|
|
30
|
+
отправки. Тест в `test/newchat-verify.test.ts`.
|
|
31
|
+
|
|
10
32
|
## [2.65.0] - 2026-10-07
|
|
11
33
|
|
|
12
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -33,15 +33,9 @@ zames does not call the model API. It launches a headless Chromium with a
|
|
|
33
33
|
persistent profile, signs in to `chat.deepseek.com` like a human, types the task
|
|
34
34
|
into the chat box, and reads the answer back.
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Z -->|"system prompt + task"| D["chat.deepseek.com"]
|
|
40
|
-
D -->|"model answers (raw SSE)"| Z
|
|
41
|
-
Z -->|"parse tool-call"| Z
|
|
42
|
-
Z -->|"run tool: Read/Edit/Bash/…"| Z
|
|
43
|
-
Z -->|"answer"| U
|
|
44
|
-
```
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="how zames works" width="860">
|
|
38
|
+
</p>
|
|
45
39
|
|
|
46
40
|
In text: you type a task → zames sends the system prompt plus the task into
|
|
47
41
|
the DeepSeek web chat → the model answers → zames parses the tool call, runs
|
package/dist/browser.js
CHANGED
|
@@ -1230,6 +1230,10 @@ export class DeepSeekBrowser {
|
|
|
1230
1230
|
// STALE old id for the fresh chat. Clear it — a fresh chat has no id until
|
|
1231
1231
|
// the first message creates one.
|
|
1232
1232
|
this._netChatId = null;
|
|
1233
|
+
// The token counter is cumulative FOR A CHAT. A fresh chat has no messages,
|
|
1234
|
+
// so carrying the old value over left a stale "ctx: 302k · 30%" on the
|
|
1235
|
+
// status line until the first send. Reset it (and the cached status).
|
|
1236
|
+
this._lastTokenUsage = null;
|
|
1233
1237
|
}
|
|
1234
1238
|
async _findVisible(selectors, timeout = 1000) {
|
|
1235
1239
|
for (const sel of selectors) {
|
package/dist/config-menu.js
CHANGED
|
@@ -53,11 +53,6 @@ export function runConfigMenu(opts) {
|
|
|
53
53
|
}
|
|
54
54
|
// cursor indexes the VISIBLE (filtered) list, not the full `fields`.
|
|
55
55
|
let cursor = 0;
|
|
56
|
-
// Index of the first VISIBLE field drawn. The menu is WINDOWED so the whole
|
|
57
|
-
// block always fits the terminal height; otherwise a block taller than the
|
|
58
|
-
// screen scrolled the top rows off and the `ESC[lastRows A` cursor restore
|
|
59
|
-
// landed on the wrong line, so arrow-key navigation did not scroll the list.
|
|
60
|
-
let scrollTop = 0;
|
|
61
56
|
let editing = false;
|
|
62
57
|
let editBuf = '';
|
|
63
58
|
// Reverse/forward filter over the list (/ to enter, typing narrows it).
|
|
@@ -141,21 +136,25 @@ export function runConfigMenu(opts) {
|
|
|
141
136
|
// Bias one row to the SAFE side (a spare blank row is harmless, a block
|
|
142
137
|
// that overflows scrolls the top away and breaks the cursor restore).
|
|
143
138
|
const maxItemRows = Math.max(3, termRows - 2 - tail.length);
|
|
144
|
-
// WINDOW over the fields
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
139
|
+
// WINDOW over the fields. We walk BACK from the cursor, counting the rows
|
|
140
|
+
// the window would take (a group header consumes a row too), until adding
|
|
141
|
+
// one more field would overflow. This guarantees the SELECTED item is always
|
|
142
|
+
// on screen — the old index-based scrollTop could push it out when many
|
|
143
|
+
// group headers sat between the window top and the cursor.
|
|
144
|
+
const groupStart = (i) => i === 0 || vis[i].groupKey !== vis[i - 1].groupKey;
|
|
145
|
+
// Rows needed to draw the window [from, cursor] (inclusive).
|
|
146
|
+
const rowsFor = (from) => {
|
|
147
|
+
let rows = 0;
|
|
148
|
+
for (let i = from; i <= cursor; i++) {
|
|
149
|
+
if (i === from || groupStart(i))
|
|
150
|
+
rows++;
|
|
151
|
+
rows++;
|
|
152
|
+
}
|
|
153
|
+
return rows;
|
|
154
|
+
};
|
|
155
|
+
let start = vis.length ? Math.max(0, Math.min(cursor, vis.length - 1)) : 0;
|
|
156
|
+
while (start > 0 && rowsFor(start - 1) <= maxItemRows)
|
|
157
|
+
start--;
|
|
159
158
|
const lines = [];
|
|
160
159
|
// Title with a position counter ("Settings — 12/49") so the operator knows
|
|
161
160
|
// how far down a long list they are; a filter narrows both numbers.
|
package/dist/input/layout.js
CHANGED
|
@@ -150,6 +150,28 @@ export function charWidth(cp) {
|
|
|
150
150
|
}
|
|
151
151
|
return 1;
|
|
152
152
|
}
|
|
153
|
+
// Truncate a PLAIN (no-ANSI) string to at most `maxCols` visible columns,
|
|
154
|
+
// appending an ellipsis when it was cut. Used for one-line previews (tool call
|
|
155
|
+
// args, tool results) so a long argument cannot print a line wider than the
|
|
156
|
+
// terminal — which made some terminals show a horizontal scrollbar.
|
|
157
|
+
export function truncateToWidth(s, maxCols) {
|
|
158
|
+
const width = Math.max(1, Math.floor(maxCols));
|
|
159
|
+
const text = String(s ?? '');
|
|
160
|
+
if (visLen(text) <= width)
|
|
161
|
+
return text;
|
|
162
|
+
// Reserve one column for the ellipsis.
|
|
163
|
+
const budget = Math.max(0, width - 1);
|
|
164
|
+
let used = 0;
|
|
165
|
+
let out = '';
|
|
166
|
+
for (const ch of text) {
|
|
167
|
+
const w = charWidth(ch.codePointAt(0));
|
|
168
|
+
if (used + w > budget)
|
|
169
|
+
break;
|
|
170
|
+
out += ch;
|
|
171
|
+
used += w;
|
|
172
|
+
}
|
|
173
|
+
return out + '…';
|
|
174
|
+
}
|
|
153
175
|
export function layoutInput(promptStr, buf, cursor, cols) {
|
|
154
176
|
// Reserve the LAST column. If a printed row reaches exactly `cols` columns,
|
|
155
177
|
// terminals with autowrap (Tabby, iTerm, Windows Terminal, xterm's default
|
package/dist/input.js
CHANGED
|
@@ -3,12 +3,12 @@ import { renderMarkdown } from './markdown.js';
|
|
|
3
3
|
import { randomThinkingPhrase, stripEllipsis, DOTS, renderDots, } from './spinner.js';
|
|
4
4
|
import { translate } from './i18n.js';
|
|
5
5
|
import { AttachmentStore, parseImagePaste, looksLikeFilePath, } from './attachments.js';
|
|
6
|
-
import { NL, CR, ESC, PASTE_START, PASTE_END, SUGGEST_PAGE, isSubsequence, pasteReplacement, expandPastes, safeJson, visRows, visLen, layoutInput, formatTokenStatus, tokenStatusLevel, CONTEXT_LIMIT, } from './input/layout.js';
|
|
6
|
+
import { NL, CR, ESC, PASTE_START, PASTE_END, SUGGEST_PAGE, isSubsequence, pasteReplacement, expandPastes, safeJson, visRows, visLen, layoutInput, formatTokenStatus, tokenStatusLevel, truncateToWidth, CONTEXT_LIMIT, } from './input/layout.js';
|
|
7
7
|
// The pure layout/formatting helpers (visual rows, cursor position, visible
|
|
8
8
|
// width, paste markers, token formatting) live in ./input/layout.js. They are
|
|
9
9
|
// re-exported here so every existing import from './input.js' keeps working,
|
|
10
10
|
// and so tests can import them from either module (C3 step 3).
|
|
11
|
-
export { PASTE_MIN_LINES, SUGGEST_PAGE, isSubsequence, countPasteLines, formatPasteMarker, pasteReplacement, expandPastes, visRows, visLen, charWidth, layoutInput, tokenStatusLevel, formatTokenStatus, formatCompactTokens, CONTEXT_LIMIT, CONTEXT_YELLOW_PCT, CONTEXT_RED_PCT, } from './input/layout.js';
|
|
11
|
+
export { PASTE_MIN_LINES, SUGGEST_PAGE, isSubsequence, countPasteLines, formatPasteMarker, pasteReplacement, expandPastes, visRows, visLen, charWidth, layoutInput, tokenStatusLevel, formatTokenStatus, formatCompactTokens, truncateToWidth, CONTEXT_LIMIT, CONTEXT_YELLOW_PCT, CONTEXT_RED_PCT, } from './input/layout.js';
|
|
12
12
|
// A permanent input line at the bottom of the terminal + a status/output area above it.
|
|
13
13
|
//
|
|
14
14
|
// Why a custom editor:
|
|
@@ -1044,7 +1044,11 @@ export class LineEditor {
|
|
|
1044
1044
|
}
|
|
1045
1045
|
toolCall(name, args) {
|
|
1046
1046
|
const full = safeJson(args);
|
|
1047
|
-
|
|
1047
|
+
// Clip by VISIBLE WIDTH, not character count: a long arg line used to print
|
|
1048
|
+
// wider than the terminal and some terminals then showed a horizontal
|
|
1049
|
+
// scrollbar. Keep one column free (autowrap safety), same as the status.
|
|
1050
|
+
const maxW = Math.max(20, (process.stdout.columns || 80) - 1);
|
|
1051
|
+
const preview = truncateToWidth(full, maxW);
|
|
1048
1052
|
this.printAbove(theme.tool('🔧 ' + name) + ' ' + theme.dim(preview));
|
|
1049
1053
|
// A tool may run for a long time (Bash, npm test, MCP). Without an active
|
|
1050
1054
|
// animation the operator sees a frozen screen and cannot tell work is in
|
|
@@ -1062,7 +1066,8 @@ export class LineEditor {
|
|
|
1062
1066
|
toolResult(result) {
|
|
1063
1067
|
this.stop();
|
|
1064
1068
|
const text = typeof result === 'string' ? result : safeJson(result);
|
|
1065
|
-
const
|
|
1069
|
+
const maxW = Math.max(20, (process.stdout.columns || 80) - 1);
|
|
1070
|
+
const preview = truncateToWidth(text.split(NL).join(' ↵ '), maxW);
|
|
1066
1071
|
this.printAbove(theme.toolResult(' → ' + preview));
|
|
1067
1072
|
}
|
|
1068
1073
|
assistant(msg) {
|
|
@@ -1373,6 +1378,19 @@ export class LineEditor {
|
|
|
1373
1378
|
this.buf = chars.join('');
|
|
1374
1379
|
this.cursor = i;
|
|
1375
1380
|
}
|
|
1381
|
+
// Delete the word on the RIGHT (Ctrl+Delete / Alt+Delete): skip spaces after
|
|
1382
|
+
// the cursor, then delete up to the end of the next word. Mirror of
|
|
1383
|
+
// _deleteWordLeft(), which handles the left side.
|
|
1384
|
+
_deleteWordRight() {
|
|
1385
|
+
const chars = Array.from(this.buf);
|
|
1386
|
+
let i = this.cursor;
|
|
1387
|
+
while (i < chars.length && /\s/.test(chars[i]))
|
|
1388
|
+
i++;
|
|
1389
|
+
while (i < chars.length && !/\s/.test(chars[i]))
|
|
1390
|
+
i++;
|
|
1391
|
+
chars.splice(this.cursor, i - this.cursor);
|
|
1392
|
+
this.buf = chars.join('');
|
|
1393
|
+
}
|
|
1376
1394
|
// ---------- history ----------
|
|
1377
1395
|
_historyUp() {
|
|
1378
1396
|
if (!this.history.length)
|
|
@@ -1731,6 +1749,37 @@ export class LineEditor {
|
|
|
1731
1749
|
this._renderInputOnly();
|
|
1732
1750
|
continue;
|
|
1733
1751
|
}
|
|
1752
|
+
// Ctrl+Delete / Alt+Delete — delete the word on the RIGHT. Different
|
|
1753
|
+
// terminals encode it differently: xterm sends ESC[3;5~, some send
|
|
1754
|
+
// ESC[3;3~ (Alt). Without this the sequence fell through to the
|
|
1755
|
+
// generic skip and nothing happened.
|
|
1756
|
+
if (s.startsWith('[3;5~') || s.startsWith('[3;3~')) {
|
|
1757
|
+
this._pushUndo();
|
|
1758
|
+
this._deleteWordRight();
|
|
1759
|
+
s = s.slice(5);
|
|
1760
|
+
this._renderInputOnly();
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
// Alt+Backspace / Ctrl+Backspace (ESC followed by DEL) — delete the
|
|
1764
|
+
// word on the LEFT. On many terminals Ctrl+Backspace is not a distinct
|
|
1765
|
+
// byte and arrives exactly like this.
|
|
1766
|
+
if (s.startsWith('\x7f') || s.startsWith('\x08')) {
|
|
1767
|
+
this._pushUndo();
|
|
1768
|
+
this._deleteWordLeft();
|
|
1769
|
+
s = s.slice(1);
|
|
1770
|
+
this._renderInputOnly();
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
// Kitty / foot / WezTerm keyboard protocol: Ctrl+Backspace is
|
|
1774
|
+
// CSI 127;5u (Alt = 127;3u). Without this the sequence fell through
|
|
1775
|
+
// to the generic skip and nothing happened.
|
|
1776
|
+
if (s.startsWith('[127;5u') || s.startsWith('[127;3u')) {
|
|
1777
|
+
this._pushUndo();
|
|
1778
|
+
this._deleteWordLeft();
|
|
1779
|
+
s = s.slice(7);
|
|
1780
|
+
this._renderInputOnly();
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1734
1783
|
if (s.startsWith('[D')) {
|
|
1735
1784
|
this._left();
|
|
1736
1785
|
s = s.slice(2);
|
package/dist/spinner.js
CHANGED
|
@@ -2,6 +2,7 @@ import ora from 'ora';
|
|
|
2
2
|
import { theme, divider } from './theme.js';
|
|
3
3
|
import { renderMarkdown } from './markdown.js';
|
|
4
4
|
import { translate } from './i18n.js';
|
|
5
|
+
import { truncateToWidth } from './input/layout.js';
|
|
5
6
|
// Phrases for the "agent is working" spinner. Chosen in random order.
|
|
6
7
|
// The text comes from i18n via the key 'spinner.phrases' (strings separated by "|").
|
|
7
8
|
export function randomThinkingPhrase(locale = 'ru') {
|
|
@@ -170,7 +171,8 @@ export function createSpinner(locale = 'ru') {
|
|
|
170
171
|
},
|
|
171
172
|
toolCall: (name, args) => {
|
|
172
173
|
stop();
|
|
173
|
-
const
|
|
174
|
+
const maxW = Math.max(20, (process.stdout.columns || 80) - 1);
|
|
175
|
+
const preview = truncateToWidth(JSON.stringify(args), maxW);
|
|
174
176
|
console.log(theme.tool('🔧 ' + name), theme.dim(preview));
|
|
175
177
|
// Keep an animated status while the tool runs (Bash/npm/MCP can take a
|
|
176
178
|
// long time): the operator must see that work is in progress.
|
|
@@ -181,10 +183,8 @@ export function createSpinner(locale = 'ru') {
|
|
|
181
183
|
toolResult: (result) => {
|
|
182
184
|
stop();
|
|
183
185
|
const text = typeof result === 'string' ? result : JSON.stringify(result);
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
.split(String.fromCharCode(10))
|
|
187
|
-
.join(' ↵ ');
|
|
186
|
+
const maxW = Math.max(20, (process.stdout.columns || 80) - 1);
|
|
187
|
+
const preview = truncateToWidth(text.split(String.fromCharCode(10)).join(' ↵ '), maxW);
|
|
188
188
|
console.log(theme.toolResult(' → ' + preview + String.fromCharCode(10)));
|
|
189
189
|
},
|
|
190
190
|
assistant: (msg) => {
|
package/package.json
CHANGED