zames_pro 2.96.9 → 2.96.11
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/browser-login.js +5 -2
- package/dist/commands.js +8 -1
- package/dist/compact.js +7 -3
- package/dist/completion-check.js +16 -1
- package/dist/config-command.js +23 -1
- package/dist/config-live.js +23 -2
- package/dist/prompt.js +16 -2
- package/dist/restore-history.js +26 -11
- package/dist/transcript.js +6 -1
- 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.11] - 2026-11-06
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **prompt:** delete a whole code point in the forward-Delete branch
|
|
15
|
+
- **completion-check:** treat extension-less build files as runnable artifacts
|
|
16
|
+
- **login:** classify a guest page with a captcha as a challenge
|
|
17
|
+
- **compact:** keep the message count in an empty-fallback reason
|
|
18
|
+
- **restore-history:** decode every JSON escape in one pass so a doubled backslash keeps the next escape
|
|
19
|
+
|
|
20
|
+
## [2.96.10] - 2026-11-06
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **export:** keep a tool result on one line and mark a clipped one
|
|
25
|
+
- **config:** validate a live store value before writing it
|
|
26
|
+
- **config:** an unknown language is rejected instead of silently defaulted
|
|
27
|
+
- **config:** a password keeps its spaces when set from the command line
|
|
28
|
+
- **transcript:** cap the session label so the file name fits the filesystem
|
|
29
|
+
|
|
10
30
|
## [2.96.9] - 2026-11-06
|
|
11
31
|
|
|
12
32
|
### Fixed
|
package/dist/browser-login.js
CHANGED
|
@@ -175,10 +175,13 @@ export function classifyLoginProbe(input) {
|
|
|
175
175
|
const role = String(input.role || '').toLowerCase();
|
|
176
176
|
if (role === 'user' || role === 'admin')
|
|
177
177
|
return 'in';
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
// A captcha is the site asking for a HUMAN: a guest page that renders one is
|
|
179
|
+
// NOT a usable session (the visible-window flow exists for exactly this), so
|
|
180
|
+
// it must be classified before the guest shortcut below.
|
|
180
181
|
if (input.captcha)
|
|
181
182
|
return 'challenge';
|
|
183
|
+
if (role === 'guest')
|
|
184
|
+
return input.signInVisible ? 'out' : 'in';
|
|
182
185
|
if (input.hasToken === true)
|
|
183
186
|
return 'in';
|
|
184
187
|
if (input.hasToken === false)
|
package/dist/commands.js
CHANGED
|
@@ -299,10 +299,17 @@ export function formatExport(entries, meta = {}) {
|
|
|
299
299
|
JSON.stringify(e.args ?? {}));
|
|
300
300
|
}
|
|
301
301
|
else if (type === 'tool_result') {
|
|
302
|
+
// A tool result is TEXT: a newline in it started a bare report line and
|
|
303
|
+
// broke the one-line list. Flatten it, and MARK a clipped result so the
|
|
304
|
+
// export does not look like the whole output.
|
|
305
|
+
const raw = String(e.result ?? '');
|
|
306
|
+
const flat = raw.split(/\s*\r?\n\s*/).join(' ↵ ');
|
|
307
|
+
const clipped = clipExportText(flat, 500);
|
|
302
308
|
out.push(' -> ' +
|
|
303
309
|
String(e.tool ?? '?') +
|
|
304
310
|
': ' +
|
|
305
|
-
|
|
311
|
+
clipped +
|
|
312
|
+
(clipped.length < flat.length ? ' …' : ''));
|
|
306
313
|
}
|
|
307
314
|
else if (type === 'assistant_final') {
|
|
308
315
|
out.push('## Answer');
|
package/dist/compact.js
CHANGED
|
@@ -143,7 +143,9 @@ export async function performCompact(opts) {
|
|
|
143
143
|
// The reason must be ACTIONABLE: a model error, a BLOCKED history
|
|
144
144
|
// fetch (auth/HTTP) and a TOO-SHORT history end here, and a bare
|
|
145
145
|
// "unknown" hides which one it was.
|
|
146
|
-
|
|
146
|
+
// A model error is reported verbatim: it already names the cause.
|
|
147
|
+
const unusableReply = !!summaryErr && /^\s*[\[\{]/.test(summaryErr);
|
|
148
|
+
const err = summaryErr && !unusableReply
|
|
147
149
|
? summaryErr
|
|
148
150
|
: histErr
|
|
149
151
|
? t('compact.summary_failed', { v: histErr })
|
|
@@ -152,8 +154,10 @@ export async function performCompact(opts) {
|
|
|
152
154
|
' (' +
|
|
153
155
|
historyLen +
|
|
154
156
|
')'
|
|
155
|
-
: t('compact.summary_failed', { v: 'history too short' })
|
|
156
|
-
|
|
157
|
+
: t('compact.summary_failed', { v: 'history too short' }) +
|
|
158
|
+
' (' +
|
|
159
|
+
historyLen +
|
|
160
|
+
')';
|
|
157
161
|
log(t('compact.summary_failed', { v: err }));
|
|
158
162
|
return {
|
|
159
163
|
ok: false,
|
package/dist/completion-check.js
CHANGED
|
@@ -34,12 +34,27 @@ const RUNNABLE_EXT = new Set([
|
|
|
34
34
|
'.php',
|
|
35
35
|
'.css',
|
|
36
36
|
]);
|
|
37
|
+
// Extension-LESS build artifacts: they are executable "does it run" targets
|
|
38
|
+
// just like a script, and they were the one family the guard silently skipped
|
|
39
|
+
// (a written Makefile/Dockerfile and an answer DONE with no Bash call in
|
|
40
|
+
// between is exactly the live failure this module exists for).
|
|
41
|
+
const RUNNABLE_NAMES = new Set([
|
|
42
|
+
'makefile',
|
|
43
|
+
'gnumakefile',
|
|
44
|
+
'dockerfile',
|
|
45
|
+
'containerfile',
|
|
46
|
+
'build.ninja',
|
|
47
|
+
'justfile',
|
|
48
|
+
]);
|
|
37
49
|
export function isRunnablePath(p) {
|
|
38
50
|
if (!p)
|
|
39
51
|
return false;
|
|
40
52
|
const lower = p.toLowerCase();
|
|
53
|
+
const base = lower.slice(lower.lastIndexOf('/') + 1);
|
|
54
|
+
if (RUNNABLE_NAMES.has(base))
|
|
55
|
+
return true;
|
|
41
56
|
const dot = lower.lastIndexOf('.');
|
|
42
|
-
if (dot < 0)
|
|
57
|
+
if (dot < 0 || dot < lower.lastIndexOf('/'))
|
|
43
58
|
return false;
|
|
44
59
|
return RUNNABLE_EXT.has(lower.slice(dot));
|
|
45
60
|
}
|
package/dist/config-command.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// The interactive menu (`/config` bare or `menu`) is delegated to openMenu()
|
|
9
9
|
// because it pauses the editor and reads keys itself.
|
|
10
10
|
import { theme } from './theme.js';
|
|
11
|
+
import { isLocale } from './i18n.js';
|
|
11
12
|
import { configGetValue, configValueHint } from './config-commands.js';
|
|
12
13
|
/**
|
|
13
14
|
* Handle a `/config <sub>` text command. Never throws for a bad subcommand or
|
|
@@ -65,6 +66,18 @@ export async function handleConfigCommand(input, deps) {
|
|
|
65
66
|
const field = schema.find((f) => f.path === 'ui.locale');
|
|
66
67
|
if (!field)
|
|
67
68
|
return;
|
|
69
|
+
// `normalizeLocale` FALLS BACK to the default for anything it does not
|
|
70
|
+
// understand, so `/config lang xx` silently SAVED English and reported it
|
|
71
|
+
// as set — the operator's typo was accepted. Reject an unknown value.
|
|
72
|
+
const normalized = String(val).trim().toLowerCase();
|
|
73
|
+
if (!isLocale(normalized) &&
|
|
74
|
+
!['russian', 'русский', 'english', 'английский'].includes(normalized)) {
|
|
75
|
+
printErr(theme.error(t('cfg.bad_value', {
|
|
76
|
+
v: 'ui.locale',
|
|
77
|
+
type: configValueHint(field, t),
|
|
78
|
+
})));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
68
81
|
const loc = normalizeLocale(val);
|
|
69
82
|
try {
|
|
70
83
|
setValue(field, loc);
|
|
@@ -94,7 +107,16 @@ export async function handleConfigCommand(input, deps) {
|
|
|
94
107
|
}
|
|
95
108
|
if (sub === 'set') {
|
|
96
109
|
const key = parts[2];
|
|
97
|
-
|
|
110
|
+
// A password/username is a SECRET: it may legally contain repeated,
|
|
111
|
+
// leading or trailing spaces, and joining the tokens with ONE space
|
|
112
|
+
// silently changed it (auto-login then failed with "wrong password").
|
|
113
|
+
// Take the text after the key VERBATIM for those fields.
|
|
114
|
+
const isSecret = /(password|username)\/?$/i.test(key || '');
|
|
115
|
+
const raw = isSecret
|
|
116
|
+
? String(input)
|
|
117
|
+
.replace(/^\s*\/config\s+set\s+\S+\s?/i, '')
|
|
118
|
+
.replace(/\s+$/, '')
|
|
119
|
+
: parts.slice(3).join(' ');
|
|
98
120
|
const field = schema.find((f) => f.path === key);
|
|
99
121
|
if (!field) {
|
|
100
122
|
printErr(theme.error(t('cfg.unknown_key', { v: key || '' })));
|
package/dist/config-live.js
CHANGED
|
@@ -59,16 +59,37 @@ export function applyBrowserLiveConfig(path, value, browser) {
|
|
|
59
59
|
* their config at construction, so a change must be written to the instance
|
|
60
60
|
* too. */
|
|
61
61
|
export function applyStoreLiveConfig(path, value, stores) {
|
|
62
|
+
// The browser map VALIDATES its values; these stores were written blindly,
|
|
63
|
+
// so a negative `maxBackups` (rejected by the schema) or a string could
|
|
64
|
+
// reach a live store and change retention silently.
|
|
65
|
+
const isCount = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
66
|
+
const isBool = (v) => typeof v === 'boolean';
|
|
62
67
|
if (path === 'undo.enabled' || path === 'undo.maxBackups') {
|
|
63
68
|
if (!stores.undo)
|
|
64
69
|
return false;
|
|
65
|
-
|
|
70
|
+
if (path === 'undo.enabled') {
|
|
71
|
+
if (!isBool(value))
|
|
72
|
+
return false;
|
|
73
|
+
stores.undo.enabled = value;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (!isCount(value))
|
|
77
|
+
return false;
|
|
78
|
+
stores.undo.maxBackups = value;
|
|
66
79
|
return true;
|
|
67
80
|
}
|
|
68
81
|
if (path === 'checkpoint.enabled' || path === 'checkpoint.maxBackups') {
|
|
69
82
|
if (!stores.checkpoints)
|
|
70
83
|
return false;
|
|
71
|
-
|
|
84
|
+
if (path === 'checkpoint.enabled') {
|
|
85
|
+
if (!isBool(value))
|
|
86
|
+
return false;
|
|
87
|
+
stores.checkpoints.enabled = value;
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (!isCount(value))
|
|
91
|
+
return false;
|
|
92
|
+
stores.checkpoints.maxBackups = value;
|
|
72
93
|
return true;
|
|
73
94
|
}
|
|
74
95
|
return false;
|
package/dist/prompt.js
CHANGED
|
@@ -200,9 +200,23 @@ export async function promptOnce(question) {
|
|
|
200
200
|
continue;
|
|
201
201
|
}
|
|
202
202
|
if (rest.startsWith('[3~')) {
|
|
203
|
-
// Delete
|
|
203
|
+
// Delete removes a CODE POINT, not one UTF-16 unit: in front of an
|
|
204
|
+
// emoji a unit-sized cut left the LOW surrogate in the line — a
|
|
205
|
+
// broken code point the caller received as its answer (same rule
|
|
206
|
+
// as the Backspace branch above). A unit cut is only safe OFF a
|
|
207
|
+
// surrogate: inside a PAIR it must move to the pair's start first.
|
|
204
208
|
if (cursor < line.length) {
|
|
205
|
-
|
|
209
|
+
const at = line.charCodeAt(cursor);
|
|
210
|
+
if (at >= 0xdc00 && at <= 0xdfff)
|
|
211
|
+
cursor--;
|
|
212
|
+
const next = line.charCodeAt(cursor);
|
|
213
|
+
const follow = cursor + 1 < line.length ? line.charCodeAt(cursor + 1) : 0;
|
|
214
|
+
const pair = next >= 0xd800 &&
|
|
215
|
+
next <= 0xdbff &&
|
|
216
|
+
follow >= 0xdc00 &&
|
|
217
|
+
follow <= 0xdfff;
|
|
218
|
+
const cut = cursor + (pair ? 2 : 1);
|
|
219
|
+
line = line.slice(0, cursor) + line.slice(cut);
|
|
206
220
|
redraw();
|
|
207
221
|
}
|
|
208
222
|
s = s.slice(3);
|
package/dist/restore-history.js
CHANGED
|
@@ -56,12 +56,32 @@ export function parseAssistantToolCall(text) {
|
|
|
56
56
|
export function unescapeJsonString(s) {
|
|
57
57
|
// A `\uD800`-style escape that is NOT followed by a low surrogate (a
|
|
58
58
|
// truncated or hand-written payload) would become a LONE surrogate - a
|
|
59
|
-
// broken code point the restored dialogue prints as `?`. Decode a valid
|
|
60
|
-
// as one code point and drop an unpaired half.
|
|
59
|
+
// broken code point the restored dialogue prints as `?`. Decode a valid
|
|
60
|
+
// PAIR as one code point and drop an unpaired half.
|
|
61
|
+
//
|
|
62
|
+
// ONE pass for EVERY escape family: `\\` is consumed before the simple
|
|
63
|
+
// escapes (`\n`, `\t`, ...) look at the second half of a DOUBLED
|
|
64
|
+
// backslash. JSON stores a literal backslash before `n` (`C:\\notes`,
|
|
65
|
+
// a printed `\n`) as `\\\\n`; decoding `\n` first turned that pair
|
|
66
|
+
// into a real LINE BREAK and lost the backslash, reflowing the restored
|
|
67
|
+
// answer.
|
|
61
68
|
let pendingHigh = '';
|
|
62
|
-
return String(s || '')
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
return String(s || '').replace(/\\(?:u([0-9a-fA-F]{4})|([nrt"\\]))/g, (_m, hex, ch) => {
|
|
70
|
+
if (!hex) {
|
|
71
|
+
if (ch === 'n')
|
|
72
|
+
return NL;
|
|
73
|
+
if (ch === 'r')
|
|
74
|
+
return String.fromCharCode(13);
|
|
75
|
+
if (ch === 't')
|
|
76
|
+
return String.fromCharCode(9);
|
|
77
|
+
if (ch === '"')
|
|
78
|
+
return '"';
|
|
79
|
+
pendingHigh = '';
|
|
80
|
+
return '\\';
|
|
81
|
+
}
|
|
82
|
+
const cp = parseInt(hex, 16);
|
|
83
|
+
// A valid PAIR is one code point; an UNPAIRED half is dropped (it
|
|
84
|
+
// would print as `?`).
|
|
65
85
|
if (cp >= 0xd800 && cp <= 0xdbff) {
|
|
66
86
|
pendingHigh = String.fromCharCode(cp);
|
|
67
87
|
return '';
|
|
@@ -73,12 +93,7 @@ export function unescapeJsonString(s) {
|
|
|
73
93
|
}
|
|
74
94
|
pendingHigh = '';
|
|
75
95
|
return String.fromCharCode(cp);
|
|
76
|
-
})
|
|
77
|
-
.replace(/\\n/g, NL)
|
|
78
|
-
.replace(/\\r/g, String.fromCharCode(13))
|
|
79
|
-
.replace(/\\t/g, String.fromCharCode(9))
|
|
80
|
-
.replace(/\\"/g, '"')
|
|
81
|
-
.replace(/\\\\/g, '\\');
|
|
96
|
+
});
|
|
82
97
|
}
|
|
83
98
|
/**
|
|
84
99
|
* Normalize one restored message for display:
|
package/dist/transcript.js
CHANGED
|
@@ -129,10 +129,15 @@ export class Transcript {
|
|
|
129
129
|
const safeName = String(sessionName)
|
|
130
130
|
.replace(/[\\/:*?"<>|\u0000-\u001f]/g, '_')
|
|
131
131
|
.trim() || 'session';
|
|
132
|
+
// The stamp and the `-`/`.jsonl` suffix take ~40 bytes of the 255-byte
|
|
133
|
+
// NAME limit: a deeply nested project path produced an over-long file
|
|
134
|
+
// name, the stream failed with ENAMETOOLONG and the WHOLE run had no
|
|
135
|
+
// transcript. Cap the label, keeping its head.
|
|
136
|
+
const cappedName = safeName.slice(0, 120);
|
|
132
137
|
// Rotation BEFORE the create: the new file then has the newest stamp,
|
|
133
138
|
// so the current run's transcript is never a deletion candidate.
|
|
134
139
|
rotateTranscripts(dir);
|
|
135
|
-
this.file = path.join(dir, `${
|
|
140
|
+
this.file = path.join(dir, `${cappedName}-${stamp}.jsonl`);
|
|
136
141
|
this.stream = fs.createWriteStream(this.file, { flags: 'a' });
|
|
137
142
|
// Write errors (disk full, file deleted, etc.) arrive as an 'error'
|
|
138
143
|
// event; without a listener this is an uncaught exception.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.96.
|
|
3
|
+
"version": "2.96.11",
|
|
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": {
|