zames_pro 2.96.1 → 2.96.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/CHANGELOG.md +15 -5
- package/dist/commands.js +8 -3
- package/dist/info-commands.js +11 -4
- package/dist/limits-command.js +13 -5
- package/dist/restore-history.js +10 -1
- package/dist/status-command.js +46 -29
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,11 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Fixed
|
|
11
11
|
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
12
|
+
- **info:** keep a /chat and /pwd value on one line
|
|
13
|
+
- **status:** flatten a status/doctor value and drop a broken count
|
|
14
|
+
- **limits:** never render a negative or NaN counter in /limits
|
|
15
|
+
- **restore-history:** keep a plain answer that opens with 'thinking for'
|
|
16
|
+
- **commands:** keep a project file/skill/command name on one /context row
|
|
17
|
+
|
|
18
|
+
## [2.96.2] - 2026-11-06
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **info:** keep a /chat and /pwd value on one line
|
|
23
|
+
- **status:** flatten a status/doctor value and drop a broken count
|
|
24
|
+
- **limits:** never render a negative or NaN counter in /limits
|
|
25
|
+
- **restore-history:** keep a plain answer that opens with 'thinking for'
|
|
26
|
+
- **commands:** keep a project file/skill/command name on one /context row
|
|
17
27
|
|
|
18
28
|
## [2.96.1] - 2026-11-06
|
|
19
29
|
|
package/dist/commands.js
CHANGED
|
@@ -64,11 +64,16 @@ export function formatDiffStat(statText, opts = {}, t = translate('en')) {
|
|
|
64
64
|
/** One renderable line per loaded context source (file + char count). */
|
|
65
65
|
export function formatContextSources(agents, memory, skills, commands, opts = {}, t = translate('en')) {
|
|
66
66
|
const lines = [];
|
|
67
|
+
// Paths, skill and command names come from the operator's PROJECT: a
|
|
68
|
+
// newline in one of them started a NEW row of the report (a crafted
|
|
69
|
+
// `/cmd\nFAKE` printed a bare `FAKE` line), so every value is flattened to
|
|
70
|
+
// one line first — the same rule as renderDoctor/formatJobLine.
|
|
71
|
+
const oneLine = (v) => String(v ?? '').replace(/\s+/g, ' ');
|
|
67
72
|
lines.push(t('context.title'));
|
|
68
73
|
if (typeof opts.systemPromptChars === 'number') {
|
|
69
74
|
lines.push(t('context.system_prompt', { n: String(opts.systemPromptChars) }));
|
|
70
75
|
}
|
|
71
|
-
const fileLine = (p, content) => ' ' + p + ' (' + content.length + ' chars)';
|
|
76
|
+
const fileLine = (p, content) => ' ' + oneLine(p) + ' (' + content.length + ' chars)';
|
|
72
77
|
lines.push(t('context.agents'));
|
|
73
78
|
if (agents.length)
|
|
74
79
|
for (const a of agents)
|
|
@@ -84,13 +89,13 @@ export function formatContextSources(agents, memory, skills, commands, opts = {}
|
|
|
84
89
|
lines.push(t('context.skills'));
|
|
85
90
|
if (skills.length)
|
|
86
91
|
for (const s of skills)
|
|
87
|
-
lines.push(' ' + s.name + (s.path ? ' ' + s.path : ''));
|
|
92
|
+
lines.push(' ' + oneLine(s.name) + (s.path ? ' ' + oneLine(s.path) : ''));
|
|
88
93
|
else
|
|
89
94
|
lines.push(' ' + t('context.none'));
|
|
90
95
|
lines.push(t('context.commands'));
|
|
91
96
|
if (commands.length)
|
|
92
97
|
for (const c of commands)
|
|
93
|
-
lines.push(' /' + c.name);
|
|
98
|
+
lines.push(' /' + oneLine(c.name));
|
|
94
99
|
else
|
|
95
100
|
lines.push(' ' + t('context.none'));
|
|
96
101
|
return lines.join(NL);
|
package/dist/info-commands.js
CHANGED
|
@@ -10,6 +10,11 @@ import { theme } from './theme.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export function createInfoCommands(deps) {
|
|
12
12
|
const { t } = deps;
|
|
13
|
+
// A chat id comes from the page URL and the working directory from the
|
|
14
|
+
// process cwd: a newline in either would start a LINE of its own in the
|
|
15
|
+
// terminal, so a value is flattened before it is printed (the same rule
|
|
16
|
+
// `/status` and `/doctor` already follow).
|
|
17
|
+
const oneLine = (v) => String(v ?? '').replace(/\s+/g, ' ');
|
|
13
18
|
const printLines = (lines, print) => {
|
|
14
19
|
for (const line of lines)
|
|
15
20
|
print(theme.system(line));
|
|
@@ -24,17 +29,19 @@ export function createInfoCommands(deps) {
|
|
|
24
29
|
handler: async (ctx) => {
|
|
25
30
|
const id = deps.currentChatId();
|
|
26
31
|
if (id) {
|
|
27
|
-
ctx.print(theme.system(t('chats.current_id', { v: id })));
|
|
28
|
-
ctx.print(theme.system(`URL: ${deps.chatUrl(id)}`));
|
|
32
|
+
ctx.print(theme.system(t('chats.current_id', { v: oneLine(id) })));
|
|
33
|
+
ctx.print(theme.system(`URL: ${oneLine(deps.chatUrl(id))}`));
|
|
29
34
|
return;
|
|
30
35
|
}
|
|
31
36
|
const live = await deps.resolveChatId();
|
|
32
|
-
ctx.print(theme.system(live
|
|
37
|
+
ctx.print(theme.system(live
|
|
38
|
+
? t('chats.current_id', { v: oneLine(live) })
|
|
39
|
+
: t('chats.not_created')));
|
|
33
40
|
},
|
|
34
41
|
},
|
|
35
42
|
{
|
|
36
43
|
name: 'pwd',
|
|
37
|
-
handler: (ctx) => ctx.print(theme.system(deps.workdir())),
|
|
44
|
+
handler: (ctx) => ctx.print(theme.system(oneLine(deps.workdir()))),
|
|
38
45
|
},
|
|
39
46
|
{
|
|
40
47
|
name: 'transcript',
|
package/dist/limits-command.js
CHANGED
|
@@ -5,9 +5,17 @@ import { translate } from './i18n.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export function formatLimits(stats, opts, t = translate('en')) {
|
|
7
7
|
const out = [];
|
|
8
|
+
// A counter is a count and a pause is a duration: a negative/NaN/Infinity
|
|
9
|
+
// value (a hand-edited telemetry line, a broken provider counter) must not
|
|
10
|
+
// render as `-3s` / `NaNs`, which reads like a real measurement.
|
|
11
|
+
const num = (v) => typeof v === 'number' && Number.isFinite(v) ? String(Math.max(0, v)) : '0';
|
|
8
12
|
out.push({
|
|
9
13
|
kind: 'title',
|
|
10
|
-
|
|
14
|
+
// The provider label comes from the config: a newline in it would start a
|
|
15
|
+
// row of its own.
|
|
16
|
+
text: t('limits.title', {
|
|
17
|
+
provider: String(opts.providerLabel ?? '').replace(/\s+/g, ' '),
|
|
18
|
+
}),
|
|
11
19
|
});
|
|
12
20
|
const windows = [
|
|
13
21
|
['10m', stats.last10m],
|
|
@@ -17,20 +25,20 @@ export function formatLimits(stats, opts, t = translate('en')) {
|
|
|
17
25
|
for (const [label, n] of windows) {
|
|
18
26
|
out.push({
|
|
19
27
|
kind: 'window',
|
|
20
|
-
text: t('limits.window', { label, n:
|
|
28
|
+
text: t('limits.window', { label, n: num(n) }),
|
|
21
29
|
});
|
|
22
30
|
}
|
|
23
31
|
out.push({
|
|
24
32
|
kind: 'rate',
|
|
25
33
|
text: t('limits.rate', {
|
|
26
|
-
n:
|
|
27
|
-
sec: String(Math.round(stats.avgBlockMs / 1000)),
|
|
34
|
+
n: num(stats.rateLimits),
|
|
35
|
+
sec: String(Math.round(Number(num(stats.avgBlockMs)) / 1000)),
|
|
28
36
|
}),
|
|
29
37
|
});
|
|
30
38
|
out.push({
|
|
31
39
|
kind: 'interval',
|
|
32
40
|
text: t('limits.interval', {
|
|
33
|
-
sec: String(Math.round(opts.intervalMs / 1000)),
|
|
41
|
+
sec: String(Math.round(Number(num(opts.intervalMs)) / 1000)),
|
|
34
42
|
adaptive: opts.adaptive ? t('limits.adaptive_on') : '',
|
|
35
43
|
}),
|
|
36
44
|
});
|
package/dist/restore-history.js
CHANGED
|
@@ -260,7 +260,16 @@ export function stripThinkBlocks(text) {
|
|
|
260
260
|
// "OK2"); the reasoning BODY was already removed with its subtree.
|
|
261
261
|
// A single newline: with the blank-line form the `[\s\S]*?` below ate
|
|
262
262
|
// the whole answer and the restored turn vanished.
|
|
263
|
-
|
|
263
|
+
// The header is `Thought for <duration>`: the FIRST word is the past tense
|
|
264
|
+
// (`Thought`) or the bare `Think`, and the duration must look like a TIME
|
|
265
|
+
// (`1 second`, `12s`, `2 minutes`). Matching any `think for <anything>`
|
|
266
|
+
// dropped a plain answer that OPENS with the same words.
|
|
267
|
+
t = t.replace(/^\s*(?:thought|thinking|think)\s+for\s+\d+(?:\.\d+)?\s*(?:seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)(?![a-z])\b[^\n]*\n?/i, '');
|
|
268
|
+
// The DOM may hand the header WITHOUT its newline (`Thought for 1
|
|
269
|
+
// secondOK2`), so a header followed directly by the answer text is dropped
|
|
270
|
+
// too. The duration must be a real TIME word, never just `for <word>`: a
|
|
271
|
+
// plain answer opening with the same words keeps its line.
|
|
272
|
+
t = t.replace(/^\s*(?:thought|thinking|think)\s+for\s+\d+(?:\.\d+)?\s*(?:seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)(?=[A-Z\u0410-\u042f])/i, '');
|
|
264
273
|
// The DOM may hand the header WITHOUT its newline ("Thought for 1
|
|
265
274
|
// secondOK2"): drop the header up to the first non-space run only when a
|
|
266
275
|
// line break or the end of the text follows it.
|
package/dist/status-command.js
CHANGED
|
@@ -6,18 +6,29 @@ export function formatStatus(s, t = translate('en')) {
|
|
|
6
6
|
const none = t('common.none');
|
|
7
7
|
const onoff = (b) => (b ? t('common.on') : t('common.off'));
|
|
8
8
|
const lines = [];
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Every value printed here comes from the ENVIRONMENT (a working directory,
|
|
10
|
+
// a chat id) or from the config: a newline in one of them starts a status
|
|
11
|
+
// row of its own, so each value is flattened to one line — the same rule
|
|
12
|
+
// renderDoctor already applies to its cells.
|
|
13
|
+
const oneLine = (v) => String(v ?? '').replace(/\s+/g, ' ');
|
|
14
|
+
// A count must be a real number: `String(NaN)` used to print `NaN` in a row
|
|
15
|
+
// that reads like a measurement.
|
|
16
|
+
const count = (n) => typeof n === 'number' && Number.isFinite(n) && n >= 0 ? String(n) : '0';
|
|
17
|
+
const val = (v, fallback) => v ? oneLine(v) : fallback;
|
|
18
|
+
lines.push(t('status.workdir', { v: oneLine(s.workdir) }));
|
|
19
|
+
lines.push(t('status.review_mode', {
|
|
20
|
+
v: s.reviewMode ? oneLine(s.reviewMode) : no,
|
|
21
|
+
}));
|
|
11
22
|
if (s.reviewMode && s.origDir) {
|
|
12
|
-
lines.push(t('status.orig_dir', { v: s.origDir }));
|
|
23
|
+
lines.push(t('status.orig_dir', { v: oneLine(s.origDir) }));
|
|
13
24
|
}
|
|
14
|
-
lines.push(t('status.chat', { v: s.chatId
|
|
25
|
+
lines.push(t('status.chat', { v: val(s.chatId, none) }));
|
|
15
26
|
lines.push(t('status.fresh_next', { v: s.freshNext ? yes : no }));
|
|
16
27
|
lines.push(t('status.prompt_next', { v: s.promptNext ? yes : no }));
|
|
17
|
-
lines.push(t('status.last_chat', { v: s.lastChat
|
|
18
|
-
lines.push(t('status.sessions', { v: s.sessionsDir }));
|
|
28
|
+
lines.push(t('status.last_chat', { v: val(s.lastChat, none) }));
|
|
29
|
+
lines.push(t('status.sessions', { v: oneLine(s.sessionsDir) }));
|
|
19
30
|
lines.push(t('status.dev', { v: s.devMode ? t('common.on') : t('common.off') }));
|
|
20
|
-
lines.push(t('status.max_iter', { v: s.maxIterations }));
|
|
31
|
+
lines.push(t('status.max_iter', { v: count(s.maxIterations) }));
|
|
21
32
|
lines.push(t('status.headless', { v: s.headless ? yes : no }));
|
|
22
33
|
lines.push(t('status.debug', { v: s.debug ? yes : no }));
|
|
23
34
|
lines.push(t('status.toggles', {
|
|
@@ -26,27 +37,28 @@ export function formatStatus(s, t = translate('en')) {
|
|
|
26
37
|
}));
|
|
27
38
|
lines.push(t('status.undo', { v: onoff(s.undo) }));
|
|
28
39
|
lines.push(t('status.checkpoint', { v: onoff(s.checkpoint) }));
|
|
29
|
-
lines.push(t('status.transcript', {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
40
|
+
lines.push(t('status.transcript', {
|
|
41
|
+
v: s.transcriptFile ? oneLine(s.transcriptFile) : t('common.off'),
|
|
42
|
+
}));
|
|
43
|
+
// The GOAL is operator text (`/goal <free text>`): one status entry must
|
|
44
|
+
// stay on one line.
|
|
34
45
|
lines.push(t('status.goal', { v: s.goal ? oneLine(s.goal) : none }));
|
|
35
|
-
lines.push(t('status.locale', { v: s.localeName }));
|
|
46
|
+
lines.push(t('status.locale', { v: oneLine(s.localeName) }));
|
|
36
47
|
// A NEGATIVE tool count is nonsense (a broken MCP server status, a
|
|
37
|
-
// hand-edited value): "MCP tools: -2" reads like a real measurement
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const mcpCount = s.mcpToolCount === null || s.mcpToolCount < 0
|
|
48
|
+
// hand-edited value): "MCP tools: -2" reads like a real measurement.
|
|
49
|
+
const mcpCount = s.mcpToolCount === null ||
|
|
50
|
+
!Number.isFinite(s.mcpToolCount) ||
|
|
51
|
+
s.mcpToolCount < 0
|
|
42
52
|
? t('common.unknown')
|
|
43
|
-
: s.mcpToolCount;
|
|
53
|
+
: String(s.mcpToolCount);
|
|
44
54
|
lines.push(t('status.mcp', { v: mcpCount }));
|
|
45
55
|
// A NEGATIVE count is nonsense (a broken provider counter): show "unknown"
|
|
46
56
|
// rather than a number that reads like a real measurement.
|
|
47
|
-
const tokens = s.tokenUsage === null || s.tokenUsage
|
|
57
|
+
const tokens = s.tokenUsage === null || !Number.isFinite(s.tokenUsage) || s.tokenUsage < 0
|
|
58
|
+
? null
|
|
59
|
+
: s.tokenUsage;
|
|
48
60
|
lines.push(t('status.tokens', {
|
|
49
|
-
v: tokens === null ? t('common.unknown') : tokens,
|
|
61
|
+
v: tokens === null ? t('common.unknown') : String(tokens),
|
|
50
62
|
}));
|
|
51
63
|
return lines;
|
|
52
64
|
}
|
|
@@ -57,26 +69,31 @@ export function formatStatus(s, t = translate('en')) {
|
|
|
57
69
|
*/
|
|
58
70
|
export function doctorProbeSummary(p) {
|
|
59
71
|
const rows = [];
|
|
72
|
+
// A branch name, a missing capability id and a server/tool COUNT come from
|
|
73
|
+
// the repository and from a broken MCP probe: a newline started a row of its
|
|
74
|
+
// own and `NaN`/`-3` read like real measurements.
|
|
75
|
+
const oneLine = (v) => String(v ?? '').replace(/\s+/g, ' ');
|
|
76
|
+
const count = (n) => typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : 0;
|
|
60
77
|
rows.push({
|
|
61
78
|
status: p.gitOk ? 'ok' : 'warn',
|
|
62
|
-
label: p.gitOk
|
|
79
|
+
label: p.gitOk
|
|
80
|
+
? 'git: ' + oneLine(p.gitBranch || 'detached')
|
|
81
|
+
: 'git: absent',
|
|
63
82
|
});
|
|
64
83
|
rows.push({
|
|
65
84
|
status: p.authSaved ? 'ok' : 'warn',
|
|
66
85
|
label: p.authSaved ? 'auth: saved' : 'auth: not saved',
|
|
67
86
|
});
|
|
87
|
+
const servers = count(p.mcpServers);
|
|
88
|
+
const tools = count(p.mcpTools);
|
|
68
89
|
rows.push({
|
|
69
|
-
status:
|
|
70
|
-
label: 'mcp: ' +
|
|
71
|
-
String(p.mcpServers) +
|
|
72
|
-
' servers / ' +
|
|
73
|
-
String(p.mcpTools) +
|
|
74
|
-
' tools',
|
|
90
|
+
status: tools > 0 ? 'ok' : 'warn',
|
|
91
|
+
label: 'mcp: ' + String(servers) + ' servers / ' + String(tools) + ' tools',
|
|
75
92
|
});
|
|
76
93
|
if (p.uiMissingCritical && p.uiMissingCritical.length) {
|
|
77
94
|
rows.push({
|
|
78
95
|
status: 'warn',
|
|
79
|
-
label: 'ui: missing ' + p.uiMissingCritical.join(', '),
|
|
96
|
+
label: 'ui: missing ' + oneLine(p.uiMissingCritical.join(', ')),
|
|
80
97
|
});
|
|
81
98
|
}
|
|
82
99
|
return rows;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.96.
|
|
3
|
+
"version": "2.96.2",
|
|
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": {
|