zames_pro 2.43.0 → 2.44.0

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.
@@ -773,10 +773,20 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
773
773
  export function truncateToolResult(text, limit) {
774
774
  if (text.length <= limit)
775
775
  return text;
776
- const omitted = text.length - limit;
777
- return (text.slice(0, limit) +
778
- String.fromCharCode(10) +
779
- `[...truncated ${omitted} chars]`);
776
+ // Prefer a LINE boundary near the limit so the model does not read a line
777
+ // cut in half (a half line is easy to misread as the real content). We look
778
+ // for the last newline within the last 20% of the window; if none, fall back
779
+ // to the hard char cut.
780
+ let cut = limit;
781
+ const NL = String.fromCharCode(10);
782
+ const searchFrom = Math.floor(limit * 0.8);
783
+ const nl = text.lastIndexOf(NL, limit);
784
+ // Cut AFTER the newline (include it) so the kept part is a whole number of
785
+ // lines — a cut that EXCLUDED the newline left a half line before the marker.
786
+ if (nl >= searchFrom)
787
+ cut = nl + 1;
788
+ const omitted = text.length - cut;
789
+ return text.slice(0, cut) + NL + `[...truncated ${omitted} chars]`;
780
790
  }
781
791
  // The answer looks like a tool call, but parseToolCall() did not recognize it.
782
792
  // Used as a safeguard against "the agent called a tool and stopped": in that
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
+ import { createHash } from 'crypto';
4
5
  import { execFileSync } from 'child_process';
5
6
  import { extractPathToken } from './path-token.js';
6
7
  export { extractPathToken };
@@ -15,6 +16,11 @@ export const IMAGE_EXTS = [
15
16
  '.ico',
16
17
  ];
17
18
  export const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
19
+ // Stable content hash for de-duplicating identical attachments (a pasted image
20
+ // is saved to a NEW temp file every time, so its path is not a stable identity).
21
+ export function contentHash(data) {
22
+ return createHash('sha1').update(data).digest('hex');
23
+ }
18
24
  function sanitize(name) {
19
25
  return (String(name || '')
20
26
  .replace(/[\u0000-\u001f\u007f]/g, '')
@@ -139,10 +145,10 @@ export class AttachmentStore {
139
145
  this.fileCount = 0;
140
146
  }
141
147
  add(item) {
142
- // De-duplicate by real path: pasting the SAME file twice must reuse one
143
- // attachment (one marker, one upload), not create [file#1] and [file#2].
144
- // The operator saw exactly that when inserting the same file repeatedly.
145
- const existing = this.items.find((a) => a.path === item.path);
148
+ // De-duplicate by real path (files/paths) OR by content hash (images and
149
+ // clipboard data, which are re-saved to a fresh temp file on every paste).
150
+ // The operator saw the SAME file become [file#1] and [file#2].
151
+ const existing = this.items.find((a) => a.path === item.path || (item.hash && a.hash && a.hash === item.hash));
146
152
  if (existing)
147
153
  return existing;
148
154
  const image = isImageName(item.name) || String(item.mime).startsWith('image/');
@@ -156,6 +162,7 @@ export class AttachmentStore {
156
162
  image,
157
163
  marker,
158
164
  size: Number(item.size) || 0,
165
+ hash: item.hash,
159
166
  };
160
167
  this.items.push(att);
161
168
  return att;
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { createTools } from './tools.js';
9
9
  import { runAgentLoop } from './agent-loop.js';
10
10
  import { createSpinner } from './spinner.js';
11
11
  import { LineEditor, expandPastes, pasteReplacement, formatCompactTokens, } from './input.js';
12
- import { parseImagePaste, extForMime, saveToTemp, guessMime, isImageName, formatSize, looksLikeFilePath, readClipboardImageDetailed, sniffMime, readWindowsClipboardFiles, hasClipboardTool, } from './attachments.js';
12
+ import { parseImagePaste, extForMime, saveToTemp, guessMime, isImageName, formatSize, looksLikeFilePath, readClipboardImageDetailed, sniffMime, readWindowsClipboardFiles, hasClipboardTool, contentHash, } from './attachments.js';
13
13
  import { loadConfig, DEFAULTS, CONFIG_PATHS, ZAMES_HOME, CONFIG_SCHEMA, getByPath, validateConfigValue, writeConfigValue, resetConfigValue, } from './config.js';
14
14
  import { runConfigMenu } from './config-menu.js';
15
15
  import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n.js';
@@ -1506,6 +1506,7 @@ async function main() {
1506
1506
  name,
1507
1507
  mime: image.mime,
1508
1508
  size: image.data.length,
1509
+ hash: contentHash(image.data),
1509
1510
  });
1510
1511
  ed.printAbove(theme.system(t('msg.attached_image', {
1511
1512
  marker: att.marker,
@@ -1579,6 +1580,7 @@ async function main() {
1579
1580
  name,
1580
1581
  mime,
1581
1582
  size: res.data.length,
1583
+ hash: contentHash(res.data),
1582
1584
  });
1583
1585
  ed.printAbove(theme.system(t('msg.attached_image', {
1584
1586
  marker: att.marker,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.43.0",
3
+ "version": "2.44.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {