zames_pro 2.96.5 โ 2.96.7
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 +20 -0
- package/dist/goal.js +28 -0
- package/dist/input/layout.js +18 -1
- package/dist/input.js +5 -1
- package/dist/markdown.js +25 -5
- package/dist/tui/background.js +7 -1
- package/dist/tui/edit.js +29 -5
- package/dist/tui/keys.js +5 -1
- package/dist/web.js +8 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.96.7] - 2026-11-06
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **tui:** a raw DEL byte is not inserted into the input line
|
|
15
|
+
- **tui:** Down after editing a recalled entry moves to the next entry
|
|
16
|
+
- **tui:** Up/Down keep the visual column on wide-glyph wrapped lines
|
|
17
|
+
- **goal:** a quoted verdict example no longer wins over the real verdict
|
|
18
|
+
- **input:** hold only a partial paste marker, not a lone ESC
|
|
19
|
+
|
|
20
|
+
## [2.96.6] - 2026-11-06
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **input:** drop a dangling ZWJ when a truncation cuts an emoji sequence
|
|
25
|
+
- **input:** count a ZWJ emoji sequence as one glyph in the layout
|
|
26
|
+
- **tui:** ZAMES_NO_BG=0 no longer disables the themed background
|
|
27
|
+
- **markdown:** an unknown code-fence language falls back to plain text
|
|
28
|
+
- **web:** decode an escaped HTML entity only once
|
|
29
|
+
|
|
10
30
|
## [2.96.5] - 2026-11-06
|
|
11
31
|
|
|
12
32
|
### Fixed
|
package/dist/goal.js
CHANGED
|
@@ -151,7 +151,31 @@ export function parseGoalVerdict(text) {
|
|
|
151
151
|
const label = /VERDICT\s*[*_`]*\s*:\s*[*_`]*\s*(IMPOSSIBLE|NOT[_ -]?MET|MET)(?![\w-])/gi;
|
|
152
152
|
let m;
|
|
153
153
|
let status = null;
|
|
154
|
+
// True when the backtick run ending right BEFORE `at` is the CLOSING fence
|
|
155
|
+
// of a block that started at the beginning of a line.
|
|
156
|
+
const isFenceClose = (src, at) => {
|
|
157
|
+
let i = at - 1;
|
|
158
|
+
while (i >= 0 && src.charAt(i) === '`')
|
|
159
|
+
i--;
|
|
160
|
+
const fenceEnd = at;
|
|
161
|
+
const fenceStart = i + 1;
|
|
162
|
+
if (fenceEnd - fenceStart < 1)
|
|
163
|
+
return false;
|
|
164
|
+
// The opening run must be at the START of a line (or of the text).
|
|
165
|
+
const lineStart = src.lastIndexOf(String.fromCharCode(10), fenceStart - 1) + 1;
|
|
166
|
+
return src.slice(lineStart, fenceStart).trim() === '';
|
|
167
|
+
};
|
|
154
168
|
while ((m = label.exec(t)) !== null) {
|
|
169
|
+
// A QUOTED or BACKTICKED label is an EXAMPLE the evaluator is explaining,
|
|
170
|
+
// not its verdict: `A verdict such as "VERDICT: MET" would be wrong here`
|
|
171
|
+
// followed by the real `VERDICT: NOT_MET` used to clear the goal as DONE.
|
|
172
|
+
// A quote/backtick directly BEFORE the label marks an example โ unless it
|
|
173
|
+
// is the CLOSING fence of a fenced verdict (```` ```Verdict: MET``` ````),
|
|
174
|
+
// which is the accepted format and must parse.
|
|
175
|
+
const before = t.charAt(m.index - 1);
|
|
176
|
+
if (before === '"' || (before === '`' && !isFenceClose(t, m.index))) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
155
179
|
// Skip whitespace/backticks between the value and whatever follows.
|
|
156
180
|
let k = m.index + m[0].length;
|
|
157
181
|
while (k < t.length && /[\s*_`]/.test(t.charAt(k)))
|
|
@@ -159,6 +183,10 @@ export function parseGoalVerdict(text) {
|
|
|
159
183
|
const after = t.charAt(k);
|
|
160
184
|
if (after === '|' || after === '/')
|
|
161
185
|
continue;
|
|
186
|
+
// A CLOSING quote/backtick right after the value is the other end of an
|
|
187
|
+
// example (`VERDICT: MET` in prose).
|
|
188
|
+
if (after === '"' || after === '`')
|
|
189
|
+
continue;
|
|
162
190
|
const raw = m[1].toUpperCase();
|
|
163
191
|
status =
|
|
164
192
|
raw === 'MET' ? 'met' : raw === 'IMPOSSIBLE' ? 'impossible' : 'not_met';
|
package/dist/input/layout.js
CHANGED
|
@@ -167,6 +167,15 @@ export function visRows(s, cols) {
|
|
|
167
167
|
// zero-width joiner/space/variation selectors count as 0.
|
|
168
168
|
export function visLen(s) {
|
|
169
169
|
s = String(s);
|
|
170
|
+
// An emoji SEQUENCE renders as ONE glyph but occupies several code
|
|
171
|
+
// points: ZWJ-joined, a skin-tone modifier, a keycap. Counting each
|
|
172
|
+
// point separately measured a family as 6 columns instead of 2, so the
|
|
173
|
+
// layout reserved extra rows and the cursor column drifted. The sequence
|
|
174
|
+
// is replaced by ONE placeholder cell before measuring.
|
|
175
|
+
s = s.replace(/(?:\p{Extended_Pictographic}|\p{Emoji_Modifier}|\u200d|\ufe0f)+/gu,
|
|
176
|
+
// The placeholder is a WIDE glyph (2 columns), the width an emoji renders
|
|
177
|
+
// at: a single `X` would under-count it.
|
|
178
|
+
'๐');
|
|
170
179
|
let n = 0;
|
|
171
180
|
let i = 0;
|
|
172
181
|
while (i < s.length) {
|
|
@@ -399,6 +408,11 @@ export function truncateToWidth(s, maxCols) {
|
|
|
399
408
|
used += w;
|
|
400
409
|
i += ch.length;
|
|
401
410
|
}
|
|
411
|
+
// A cut inside a ZWJ emoji sequence leaves a DANGLING joiner: the terminal
|
|
412
|
+
// glues the following `โฆ` to the emoji, so the marker becomes invisible and
|
|
413
|
+
// the visible line is wider than requested. Drop the dangling tail (the
|
|
414
|
+
// whole incomplete grapheme) before closing the style.
|
|
415
|
+
out = out.replace(/\u200d+$/, '');
|
|
402
416
|
// The visible text was cut BEFORE any trailing RESET, so the ellipsis and
|
|
403
417
|
// everything printed after it on the row would inherit the last colour.
|
|
404
418
|
// Close any style the kept prefix opened (the text may still be plain, in
|
|
@@ -645,7 +659,10 @@ export function splitPasteTail(s) {
|
|
|
645
659
|
// The longest marker prefix to consider is one char short of the marker.
|
|
646
660
|
const markers = [PASTE_START, PASTE_END];
|
|
647
661
|
const maxHold = Math.max(...markers.map((m) => m.length)) - 1;
|
|
648
|
-
|
|
662
|
+
// Hold a PARTIAL marker up to the full marker length: for a 1-char chunk
|
|
663
|
+
// (`str.length - 1` = 0) nothing was held, so a read ending on a lone ESC
|
|
664
|
+
// leaked it into the buffer instead of waiting for the rest of `ESC[200~`.
|
|
665
|
+
const upto = Math.min(maxHold, str.length);
|
|
649
666
|
for (let len = upto; len >= 1; len--) {
|
|
650
667
|
const tail = str.slice(str.length - len);
|
|
651
668
|
if (!tail.startsWith(ESC))
|
package/dist/input.js
CHANGED
|
@@ -1640,7 +1640,11 @@ export class LineEditor {
|
|
|
1640
1640
|
// to _pasteBuf (it is not parsed by this loop), so a held tail would be
|
|
1641
1641
|
// re-attached to `s` and inserted as text BEFORE the body โ the tail
|
|
1642
1642
|
// (`1~`) leaked into the input line and was not part of the pasted text.
|
|
1643
|
-
|
|
1643
|
+
// The bare-ESC disambiguation below must still see a chunk that IS just an
|
|
1644
|
+
// ESC: holding it as a partial paste marker would swallow the Escape key
|
|
1645
|
+
// (and a following arrow tail would then be read as literal text). Only a
|
|
1646
|
+
// PARTIAL marker of 2+ chars is held here; the lone ESC keeps its own path.
|
|
1647
|
+
const held = this._inPaste || s === ESC
|
|
1644
1648
|
? { head: s, tail: null }
|
|
1645
1649
|
: splitPasteTail(s);
|
|
1646
1650
|
if (held.tail) {
|
package/dist/markdown.js
CHANGED
|
@@ -98,11 +98,31 @@ export function renderMarkdown(text) {
|
|
|
98
98
|
// opt-out is NO_COLOR, handled above.
|
|
99
99
|
color: true,
|
|
100
100
|
theme: mdTheme(p),
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
// An UNKNOWN language tag throws inside cli-highlight, and the throw
|
|
102
|
+
// escaped the whole render: ONE ```` ```kotlin-ish ```` fence turned the
|
|
103
|
+
// entire answer into raw markdown (no wrap, no theme, no code box). Fall
|
|
104
|
+
// back to plaintext for this block only.
|
|
105
|
+
highlighter: (code, lang) => {
|
|
106
|
+
try {
|
|
107
|
+
return highlight(code, {
|
|
108
|
+
language: lang || 'plaintext',
|
|
109
|
+
ignoreIllegals: true,
|
|
110
|
+
theme: hlTheme(p),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
try {
|
|
115
|
+
return highlight(code, {
|
|
116
|
+
language: 'plaintext',
|
|
117
|
+
ignoreIllegals: true,
|
|
118
|
+
theme: hlTheme(p),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return String(code);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
106
126
|
codeBox: true,
|
|
107
127
|
tableBorder: 'unicode',
|
|
108
128
|
listIndent: 2,
|
package/dist/tui/background.js
CHANGED
|
@@ -23,7 +23,13 @@ function hexToOsc(hex) {
|
|
|
23
23
|
* testable without a terminal.
|
|
24
24
|
*/
|
|
25
25
|
export function backgroundOsc(name) {
|
|
26
|
-
|
|
26
|
+
// NO_COLOR is "present means off" (no-color.org). ZAMES_NO_BG is our OWN
|
|
27
|
+
// switch: a shell profile writing `ZAMES_NO_BG=0` clearly means KEEP it, so
|
|
28
|
+
// only a truthy value opts out.
|
|
29
|
+
const noBg = String(process.env.ZAMES_NO_BG ?? '').toLowerCase();
|
|
30
|
+
if (process.env.NO_COLOR)
|
|
31
|
+
return '';
|
|
32
|
+
if (noBg && noBg !== '0' && noBg !== 'false' && noBg !== 'no')
|
|
27
33
|
return '';
|
|
28
34
|
const id = isThemeName(name) ? name : DEFAULT_THEME;
|
|
29
35
|
return '\x1b]11;rgb:' + hexToOsc(ALL_PALETTES[id].bg) + '\x07';
|
package/dist/tui/edit.js
CHANGED
|
@@ -199,9 +199,14 @@ export function moveVisualRows(s, delta, promptStr, cols) {
|
|
|
199
199
|
// caret past the target row's text and onto the NEXT row entirely.
|
|
200
200
|
const curRow = lay.rows[lay.cursorRow];
|
|
201
201
|
const curChars = Array.from(curRow.text);
|
|
202
|
+
// Walk the characters of the current row until their VISUAL width reaches
|
|
203
|
+
// the caret's column. The old loop ran `cursorCol` TIMES over characters:
|
|
204
|
+
// with a wide (CJK) glyph two columns come from ONE character, so it read
|
|
205
|
+
// past the row (undefined counted as 1) and Up/Down landed too far right.
|
|
202
206
|
let want = 0;
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
const targetCol = lay.cursorCol - visLen(curRow.prefix);
|
|
208
|
+
for (let k = 0; k < curChars.length && want < targetCol; k++) {
|
|
209
|
+
want += charWidth(curChars[k].codePointAt(0));
|
|
205
210
|
}
|
|
206
211
|
let idx = row.start;
|
|
207
212
|
let w = 0;
|
|
@@ -221,8 +226,16 @@ export function moveVisualRows(s, delta, promptStr, cols) {
|
|
|
221
226
|
// rows" bug.
|
|
222
227
|
if (idx > row.start + rowChars.length)
|
|
223
228
|
idx = row.start + rowChars.length;
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
// Keep the caret INSIDE the target row โ but a caret at the row's END is
|
|
230
|
+
// the natural position for a WIDE last glyph: stepping back one CODE UNIT
|
|
231
|
+
// put it in the MIDDLE of a surrogate pair or before a CJK glyph, so Up
|
|
232
|
+
// from the end of a wrapped CJK line landed a glyph too far left. Only step
|
|
233
|
+
// back when the end is followed by MORE text on the same visual line.
|
|
234
|
+
if (idx === row.start + rowChars.length && rowChars.length > 0) {
|
|
235
|
+
const atWide = idx > 0 && charWidth(s.buf.codePointAt(idx - 1) ?? 32) === 2;
|
|
236
|
+
if (!atWide)
|
|
237
|
+
idx--;
|
|
238
|
+
}
|
|
226
239
|
return { ...s, cursor: idx };
|
|
227
240
|
}
|
|
228
241
|
/** Insert a newline; a trailing ยซ\ยป before the cursor is removed first. */
|
|
@@ -274,7 +287,18 @@ export function historyDown(s) {
|
|
|
274
287
|
// The CURRENT line is the operator's, not a history entry: leaving the
|
|
275
288
|
// history must bring it back. Without this, editing a recalled line and then
|
|
276
289
|
// pressing Down restored the OLD draft and dropped the edit.
|
|
277
|
-
|
|
290
|
+
//
|
|
291
|
+
// An EDIT of a recalled entry is a scratch copy of THAT slot, not the
|
|
292
|
+
// operator's draft: `typingRun` marks it. Treating it as "not browsing"
|
|
293
|
+
// jumped OUT of the history and dropped the entry the operator was fixing โ
|
|
294
|
+
// Down must still move to the NEXT entry.
|
|
295
|
+
// A REAL edit means the buffer no longer matches the recalled entry: the
|
|
296
|
+
// `typingRun` flag is set by the KEY layer, not by `insertText` itself, so
|
|
297
|
+
// the comparison is what is reliable here.
|
|
298
|
+
const recalled = s.history[s.histIndex];
|
|
299
|
+
const editingRecalled = s.histIndex < s.history.length &&
|
|
300
|
+
(s.typingRun || (recalled !== undefined && s.buf !== recalled));
|
|
301
|
+
const draft = browsingHistory(s) && !editingRecalled ? s.histDraft : s.buf;
|
|
278
302
|
let buf;
|
|
279
303
|
if (histIndex < s.history.length - 1) {
|
|
280
304
|
histIndex++;
|
package/dist/tui/keys.js
CHANGED
|
@@ -197,7 +197,11 @@ export function dispatchKey(input, key, ctx, isVisualEdge) {
|
|
|
197
197
|
if (key.escape)
|
|
198
198
|
return { type: 'escape' };
|
|
199
199
|
// ---- printable text ----
|
|
200
|
-
|
|
200
|
+
// A raw DEL byte (0x7f) is NOT printable: some terminals hand it over as
|
|
201
|
+
// plain `input` without `key.backspace`, and `input >= ' '` (DEL is 127)
|
|
202
|
+
// inserted it as an invisible control character into the buffer.
|
|
203
|
+
const printable = input && input !== String.fromCharCode(127) && input >= ' ';
|
|
204
|
+
if (printable) {
|
|
201
205
|
return { type: 'insert', text: input };
|
|
202
206
|
}
|
|
203
207
|
return null;
|
package/dist/web.js
CHANGED
|
@@ -77,10 +77,13 @@ export function htmlToText(html) {
|
|
|
77
77
|
// A parked `<pre>` block comes back AFTER the tag strip, with only its own
|
|
78
78
|
// tags removed and its text kept verbatim.
|
|
79
79
|
s = s.replace(/\u0000PRE(\d+)PRE\u0000/g, (_m, i) => (parkedPre[Number(i)] ?? '').replace(/<[^>]+>/g, ''));
|
|
80
|
-
// HTML entities โ the basic ones
|
|
80
|
+
// HTML entities โ the basic ones. `&` is decoded LAST, otherwise an
|
|
81
|
+
// escaped entity (`&#38;` โ the literal text `&`) is decoded TWICE
|
|
82
|
+
// and the page's text is changed.
|
|
83
|
+
const AMP = String.fromCharCode(0) + 'amp' + String.fromCharCode(0);
|
|
84
|
+
s = s.replace(/&/g, AMP);
|
|
81
85
|
s = s
|
|
82
86
|
.replace(/ /g, ' ')
|
|
83
|
-
.replace(/&/g, '&')
|
|
84
87
|
.replace(/</g, '<')
|
|
85
88
|
.replace(/>/g, '>')
|
|
86
89
|
.replace(/"/g, '"')
|
|
@@ -89,7 +92,9 @@ export function htmlToText(html) {
|
|
|
89
92
|
.replace(/–/g, 'โ')
|
|
90
93
|
.replace(/…/g, 'โฆ')
|
|
91
94
|
.replace(/&#(\d+);/g, (_, n) => decodeEntityCode(Number(n)))
|
|
92
|
-
.replace(/&#x([0-9a-f]+);/gi, (_, n) => decodeEntityCode(parseInt(n, 16)))
|
|
95
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, n) => decodeEntityCode(parseInt(n, 16)))
|
|
96
|
+
.split(AMP)
|
|
97
|
+
.join('&');
|
|
93
98
|
// Collapse blank lines
|
|
94
99
|
s = s
|
|
95
100
|
.split('\n')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.96.
|
|
3
|
+
"version": "2.96.7",
|
|
4
4
|
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen or GLM โ no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|