zames_pro 2.6.0 → 2.7.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.
package/dist/spinner.js CHANGED
@@ -2,8 +2,8 @@ import ora from 'ora';
2
2
  import { theme } from './theme.js';
3
3
  import { renderMarkdown } from './markdown.js';
4
4
  import { translate } from './i18n.js';
5
- // Фразы для спиннера «агент работает». Выбираются в случайном порядке.
6
- // Текст берётся из i18n по ключу 'spinner.phrases' (строки разделены «|»).
5
+ // Phrases for the "agent is working" spinner. Chosen in random order.
6
+ // The text comes from i18n via the key 'spinner.phrases' (strings separated by "|").
7
7
  export function randomThinkingPhrase(locale = 'ru') {
8
8
  const raw = translate(locale)('spinner.phrases');
9
9
  const phrases = raw.split('|').filter((s) => s.trim().length);
@@ -11,7 +11,7 @@ export function randomThinkingPhrase(locale = 'ru') {
11
11
  return '';
12
12
  return phrases[Math.floor(Math.random() * phrases.length)];
13
13
  }
14
- // Убираем завершающее многоточие из фразы — точки анимируем отдельно.
14
+ // Strip the trailing ellipsis from the phrase — dots are animated separately.
15
15
  export function stripEllipsis(phrase) {
16
16
  return phrase.replace(/[.…]+\s*$/, '');
17
17
  }
@@ -20,8 +20,8 @@ export function createSpinner(locale = 'ru') {
20
20
  let dotTimer = null;
21
21
  let dotPhase = 0;
22
22
  let pending = null;
23
- // Анимация точек: старт с пустой строки (0 точек), затем рост.
24
- // Ширину выравниваем по максимуму (3), чтобы подсказка не смещалась.
23
+ // Dot animation: start from an empty string (0 dots), then grow.
24
+ // We align the width to the maximum (3) so the hint doesn't shift.
25
25
  const DOTS = ['', '.', '..', '...'];
26
26
  const DOTS_PAD = ' ';
27
27
  const start = (text) => {
@@ -38,14 +38,14 @@ export function createSpinner(locale = 'ru') {
38
38
  if (spinner)
39
39
  spinner.stop();
40
40
  };
41
- // Подсказка в строке статуса: во время работы агента терминал живой,
42
- // можно печатать следующее сообщение. Без неё это неочевидно.
41
+ // Hint in the status line: while the agent works the terminal is live,
42
+ // so you can type the next message. Without it this is not obvious.
43
43
  const HINT = theme.dim(' · ' + translate(locale)('spinner.hint'));
44
- // Запуск анимированного статуса: коричневый текст + «бегущие» точки.
44
+ // Start the animated status: brown text + "running" dots.
45
45
  const startThinking = () => {
46
46
  const base = theme.brown(stripEllipsis(randomThinkingPhrase(locale)));
47
- // Точки того же цвета, что и база, и фиксированной ширины — иначе
48
- // подсказка справа «прыгает» при смене фазы анимации.
47
+ // The dots are the same color as the base and of fixed width — otherwise
48
+ // the hint on the right "jumps" when the animation phase changes.
49
49
  const dots = (n) => theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
50
50
  start(base + dots(0) + HINT);
51
51
  dotPhase = 0;
@@ -60,8 +60,8 @@ export function createSpinner(locale = 'ru') {
60
60
  if (dotTimer.unref)
61
61
  dotTimer.unref();
62
62
  };
63
- // Показать набранный, но ещё не отправленный текст вместо спиннера.
64
- // Позволяет печатать сообщение прямо во время работы агента.
63
+ // Show the typed but not yet sent text instead of the spinner.
64
+ // Lets you type a message right while the agent is working.
65
65
  const showPending = () => {
66
66
  if (dotTimer) {
67
67
  clearInterval(dotTimer);
@@ -80,8 +80,8 @@ export function createSpinner(locale = 'ru') {
80
80
  }
81
81
  startThinking();
82
82
  },
83
- // Текст, который пользователь набирает во время работы агента.
84
- // Пустая строка / null — вернуть обычный спиннер.
83
+ // The text the user types while the agent is working.
84
+ // Empty string / null — restore the regular spinner.
85
85
  setPending: (text) => {
86
86
  pending = text && String(text).length ? String(text) : null;
87
87
  if (pending)
@@ -104,12 +104,16 @@ export function createSpinner(locale = 'ru') {
104
104
  stop();
105
105
  const NL = String.fromCharCode(10);
106
106
  const rendered = renderMarkdown(msg);
107
- // Маркер ответа модели: помогает визуально отделить его от ввода
108
- // пользователя (который подсвечен приглашением с золотой стрелкой).
107
+ // Model answer marker: helps visually separate it from the user's input
108
+ // (which is highlighted by the prompt with a golden arrow).
109
109
  console.log(NL + theme.assistant('● Ответ') + NL);
110
110
  console.log(rendered);
111
111
  console.log(theme.dim('─'.repeat(60)) + NL);
112
112
  },
113
+ warning: (msg) => {
114
+ stop();
115
+ console.log(String.fromCharCode(10) + theme.warn('⚠ ' + msg));
116
+ },
113
117
  stop,
114
118
  succeed: () => { },
115
119
  fail: () => { },
@@ -65,6 +65,29 @@ To give a final answer to the user, respond with:
65
65
 
66
66
  {"tool": "respond", "args": {"message": "your final answer here"}}
67
67
 
68
+ ## TOOL CALL FORMAT - EXACT REQUIREMENTS (read carefully)
69
+
70
+ The parser is strict about the SHAPE of your call. These mistakes have caused
71
+ the agent to silently stall (the call was not recognized, so the task ended):
72
+
73
+ 1. ALWAYS wrap the call in curly braces { ... }. Never emit tool-colon-quote
74
+ without the leading brace. Never drop the very first character.
75
+ 2. ALWAYS put DOUBLE quotes around keys and string values, e.g.
76
+ {"tool": "Read", "args": {"path": "a.ts"}}.
77
+ NEVER use single quotes; that is not valid JSON.
78
+ 3. NEVER wrap the call in markdown fences or in XML/DSML tags
79
+ (invoke, parameter, tool_calls, DSML variants). Plain JSON only.
80
+ 4. NEVER put prose before or after the JSON in the same response. Not even
81
+ a short lead-in. The whole response is the JSON.
82
+ 5. Do NOT truncate long calls. If a Bash command, file content, or Edit is
83
+ large, SPLIT it: run several smaller Bash commands, or write the file in
84
+ parts (Write then Edit). A cut-off JSON never parses and stalls the agent.
85
+ 6. One object per call; to call several independent tools at once, use a JSON
86
+ ARRAY of objects, still no prose around it.
87
+
88
+ If you catch yourself about to emit anything other than a bare JSON object or
89
+ array, stop and reformat it first. A malformed call is worse than a slow one.
90
+
68
91
  ## Git
69
92
 
70
93
  - You CAN commit and push using GitAdd / GitCommit / GitPush. Prefer these over raw \`git\` through Bash.
package/dist/theme.js CHANGED
@@ -1,32 +1,32 @@
1
1
  import chalk from 'chalk';
2
- // Спокойная палитра для тёмного терминала.
3
- // Приглушённые, слегка «выцветшие» тона без высокой насыщенности
4
- // (в духе Tokyo Night / Nord). Задача — комфортное чтение длинных
5
- // ответов, а не максимальная контрастность.
2
+ // A calm palette for a dark terminal.
3
+ // Muted, slightly "faded" tones without high saturation
4
+ // (in the spirit of Tokyo Night / Nord). The goal is comfortable reading of
5
+ // long answers, not maximum contrast.
6
6
  //
7
- // Роли:
8
- // - user — мягкий лавандово-серый (служебные сообщения)
9
- // - prompt — тёплое золотистое (приглашение ввода, заметное)
10
- // - dir — светло-голубое (имя рабочей директории)
11
- // - assistant — тёплый песочный (ответы модели)
12
- // - tool — приглушённый янтарный
13
- // - toolResult — серо-голубой
14
- // - system — нейтральный серый
15
- // - warn — спокойный охра
16
- // - error — приглушённый терракот
7
+ // Roles:
8
+ // - user — soft lavender-gray (service messages)
9
+ // - prompt — warm gold (input prompt, noticeable)
10
+ // - dir — light blue (working directory name)
11
+ // - assistant — warm sand (model answers)
12
+ // - tool — muted amber
13
+ // - toolResult — gray-blue
14
+ // - system — neutral gray
15
+ // - warn — calm ochre
16
+ // - error — muted terracotta
17
17
  export const theme = {
18
- user: chalk.hex('#b4b8d0'), // мягкий лавандово-серый
19
- prompt: chalk.hex('#c8b06a').bold, // приглашение ввода — тёплое золотистое, заметное
20
- dir: chalk.hex('#7fc4f0'), // имя рабочей директории — заметно голубое
21
- assistant: chalk.hex('#cfc9b0'), // тёплый песочный
22
- tool: chalk.hex('#c6a97e'), // приглушённый янтарный
23
- toolResult: chalk.hex('#8a9bb5'), // серо-голубой
24
- system: chalk.hex('#808896'), // нейтральный серый
25
- dim: chalk.hex('#5b616e'), // тёмно-серый
26
- warn: chalk.hex('#c9a86a'), // спокойный охра
27
- error: chalk.hex('#c98a80'), // приглушённый терракот
28
- success: chalk.hex('#a9c08c'), // мягкий шалфейный
29
- brown: chalk.hex('#a1723f'), // коричневый (статус/спиннер)
18
+ user: chalk.hex('#b4b8d0'), // soft lavender-gray
19
+ prompt: chalk.hex('#c8b06a').bold, // input prompt — warm gold, noticeable
20
+ dir: chalk.hex('#7fc4f0'), // working directory name — noticeably blue
21
+ assistant: chalk.hex('#cfc9b0'), // warm sand
22
+ tool: chalk.hex('#c6a97e'), // muted amber
23
+ toolResult: chalk.hex('#8a9bb5'), // gray-blue
24
+ system: chalk.hex('#808896'), // neutral gray
25
+ dim: chalk.hex('#5b616e'), // dark gray
26
+ warn: chalk.hex('#c9a86a'), // calm ochre
27
+ error: chalk.hex('#c98a80'), // muted terracotta
28
+ success: chalk.hex('#a9c08c'), // soft sage
29
+ brown: chalk.hex('#a1723f'), // brown (status/spinner)
30
30
  bold: chalk.bold,
31
31
  };
32
32
  export default theme;
package/dist/tools.js CHANGED
@@ -7,16 +7,16 @@ export function createTools(workdir, { undo } = {}) {
7
7
  const root = path.resolve(workdir);
8
8
  const safe = (p) => {
9
9
  const resolved = path.resolve(root, p);
10
- // startsWith(root) пропускал бы соседние пути с общим префиксом
11
- // (C:\work\proj vs C:\work\proj-old). Считаем через relative().
10
+ // startsWith(root) would let through sibling paths with a common prefix
11
+ // (C:\work\proj vs C:\work\proj-old). We compute via relative().
12
12
  const rel = path.relative(root, resolved);
13
13
  if (rel.startsWith('..') || path.isAbsolute(rel)) {
14
14
  throw new Error(`Доступ за пределы рабочей директории: ${p}`);
15
15
  }
16
16
  return resolved;
17
17
  };
18
- // Sandbox (вариант A): не даём команде выйти выше root.
19
- // Это защитный барьер, а не полноценная изоляция ОС.
18
+ // Sandbox (option A): don't let the command go above root.
19
+ // This is a protective barrier, not full OS isolation.
20
20
  const assertCommandInsideRoot = (command) => {
21
21
  const cmd = String(command || '');
22
22
  const cdRe = /(?:^|[;&|]|\s)(?:cd|pushd)\s+([^;&|]+)/gi;
@@ -74,10 +74,10 @@ export function createTools(workdir, { undo } = {}) {
74
74
  resolve(parts.join('\n'));
75
75
  });
76
76
  });
77
- // Достаёт текст из content/content_base64. base64 нужен, потому что канал
78
- // передачи ответа модели может искажать символы ($, обратные слэши,
79
- // переводы строк). base64 состоит только из [A-Za-z0-9+/=] и искажению
80
- // не подвержен.
77
+ // Extracts text from content/content_base64. base64 is needed because the
78
+ // channel that delivers the model's answer may corrupt characters ($,
79
+ // backslashes, newlines). base64 consists only of [A-Za-z0-9+/=] and is not
80
+ // subject to corruption.
81
81
  const decodeContent = (content, contentBase64) => {
82
82
  if (typeof contentBase64 === 'string' && contentBase64.length) {
83
83
  return Buffer.from(contentBase64, 'base64').toString('utf-8');
@@ -172,7 +172,7 @@ export function createTools(workdir, { undo } = {}) {
172
172
  const { glob } = await import('fs/promises');
173
173
  const results = [];
174
174
  for await (const f of glob(String(pattern), { cwd: workdir })) {
175
- // Sandbox: игнорируем всё, что выходит за пределы root.
175
+ // Sandbox: ignore anything that goes outside root.
176
176
  const abs = path.resolve(workdir, f);
177
177
  const rel = path.relative(root, abs);
178
178
  if (rel.startsWith('..') || path.isAbsolute(rel))
@@ -17,8 +17,8 @@ export class Transcript {
17
17
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
18
18
  this.file = path.join(dir, `${sessionName}-${stamp}.jsonl`);
19
19
  this.stream = fs.createWriteStream(this.file, { flags: 'a' });
20
- // Ошибки записи (диск переполнен, файл удалён и т.п.) приходят
21
- // событием 'error'; без слушателя это uncaught exception.
20
+ // Write errors (disk full, file deleted, etc.) arrive as an 'error'
21
+ // event; without a listener this is an uncaught exception.
22
22
  this.stream.on('error', (e) => {
23
23
  console.error(`transcript: ошибка записи: ${e.message}`);
24
24
  this.enabled = false;
package/dist/types.js CHANGED
@@ -1,3 +1,3 @@
1
- // Общие типы-контракты для всего агента.
2
- // Здесь только типы — ни рантайм-кода, ни побочных эффектов.
1
+ // Shared contract types for the whole agent.
2
+ // Types only here — no runtime code, no side effects.
3
3
  export {};
package/dist/web.js CHANGED
@@ -1,27 +1,27 @@
1
1
  import { chromium } from 'playwright';
2
2
  const DEFAULT_TIMEOUT = 20_000;
3
3
  const MAX_TEXT = 12_000;
4
- // Убирает скрипты, стили, и превращает HTML в читабельный текст.
4
+ // Removes scripts, styles, and turns HTML into readable text.
5
5
  function htmlToText(html) {
6
- // Удаляем блоки, которые не нужны
6
+ // Remove blocks we don't need
7
7
  let s = html
8
8
  .replace(/<script[\s\S]*?<\/script>/gi, '')
9
9
  .replace(/<style[\s\S]*?<\/style>/gi, '')
10
10
  .replace(/<noscript[\s\S]*?<\/noscript>/gi, '')
11
11
  .replace(/<svg[\s\S]*?<\/svg>/gi, '')
12
12
  .replace(/<!--[\s\S]*?-->/g, '');
13
- // Ссылки: <a href="URL">text</a> → text (URL)
13
+ // Links: <a href="URL">text</a> → text (URL)
14
14
  s = s.replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, text) => {
15
15
  const t = text.replace(/<[^>]+>/g, '').trim();
16
16
  return t ? `${t} (${href})` : href;
17
17
  });
18
- // Заголовки, параграфы, BR → переводы строк
18
+ // Headings, paragraphs, BR → newlines
19
19
  s = s
20
20
  .replace(/<\/(h[1-6]|p|div|li|tr|section|article)>/gi, '\n')
21
21
  .replace(/<br\s*\/?>/gi, '\n')
22
22
  .replace(/<li[^>]*>/gi, '- ')
23
23
  .replace(/<[^>]+>/g, '');
24
- // HTML entities — базовые
24
+ // HTML entities — the basic ones
25
25
  s = s
26
26
  .replace(/&nbsp;/g, ' ')
27
27
  .replace(/&amp;/g, '&')
@@ -34,7 +34,7 @@ function htmlToText(html) {
34
34
  .replace(/&hellip;/g, '…')
35
35
  .replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
36
36
  .replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16)));
37
- // Сжимаем пустые строки
37
+ // Collapse blank lines
38
38
  s = s
39
39
  .split('\n')
40
40
  .map((l) => l.replace(/[ \t]+/g, ' ').trim())
@@ -42,7 +42,7 @@ function htmlToText(html) {
42
42
  .join('\n');
43
43
  return s;
44
44
  }
45
- // fetch с редиректами и таймаутом
45
+ // fetch with redirects and a timeout
46
46
  async function httpFetch(url, { timeout = DEFAULT_TIMEOUT, headers = {} } = {}) {
47
47
  const ctrl = new AbortController();
48
48
  const t = setTimeout(() => ctrl.abort(), timeout);
@@ -64,8 +64,8 @@ async function httpFetch(url, { timeout = DEFAULT_TIMEOUT, headers = {} } = {})
64
64
  clearTimeout(t);
65
65
  }
66
66
  }
67
- // Отдельный headless-браузер для JS-страниц.
68
- // Ленивая инициализация, чтобы не тратить ресурсы впустую.
67
+ // A separate headless browser for JS pages.
68
+ // Lazy initialization so we don't waste resources.
69
69
  let _headless = null;
70
70
  async function getHeadless() {
71
71
  if (_headless)
@@ -96,7 +96,7 @@ async function renderWithHeadless(url, { timeout = DEFAULT_TIMEOUT } = {}) {
96
96
  waitUntil: 'domcontentloaded',
97
97
  timeout,
98
98
  });
99
- // Дать JS немного времени дорисовать
99
+ // Give JS a little time to finish rendering
100
100
  await page.waitForTimeout(1500);
101
101
  const html = await page.content();
102
102
  const status = resp ? resp.status() : 0;
@@ -107,7 +107,7 @@ async function renderWithHeadless(url, { timeout = DEFAULT_TIMEOUT } = {}) {
107
107
  await ctx.close();
108
108
  }
109
109
  }
110
- // ---------- инструменты ----------
110
+ // ---------- tools ----------
111
111
  export function createWebTools() {
112
112
  return [
113
113
  {
@@ -132,14 +132,14 @@ export function createWebTools() {
132
132
  return formatResult(r.status, r.url, text, limit);
133
133
  }
134
134
  const r = await httpFetch(String(url));
135
- // Если это JSON/plain text — вернуть как есть
135
+ // If it's JSON/plain text — return as is
136
136
  if (/application\/json|text\/plain|text\/markdown/i.test(r.contentType)) {
137
137
  const trimmed = r.body.slice(0, limit);
138
138
  return `HTTP ${r.status} ${r.url}\nContent-Type: ${r.contentType}\n\n${trimmed}`;
139
139
  }
140
- // HTML — почистить
140
+ // HTML — clean it up
141
141
  const text = htmlToText(r.body);
142
- // Если текста почти нет — вероятно JS-страница, дать намёк
142
+ // If there's almost no text — probably a JS page, give a hint
143
143
  if (text.length < 200) {
144
144
  return (`HTTP ${r.status} ${r.url}\n` +
145
145
  `Content-Type: ${r.contentType}\n\n` +
@@ -177,13 +177,13 @@ export function createWebTools() {
177
177
  if (r.status !== 200) {
178
178
  return `DuckDuckGo вернул HTTP ${r.status}`;
179
179
  }
180
- // Парсим простыми регексами. Формат html.duckduckgo.com стабильный.
180
+ // We parse with simple regexes. The html.duckduckgo.com format is stable.
181
181
  const results = [];
182
182
  const re = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?(?:<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>)?/gi;
183
183
  let m;
184
184
  while ((m = re.exec(r.body)) && results.length < limit) {
185
185
  let href = m[1];
186
- // DuckDuckGo заворачивает ссылки в редирект вида /l/?uddg=...
186
+ // DuckDuckGo wraps links in a redirect like /l/?uddg=...
187
187
  const uddgMatch = href.match(/[?&]uddg=([^&]+)/);
188
188
  if (uddgMatch)
189
189
  href = decodeURIComponent(uddgMatch[1]);
@@ -13,13 +13,13 @@ function readAttr(attrs, name) {
13
13
  const m = attrs.match(re);
14
14
  return m ? m[2] : null;
15
15
  }
16
- // Достаёт параметры из тела <invoke>. Тег открытия и закрытия может нести
17
- // произвольный префикс (DSML и т.п.), а некоторые модели путают пары тегов
18
- // (<parameter …> … </|DSML| parameter>). Регулярка ниже матчит любую пару
19
- // <…parameter…> … </…parameter…>, поэтому такие «смешанные» теги тоже
20
- // разбираются. Значения дедуплицируются: если модель повторила parameter
21
- // несколько раз, берём ПЕРВОЕ значение, а не последнее (повторы обычно
22
- // дублируют корректный параметр).
16
+ // Extracts parameters from an <invoke> body. The opening and closing tags may
17
+ // carry an arbitrary prefix (DSML, etc.), and some models confuse tag pairs
18
+ // (<parameter …> … </|DSML| parameter>). The regex below matches any pair
19
+ // <…parameter…> … </…parameter…>, so such "mixed" tags are parsed too.
20
+ // Values are deduplicated: if the model repeated a parameter several times,
21
+ // we take the FIRST value, not the last (repeats usually duplicate a correct
22
+ // parameter).
23
23
  function parseParameters(body) {
24
24
  const args = {};
25
25
  const paramRe = /<[^>]*?parameter([^>]*)>([\s\S]*?)<\/[^>]*?parameter[^>]*>/gi;
@@ -57,8 +57,8 @@ export function parseXmlToolCalls(text) {
57
57
  const closeMatch = closeRe.exec(rest);
58
58
  const body = closeMatch ? rest.slice(0, closeMatch.index) : rest;
59
59
  const args = parseParameters(body);
60
- // Некоторые модели кладут весь JSON-объект аргументов в один
61
- // parameter с именем args. Разворачиваем, чтобы не было
60
+ // Some models put the whole JSON arguments object into a single
61
+ // parameter named args. We unwrap it so there's no
62
62
  // {args: {args: {...}}}.
63
63
  let finalArgs = args;
64
64
  const keys = Object.keys(args);
@@ -73,10 +73,10 @@ export function parseXmlToolCalls(text) {
73
73
  }
74
74
  if (calls.length)
75
75
  return calls.length === 1 ? calls[0] : calls;
76
- // Модель иногда опускает тег <invoke> и оставляет только DSML-обёртку
77
- // (<|DSML| calls> … <|DSML| parameter name=…>…). Имя инструмента в таком
78
- // ответе отсутствует, поэтому восстановить вызов нельзя — но и молча
79
- // принимать его за финальный ответ нельзя: agent-loop попросит повторить
80
- // (looksLikeToolCall срабатывает по слову parameter/DSML).
76
+ // The model sometimes omits the <invoke> tag and leaves only the DSML
77
+ // wrapper (<|DSML| calls> … <|DSML| parameter name=…>…). There's no tool
78
+ // name in such an answer, so the call can't be restored — but it also must
79
+ // not be silently taken as the final answer: agent-loop will ask to retry
80
+ // (looksLikeToolCall triggers on the word parameter/DSML).
81
81
  return null;
82
82
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {