zames_pro 2.82.67 → 2.82.69

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 CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.82.69] - 2026-10-24
11
+
12
+ ### Fixed
13
+
14
+ - **attachments:** keep the extension when a temp name is capped
15
+
16
+ ## [2.82.68] - 2026-10-24
17
+
18
+ ### Fixed
19
+
20
+ - **tools:** do not drop read-only MCP tools from plan mode
21
+
10
22
  ## [2.82.67] - 2026-10-24
11
23
 
12
24
  ### Fixed
@@ -23,11 +23,20 @@ export function contentHash(data) {
23
23
  return createHash('sha1').update(data).digest('hex');
24
24
  }
25
25
  function sanitize(name) {
26
- return (String(name || '')
26
+ const clean = String(name || '')
27
27
  .replace(/[\u0000-\u001f\u007f]/g, '')
28
28
  .replace(/[\\/:*?"<>|]/g, '_')
29
- .replace(/\s+/g, '_')
30
- .slice(0, 80) || 'file');
29
+ .replace(/\s+/g, '_');
30
+ if (!clean)
31
+ return 'file';
32
+ if (clean.length <= 80)
33
+ return clean;
34
+ // The cap must not eat the EXTENSION: a long name became a bare stem, so the
35
+ // temp file had no `.png` and every extension-based check (guessMime,
36
+ // isImageName, the upload dialog) saw a plain blob instead of an image.
37
+ const ext = path.extname(clean).slice(0, 16);
38
+ const stem = clean.slice(0, clean.length - ext.length);
39
+ return stem.slice(0, Math.max(1, 80 - ext.length)) + ext;
31
40
  }
32
41
  export function guessMime(name) {
33
42
  const ext = path.extname(String(name || '')).toLowerCase();
package/dist/tools.js CHANGED
@@ -479,6 +479,10 @@ const MCP_MUTATING_WORDS = new Set(('click type navigate press upload select dra
479
479
  // Multi-word verbs are matched as a substring of the normalized name: they
480
480
  // never stand alone as ONE `_word_`, so a word-set alone missed them.
481
481
  const MCP_MUTATING_PHRASES = ['run_code', 'run_', 'handle_dialog'];
482
+ // Read-only SCOPES that legitimately prefix a verb-like word (`read_...`,
483
+ // `get_...`, `list_...`, `describe_...`): a name like `read_insert_log` is a
484
+ // read action on a log, not an insert. A writer never puts these first.
485
+ const MCP_READ_SCOPES = new Set(['read', 'get', 'list', 'describe', 'fetch']);
482
486
  // A camelCase name is split BEFORE it is lowercased: `browserClick` used to
483
487
  // become `browserclick` (one word, no verb) and slipped through read-only mode.
484
488
  function splitCamelCase(s) {
@@ -494,12 +498,35 @@ export function isMutatingMcpTool(name) {
494
498
  if (MCP_MUTATING_PHRASES.some((p) => norm.includes(p)))
495
499
  return true;
496
500
  const words = norm.split('_').filter(Boolean);
501
+ // A read SCOPE (`read_…`, `get_…`) makes the whole name a READ even when a
502
+ // verb-like word follows: `describe_type` describes a type,
503
+ // `read_insert_log` reads a log. Dropping them from plan mode removed a tool
504
+ // the model may use. Checked BEFORE the word match, so the scope word wins.
505
+ if (MCP_READ_SCOPES.has(words[0]))
506
+ return false;
497
507
  if (words.some((w) => MCP_MUTATING_WORDS.has(w)))
498
508
  return true;
499
509
  // A CONCATENATED style (all lowercase, no separator: `browserclick`,
500
- // `pageevaluate`) still hides a writer. A verb as a substring is safe here
501
- // because every verb is a whole action word no read-only tool contains.
502
- return [...MCP_MUTATING_WORDS].some((v) => norm.includes(v));
510
+ // `pageevaluate`) still hides a writer. A BARE substring test is too
511
+ // aggressive, though: `get_settings` contains `set`, `get_keywords`
512
+ // contains `key`, `read_insert_log` contains `insert`, `describe_type`
513
+ // contains `type` — genuine READ-ONLY tools were dropped from plan mode.
514
+ // A concatenated verb is only trusted when it is a PREFIX of the name
515
+ // (`browserclick` -> after the `browser` scope) or spans the WHOLE name
516
+ // (`pageevaluate`); a verb buried inside another word is not an action.
517
+ const compact = norm.replace(/_/g, '');
518
+ const lastWord = words[words.length - 1];
519
+ return [...MCP_MUTATING_WORDS].some((v) => {
520
+ if (lastWord === v)
521
+ return true;
522
+ // A concatenated writer glues the verb to a SCOPE word: `fileupload`,
523
+ // `mouseclick`, `codeexecute`. Only the scope+verb pair counts — a bare
524
+ // word that merely CONTAINS the letters (`readinsertlog`,
525
+ // `describetype`) is a read action on a thing, not a write.
526
+ if (!compact.endsWith(v) || compact === v)
527
+ return false;
528
+ return !MCP_READ_SCOPES.has(compact.slice(0, -v.length));
529
+ });
503
530
  }
504
531
  // Merge MCP tools into the base set. In read-only (plan) mode the MCP tools
505
532
  // must be filtered too: an MCP server (e.g. @playwright/mcp) exposes mutating
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.82.67",
3
+ "version": "2.82.69",
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": {