zames_pro 2.78.0 → 2.78.1
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 +9 -0
- package/dist/diagnostics.js +10 -4
- package/dist/hooks.js +12 -1
- package/dist/permissions.js +6 -2
- package/dist/statusline.js +13 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.78.1] - 2026-10-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **permissions:** match a command rule against every plausible command key
|
|
15
|
+
- **hooks:** cap a PreToolUse denial so it cannot flood the tool result
|
|
16
|
+
- **diagnostics:** treat a zero-error watch banner as clean
|
|
17
|
+
- **statusline:** cap the hook output so a chatty command cannot flood the row
|
|
18
|
+
|
|
10
19
|
## [2.78.0] - 2026-10-18
|
|
11
20
|
|
|
12
21
|
### Added
|
package/dist/diagnostics.js
CHANGED
|
@@ -23,11 +23,17 @@ export function summarizeDiagnostics(raw, maxChars) {
|
|
|
23
23
|
const text = String(raw || '').trim();
|
|
24
24
|
if (!text)
|
|
25
25
|
return null;
|
|
26
|
-
// tsc prints nothing on success
|
|
27
|
-
//
|
|
28
|
-
|
|
26
|
+
// tsc prints nothing on success, but with `--watch` it prints a banner
|
|
27
|
+
// ("Found 0 errors. Watching for file changes.") that is longer than the
|
|
28
|
+
// old 40-char cutoff — so every edit in watch mode appended a pointless
|
|
29
|
+
// diagnostics block to the tool result. Treat a ZERO-count banner as clean
|
|
30
|
+
// however long it is; a non-zero count still falls through to the lines.
|
|
31
|
+
if (/^(?:found\s+)?0\s+errors?\b/im.test(text))
|
|
32
|
+
return null;
|
|
33
|
+
if (/^no errors?\.?$/im.test(text))
|
|
34
|
+
return null;
|
|
35
|
+
if (/^0 problems?\b/im.test(text))
|
|
29
36
|
return null;
|
|
30
|
-
}
|
|
31
37
|
// Keep only error-looking lines when we can recognise them, so a long build
|
|
32
38
|
// log collapses to the actual problems. Fall back to the whole output.
|
|
33
39
|
const lines = text.split(/\r?\n/);
|
package/dist/hooks.js
CHANGED
|
@@ -12,6 +12,9 @@ const LIFECYCLE_EVENTS = [
|
|
|
12
12
|
// tool call must not be held hostage by it.
|
|
13
13
|
const HOOK_TIMEOUT_MS = 10_000;
|
|
14
14
|
const HOOK_MAX_BUFFER = 1024 * 1024;
|
|
15
|
+
// A PreToolUse denial becomes the tool result the model reads, so it is capped
|
|
16
|
+
// like any other tool output.
|
|
17
|
+
const HOOK_DENIAL_CAP = 4000;
|
|
15
18
|
export function hooksConfigPath(workdir) {
|
|
16
19
|
return path.join(workdir, '.zames', 'hooks.json');
|
|
17
20
|
}
|
|
@@ -142,7 +145,15 @@ export async function runPreToolUse(hooks, tool, args, workdir) {
|
|
|
142
145
|
const r = await runHook(entry, 'PreToolUse', tool, args, workdir);
|
|
143
146
|
if (r.code !== 0) {
|
|
144
147
|
const reason = (r.stderr || r.stdout).trim();
|
|
145
|
-
|
|
148
|
+
// The denial text becomes a TOOL RESULT the model reads: a hook that
|
|
149
|
+
// dumps a log must not flood the context with one call.
|
|
150
|
+
const capped = reason.slice(0, HOOK_DENIAL_CAP);
|
|
151
|
+
const suffix = reason.length > HOOK_DENIAL_CAP
|
|
152
|
+
? '\n… [denial truncated: ' +
|
|
153
|
+
(reason.length - HOOK_DENIAL_CAP) +
|
|
154
|
+
' chars omitted]'
|
|
155
|
+
: '';
|
|
156
|
+
return capped + suffix || `hook exited with code ${r.code}`;
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
return null;
|
package/dist/permissions.js
CHANGED
|
@@ -63,6 +63,10 @@ function safeRegex(source) {
|
|
|
63
63
|
// A file path may arrive in any of these keys depending on the tool.
|
|
64
64
|
// We collect them all and match the path regex against ANY of them.
|
|
65
65
|
const PATH_KEYS = ['path', 'file_path', 'notebook_path', 'from', 'to'];
|
|
66
|
+
// The command may be named differently by an MCP tool (its JSON Schema picks
|
|
67
|
+
// the field name) or invented by the model. Checking every plausible key keeps
|
|
68
|
+
// a `command` rule from being bypassed just because the field is called `cmd`.
|
|
69
|
+
const COMMAND_KEYS = ['command', 'cmd', 'shell_command', 'script', 'argv'];
|
|
66
70
|
// Best-effort check whether a string looks like a filesystem path. Used for
|
|
67
71
|
// tools whose arguments are not covered by PATH_KEYS (MCP tools take arbitrary
|
|
68
72
|
// arg names from their JSON Schema), so a `path` rule is not silently
|
|
@@ -106,7 +110,7 @@ export function decidePermission(policy, tool, args) {
|
|
|
106
110
|
if (!policy)
|
|
107
111
|
return { action: 'allow', reason: 'no policy' };
|
|
108
112
|
const a = (args ?? {});
|
|
109
|
-
const
|
|
113
|
+
const commands = COMMAND_KEYS.filter((k) => typeof a[k] === 'string').map((k) => a[k]);
|
|
110
114
|
const paths = collectPathCandidates(tool, args);
|
|
111
115
|
for (const r of policy.rules) {
|
|
112
116
|
if (r.tool) {
|
|
@@ -116,7 +120,7 @@ export function decidePermission(policy, tool, args) {
|
|
|
116
120
|
}
|
|
117
121
|
if (r.command) {
|
|
118
122
|
const re = safeRegex(r.command);
|
|
119
|
-
if (!re || !
|
|
123
|
+
if (!re || !commands.some((c) => re.test(c)))
|
|
120
124
|
continue;
|
|
121
125
|
}
|
|
122
126
|
if (r.path) {
|
package/dist/statusline.js
CHANGED
|
@@ -26,6 +26,10 @@ export function runStatusLineCommand(workdir, command, info, timeoutMs = 4000) {
|
|
|
26
26
|
const first = String(out).split(/\r?\n/)[0].trim();
|
|
27
27
|
resolve(first);
|
|
28
28
|
};
|
|
29
|
+
// Only the FIRST LINE is shown, so there is no reason to keep collecting a
|
|
30
|
+
// big blob: a chatty command (a JSON dump, `ls -R`) would grow `out`
|
|
31
|
+
// unboundedly and the status row then repainted megabytes. Stop appending
|
|
32
|
+
// once a short line is buffered (the rest of that line does not matter).
|
|
29
33
|
const timer = setTimeout(() => {
|
|
30
34
|
try {
|
|
31
35
|
child.kill();
|
|
@@ -33,8 +37,16 @@ export function runStatusLineCommand(workdir, command, info, timeoutMs = 4000) {
|
|
|
33
37
|
catch { }
|
|
34
38
|
finish();
|
|
35
39
|
}, timeoutMs);
|
|
40
|
+
// A status line is a short badge: cap what is buffered so a chatty
|
|
41
|
+
// command cannot grow it (and the rendered row) without bound.
|
|
42
|
+
const MAX_OUT = 400;
|
|
36
43
|
child.stdout?.on('data', (c) => {
|
|
37
|
-
out
|
|
44
|
+
if (out.length >= MAX_OUT)
|
|
45
|
+
return;
|
|
46
|
+
// A chunk can be huge (one 64 KB read); clip it to the remaining space
|
|
47
|
+
// so the buffer never exceeds MAX_OUT. An unbounded `out` made the
|
|
48
|
+
// status row repaint megabytes on every tick.
|
|
49
|
+
out += c.toString().slice(0, MAX_OUT - out.length);
|
|
38
50
|
});
|
|
39
51
|
child.on('error', () => {
|
|
40
52
|
clearTimeout(timer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.78.
|
|
3
|
+
"version": "2.78.1",
|
|
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": {
|