zames_pro 2.96.8 → 2.96.10
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/commands.js +8 -1
- package/dist/config-command.js +23 -1
- package/dist/config-live.js +23 -2
- package/dist/context-limit.js +5 -1
- package/dist/model-command.js +7 -1
- package/dist/resume-command.js +7 -2
- package/dist/spinner.js +4 -1
- package/dist/statusline.js +9 -0
- 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.10] - 2026-11-06
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **export:** keep a tool result on one line and mark a clipped one
|
|
15
|
+
- **config:** validate a live store value before writing it
|
|
16
|
+
- **config:** an unknown language is rejected instead of silently defaulted
|
|
17
|
+
- **config:** a password keeps its spaces when set from the command line
|
|
18
|
+
- **transcript:** cap the session label so the file name fits the filesystem
|
|
19
|
+
|
|
20
|
+
## [2.96.9] - 2026-11-06
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **statusline:** a backgrounded child no longer holds the badge until the timeout
|
|
25
|
+
- **resume:** a junk session record is skipped instead of crashing /sessions
|
|
26
|
+
- **model:** a model id with a newline is flattened to one line
|
|
27
|
+
- **context-limit:** a non-finite retry budget gives up instead of retrying forever
|
|
28
|
+
- **spinner:** a dots-only phrase keeps a label instead of going empty
|
|
29
|
+
|
|
10
30
|
## [2.96.8] - 2026-11-06
|
|
11
31
|
|
|
12
32
|
### Fixed
|
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/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/context-limit.js
CHANGED
|
@@ -9,7 +9,11 @@ export async function recoverFromContextLimit(deps, original) {
|
|
|
9
9
|
notice(t(keys.giveUp, { provider }));
|
|
10
10
|
return { resend: false, error: original };
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
// A NON-FINITE budget makes `attempt > maxRetries` false forever (`x > NaN`
|
|
13
|
+
// is false), so a chat that can never accept another message was
|
|
14
|
+
// re-compacted on every send with no budget at all. Require a real number.
|
|
15
|
+
const budget = Number.isFinite(maxRetries) && maxRetries >= 0 ? maxRetries : 0;
|
|
16
|
+
if (!recover || attempt > budget)
|
|
13
17
|
return giveUp();
|
|
14
18
|
notice(t(keys.compact, { provider }));
|
|
15
19
|
let recovered = false;
|
package/dist/model-command.js
CHANGED
|
@@ -12,7 +12,13 @@ export function parseModelIntent(provider, arg, t = translate('en')) {
|
|
|
12
12
|
message: t('model.unsupported', { provider }),
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
// A model id is a SINGLE token: the operator's text can carry a newline
|
|
16
|
+
// (a paste, a wrapped line), and it was stored and echoed verbatim — the
|
|
17
|
+
// newline broke the status line and the `/model set` report into fake
|
|
18
|
+
// rows. Collapse whitespace; an id is never allowed to contain a break.
|
|
19
|
+
const model = String(arg ?? '')
|
|
20
|
+
.replace(/\s+/g, ' ')
|
|
21
|
+
.trim();
|
|
16
22
|
return model ? { kind: 'set', model } : { kind: 'list' };
|
|
17
23
|
}
|
|
18
24
|
/** The text for an EMPTY model list (a provider that answered with nothing). */
|
package/dist/resume-command.js
CHANGED
|
@@ -14,8 +14,13 @@ export function filterSessions(sessions, filter) {
|
|
|
14
14
|
.toLowerCase();
|
|
15
15
|
if (!q)
|
|
16
16
|
return sessions.slice();
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// Session files are hand-editable and are written by OLDER builds: an array
|
|
18
|
+
// element can be `null`, and a field can be a number. Reading them blindly
|
|
19
|
+
// threw a raw TypeError out of `/sessions`.
|
|
20
|
+
const text = (v) => (typeof v === 'string' ? v : '');
|
|
21
|
+
return sessions.filter((s) => !!s &&
|
|
22
|
+
(text(s.title).toLowerCase().includes(q) ||
|
|
23
|
+
text(s.workdir).toLowerCase().includes(q)));
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
21
26
|
* A chat id only exists on the backend that created it, so opening a session
|
package/dist/spinner.js
CHANGED
|
@@ -15,7 +15,10 @@ export function randomThinkingPhrase(locale = DEFAULT_LOCALE) {
|
|
|
15
15
|
}
|
|
16
16
|
// Strip the trailing ellipsis from the phrase — dots are animated separately.
|
|
17
17
|
export function stripEllipsis(phrase) {
|
|
18
|
-
|
|
18
|
+
const out = phrase.replace(/[.…]+\s*$/, '');
|
|
19
|
+
// A phrase that is ONLY dots (an odd translation) must not become an EMPTY
|
|
20
|
+
// label: the spinner would then animate bare dots with no text at all.
|
|
21
|
+
return out === '' ? phrase.trim() : out;
|
|
19
22
|
}
|
|
20
23
|
// Dot animation shared by BOTH UIs (the ora spinner and the LineEditor): start
|
|
21
24
|
// from an empty string (0 dots), then grow. The width is padded to the maximum
|
package/dist/statusline.js
CHANGED
|
@@ -94,6 +94,15 @@ export function runStatusLineCommand(workdir, command, info, timeoutMs = 4000) {
|
|
|
94
94
|
clearTimeout(timer);
|
|
95
95
|
finish();
|
|
96
96
|
});
|
|
97
|
+
// A NEWLINE already ends the FIRST line, and that is all the badge shows.
|
|
98
|
+
// A backgrounded grandchild keeps the pipe OPEN (`my-status &`), so `end`
|
|
99
|
+
// never fired and the badge waited out the whole timeout on every tick.
|
|
100
|
+
child.stdout?.on('data', (c) => {
|
|
101
|
+
if (String(c).includes(String.fromCharCode(10))) {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
finish();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
97
106
|
child.on('close', () => {
|
|
98
107
|
clearTimeout(timer);
|
|
99
108
|
finish();
|
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.10",
|
|
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": {
|