zames_pro 2.82.55 → 2.82.57
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 +14 -0
- package/dist/input/layout.js +67 -18
- package/dist/shell.js +6 -1
- package/dist/tool-summary.js +5 -0
- package/dist/tools.js +24 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.82.57] - 2026-10-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **shell:** strip every quote from a cd target before the sandbox check
|
|
15
|
+
|
|
16
|
+
## [2.82.56] - 2026-10-23
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **tool-summary:** mark an MCP failure as an error, not a success
|
|
21
|
+
- **tools:** answer Read failures in the tool's own words
|
|
22
|
+
- **paste:** never swap the bodies of two same-size pastes
|
|
23
|
+
|
|
10
24
|
## [2.82.55] - 2026-10-23
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/dist/input/layout.js
CHANGED
|
@@ -64,28 +64,77 @@ export function pasteReplacement(raw) {
|
|
|
64
64
|
return null;
|
|
65
65
|
return { marker: formatPasteMarker(lines), text };
|
|
66
66
|
}
|
|
67
|
-
// Replace "[Pasted lines#N]" markers with the original pasted text.
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
67
|
+
// Replace "[Pasted lines#N]" markers with the original pasted text.
|
|
68
|
+
//
|
|
69
|
+
// The marker carries ONLY the line count, so two pastes of the same length are
|
|
70
|
+
// indistinguishable in the buffer. The old "last occurrence for the last paste"
|
|
71
|
+
// walk handed the SURVIVING marker the wrong body whenever the operator deleted
|
|
72
|
+
// the other one. The caller can also name the paste it deleted
|
|
73
|
+
// (`deletedIndex`), which removes the ambiguity completely; without it, an
|
|
74
|
+
// ambiguous case (two same-size pastes, exactly one marker left) cannot be
|
|
75
|
+
// resolved from the text, so the marker is left AS IS — sending the literal
|
|
76
|
+
// "[Pasted lines#3]" is visible to the operator, while the wrong body under a
|
|
77
|
+
// plausible marker silently sends the model someone else's text.
|
|
78
|
+
export function expandPastes(pastes, text, deletedIndex) {
|
|
76
79
|
if (!pastes.length)
|
|
77
80
|
return text;
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const src = String(text);
|
|
82
|
+
// Without a deletion hint, expansion is only unambiguous when the number of
|
|
83
|
+
// surviving markers matches the number of pastes (nothing was deleted) or
|
|
84
|
+
// the markers differ (different line counts).
|
|
85
|
+
const occurrences = new Map();
|
|
86
|
+
for (const p of pastes) {
|
|
87
|
+
occurrences.set(p.marker, (occurrences.get(p.marker) || 0) + 1);
|
|
88
|
+
}
|
|
89
|
+
let ambiguous = false;
|
|
90
|
+
for (const [marker, count] of occurrences) {
|
|
91
|
+
let seen = 0;
|
|
92
|
+
let from = 0;
|
|
93
|
+
while (true) {
|
|
94
|
+
const idx = src.indexOf(marker, from);
|
|
95
|
+
if (idx === -1)
|
|
96
|
+
break;
|
|
97
|
+
seen++;
|
|
98
|
+
from = idx + marker.length;
|
|
99
|
+
}
|
|
100
|
+
if (seen === count)
|
|
101
|
+
continue;
|
|
102
|
+
// Fewer markers than pastes means one was deleted; more than one survivor
|
|
103
|
+
// of the same text cannot be attributed.
|
|
104
|
+
if (seen > 1 || count > 1)
|
|
105
|
+
ambiguous = true;
|
|
106
|
+
}
|
|
107
|
+
if (deletedIndex !== undefined)
|
|
108
|
+
ambiguous = false;
|
|
109
|
+
// With a KNOWN deletion index the mapping is exact: the remaining pastes in
|
|
110
|
+
// order own the remaining markers in order.
|
|
111
|
+
const pool = deletedIndex !== undefined &&
|
|
112
|
+
deletedIndex >= 0 &&
|
|
113
|
+
deletedIndex < pastes.length
|
|
114
|
+
? pastes.filter((_p, i) => i !== deletedIndex)
|
|
115
|
+
: pastes;
|
|
116
|
+
let out = src;
|
|
117
|
+
let scanFrom = 0;
|
|
118
|
+
const positions = [];
|
|
119
|
+
for (const p of pool) {
|
|
120
|
+
const idx = out.indexOf(p.marker, scanFrom);
|
|
87
121
|
if (idx === -1)
|
|
88
122
|
continue;
|
|
123
|
+
positions.push({ p, idx });
|
|
124
|
+
scanFrom = idx + p.marker.length;
|
|
125
|
+
}
|
|
126
|
+
const survivors = pool.length;
|
|
127
|
+
const total = pastes.length;
|
|
128
|
+
const lost = total - pool.length;
|
|
129
|
+
const found = positions.length;
|
|
130
|
+
// Every marker left in the text must be accounted for, and every unmatched
|
|
131
|
+
// paste must have actually lost its marker. Anything else is ambiguous.
|
|
132
|
+
if (!ambiguous && (found !== survivors || lost < 0))
|
|
133
|
+
ambiguous = true;
|
|
134
|
+
if (ambiguous)
|
|
135
|
+
return src;
|
|
136
|
+
for (let i = positions.length - 1; i >= 0; i--) {
|
|
137
|
+
const { p, idx } = positions[i];
|
|
89
138
|
out = out.slice(0, idx) + p.text + out.slice(idx + p.marker.length);
|
|
90
139
|
}
|
|
91
140
|
return out;
|
package/dist/shell.js
CHANGED
|
@@ -33,7 +33,12 @@ export function assertCommandInsideRoot(root, command) {
|
|
|
33
33
|
const cdRe = /(?:^|[;&|(\s`])(?:cd|pushd)\s*([^;&|)]+)/gi;
|
|
34
34
|
let m;
|
|
35
35
|
while ((m = cdRe.exec(cmd))) {
|
|
36
|
-
|
|
36
|
+
// The shell removes EVERY quote in the word, wherever it sits —
|
|
37
|
+
// `cd "a"/"../../.."` is one word whose value is `a/../../..`. Stripping
|
|
38
|
+
// only a matching PAIR at the ENDS left the inner quotes in place, so the
|
|
39
|
+
// target was compared as the literal `a/"/../../..` (which normalizes to
|
|
40
|
+
// a path INSIDE the root), passed the guard, and then escaped for real.
|
|
41
|
+
const raw = m[1].trim().replace(/["']/g, '');
|
|
37
42
|
if (!raw || raw === '-')
|
|
38
43
|
continue;
|
|
39
44
|
// A `cd` target we cannot RESOLVE statically is rejected: the shell would
|
package/dist/tool-summary.js
CHANGED
|
@@ -97,6 +97,11 @@ export function looksLikeToolError(result) {
|
|
|
97
97
|
head.startsWith('blocked by') ||
|
|
98
98
|
head.startsWith('denied by') ||
|
|
99
99
|
head.startsWith('unknown tool') ||
|
|
100
|
+
// An MCP failure is returned AS the result (see renderMcpResult):
|
|
101
|
+
// `MCP error: …` for an isError answer, `MCP error (server/tool): …`
|
|
102
|
+
// when the call throws. Neither was recognised, so a failed MCP call was
|
|
103
|
+
// drawn with a green check.
|
|
104
|
+
head.startsWith('mcp error') ||
|
|
100
105
|
head.startsWith('subagents are not available') ||
|
|
101
106
|
head.startsWith('subagent failed') ||
|
|
102
107
|
head.startsWith('subagent error') ||
|
package/dist/tools.js
CHANGED
|
@@ -107,7 +107,30 @@ export function createTools(workdir, { undo, todos, readOnly = false, subagents
|
|
|
107
107
|
// Binary guard (N35): reading an image/archive as utf-8 produced
|
|
108
108
|
// mojibake the model tried to reason about. Detect a binary buffer
|
|
109
109
|
// BEFORE decoding and answer with useful metadata instead.
|
|
110
|
-
|
|
110
|
+
// A read FAILURE (a missing file, a directory, EACCES) is answered in
|
|
111
|
+
// the tool's own words: the raw Node error carries the ABSOLUTE server
|
|
112
|
+
// path and a POSIX-only message, and the loop would wrap it as
|
|
113
|
+
// `Error: ENOENT: …`, which tells the model nothing actionable.
|
|
114
|
+
let buf;
|
|
115
|
+
try {
|
|
116
|
+
buf = await fs.readFile(file);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
const code = e.code;
|
|
120
|
+
if (code === 'ENOENT') {
|
|
121
|
+
return ('File not found: ' +
|
|
122
|
+
p +
|
|
123
|
+
' (check the path; it must be inside the working directory)');
|
|
124
|
+
}
|
|
125
|
+
if (code === 'EISDIR') {
|
|
126
|
+
return (p +
|
|
127
|
+
' is a directory, not a file. Use LS or Glob to list its contents.');
|
|
128
|
+
}
|
|
129
|
+
if (code === 'EACCES' || code === 'EPERM') {
|
|
130
|
+
return 'Permission denied reading ' + p;
|
|
131
|
+
}
|
|
132
|
+
return 'Error reading ' + p + ': ' + e.message;
|
|
133
|
+
}
|
|
111
134
|
if (isImageName(file)) {
|
|
112
135
|
return ('[image: ' +
|
|
113
136
|
p +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.82.
|
|
3
|
+
"version": "2.82.57",
|
|
4
4
|
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen and other web LLMs — 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": {
|