zames_pro 2.55.0 → 2.59.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/config.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import { DEFAULT_LOCALE } from './i18n.js';
5
+ import { writeJsonAtomic } from './fsutil.js';
5
6
  const ZAMES_HOME = path.join(os.homedir(), '.zames');
6
7
  const HOME_CONFIG = path.join(ZAMES_HOME, 'config.json');
7
8
  // The PROJECT config path is resolved LAZILY: `/cd` changes process.cwd() at
@@ -10,13 +11,6 @@ const HOME_CONFIG = path.join(ZAMES_HOME, 'config.json');
10
11
  export function projectConfigPath() {
11
12
  return path.join(process.cwd(), '.zamesrc.json');
12
13
  }
13
- // Write via a temp file + rename so a crash or Ctrl+C mid-write can never
14
- // leave a truncated/broken config (it is read at every startup).
15
- function atomicWriteJson(file, value) {
16
- const tmp = `${file}.tmp-${process.pid}`;
17
- fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf-8');
18
- fs.renameSync(tmp, file);
19
- }
20
14
  export const DEFAULTS = {
21
15
  maxIterations: 0,
22
16
  // Headless by default: the browser runs without a visible window. Set
@@ -418,7 +412,7 @@ export function writeConfigValue(scope, key, raw) {
418
412
  const data = readConfigFile(file);
419
413
  setByPath(data, key, value);
420
414
  fs.mkdirSync(path.dirname(file), { recursive: true });
421
- atomicWriteJson(file, data);
415
+ writeJsonAtomic(file, data);
422
416
  return { value, file };
423
417
  }
424
418
  /** Reset a setting to the default (remove the key from the file). */
@@ -441,7 +435,7 @@ export function resetConfigValue(scope, key) {
441
435
  if (cur)
442
436
  delete cur[parts[parts.length - 1]];
443
437
  fs.mkdirSync(path.dirname(file), { recursive: true });
444
- atomicWriteJson(file, data);
438
+ writeJsonAtomic(file, data);
445
439
  return file;
446
440
  }
447
441
  // 'PROJECT_CONFIG' stays a getter so existing callers that only DISPLAY the
package/dist/fsutil.js ADDED
@@ -0,0 +1,18 @@
1
+ import fs from 'fs';
2
+ // One atomic writer shared by config, sessions and undo. Writing to a temp
3
+ // file in the SAME directory and renaming over the target is atomic on POSIX
4
+ // and Windows (same volume), so a crash or Ctrl+C mid-write can never leave a
5
+ // truncated/broken file behind. The temp name embeds the pid so two
6
+ // concurrent processes never collide on it.
7
+ //
8
+ // Kept in its own module because the pattern used to be copy-pasted in at
9
+ // least three places; drift between the copies is exactly how one of them
10
+ // silently loses its atomicity.
11
+ export function writeFileAtomic(file, data) {
12
+ const tmp = file + '.tmp-' + process.pid;
13
+ fs.writeFileSync(tmp, data);
14
+ fs.renameSync(tmp, file);
15
+ }
16
+ export function writeJsonAtomic(file, value) {
17
+ writeFileAtomic(file, JSON.stringify(value, null, 2) + String.fromCharCode(10));
18
+ }
package/dist/gitTools.js CHANGED
@@ -149,6 +149,36 @@ export function createGitTools(workdir) {
149
149
  return runGit(`git log --oneline --decorate -n ${Number(count) || 10}`, workdir, 10_000);
150
150
  },
151
151
  },
152
+ {
153
+ name: 'GitShow',
154
+ description: 'Show a commit or object: git show <ref> (ref defaults to HEAD). ' +
155
+ 'Read-only — use it to inspect an old version without checking out.',
156
+ parameters: { ref: 'string?', stat: 'boolean?' },
157
+ fn: async ({ ref, stat }) => {
158
+ const err = await ensureRepo();
159
+ if (err)
160
+ return err;
161
+ const r = ref && String(ref).trim() ? String(ref).trim() : 'HEAD';
162
+ // `stat` gives the cheap file list; the default dumps the full diff.
163
+ const flag = stat ? '--stat ' : '';
164
+ return runGit(`git show ${flag}${JSON.stringify(r)}`, workdir, 20_000);
165
+ },
166
+ },
167
+ {
168
+ name: 'GitBranchList',
169
+ description: 'List local git branches (name, current marker, last commit). ' +
170
+ 'Read-only — use it to pick a base branch without a raw `git branch`.',
171
+ parameters: {},
172
+ fn: async () => {
173
+ const err = await ensureRepo();
174
+ if (err)
175
+ return err;
176
+ // The format string contains parentheses/brackets, which the POSIX
177
+ // shell would treat as syntax — quote it as one JSON arg.
178
+ const format = JSON.stringify('%(HEAD) %(refname:short) %(committerdate:relative) %(subject)');
179
+ return runGit('git branch --sort=-committerdate --format=' + format, workdir, 10_000);
180
+ },
181
+ },
152
182
  {
153
183
  name: 'GitAdd',
154
184
  description: 'Stage files. If paths is not given — git add -A (everything).',
@@ -209,6 +239,13 @@ export function createGitTools(workdir) {
209
239
  : (await runGit('git branch --show-current', workdir, 5000)).trim();
210
240
  if (!b)
211
241
  return 'Error: could not determine the branch to push.';
242
+ // The branch name reaches the shell command below. Validate it as a
243
+ // real ref so a model-supplied value cannot smuggle shell metacharacters
244
+ // (`;`, backticks, `$()`) into `git push`. The remote stays a literal
245
+ // 'origin' and is not user-controlled.
246
+ if (!/^[A-Za-z0-9._/-]+$/.test(b) || b.startsWith('-')) {
247
+ return `Error: invalid branch name: ${b}`;
248
+ }
212
249
  const flag = setUpstream ? '-u ' : '';
213
250
  const target = quoteArgs(b);
214
251
  return runGit(`git push ${flag}origin ${target}`, workdir, 120_000);
package/dist/i18n.js CHANGED
@@ -77,10 +77,6 @@ export const CATALOG = {
77
77
  en: 'show the browser window (debug/sign-in)',
78
78
  },
79
79
  'help.opt.debug': { ru: 'подробный лог', en: 'verbose log' },
80
- 'help.opt.calibrate': {
81
- ru: 'режим калибровки селекторов',
82
- en: 'selector calibration mode',
83
- },
84
80
  'help.opt.dev': {
85
81
  ru: 'режим разработки: авто-перечитывание модулей',
86
82
  en: 'dev mode: auto-reload modules',
@@ -139,7 +135,13 @@ export const CATALOG = {
139
135
  ru: '!команда выполнить shell-команду напрямую (мимо модели)',
140
136
  en: '!command run a shell command directly (bypass the model)',
141
137
  },
142
- 'help.commands': { ru: 'Обычные команды:', en: 'Commands:' },
138
+ 'help.commands': { ru: 'Команды:', en: 'Commands:' },
139
+ 'help.sec.session': { ru: 'Сессия', en: 'Session' },
140
+ 'help.sec.workspace': { ru: 'Рабочая директория', en: 'Workspace' },
141
+ 'help.sec.git': { ru: 'Git', en: 'Git' },
142
+ 'help.sec.agent': { ru: 'Агент', en: 'Agent' },
143
+ 'help.sec.context': { ru: 'Контекст и настройки', en: 'Context & settings' },
144
+ 'help.sec.files': { ru: 'Файлы и прочее', en: 'Files & misc' },
143
145
  'help.cmd.new': {
144
146
  ru: '/new, /clear новый чат (сброс контекста)',
145
147
  en: '/new, /clear new chat (reset context)',
@@ -200,6 +202,26 @@ export const CATALOG = {
200
202
  ru: '/diff [--staged] показать git diff',
201
203
  en: '/diff [--staged] show working tree git diff',
202
204
  },
205
+ 'help.cmd.diffstat': {
206
+ ru: '/diffstat сводка изменений (git diff --stat)',
207
+ en: '/diffstat change summary (git diff --stat)',
208
+ },
209
+ 'help.cmd.retry': {
210
+ ru: '/retry повторить последнюю задачу в этом чате',
211
+ en: '/retry resend the last task into this chat',
212
+ },
213
+ 'help.cmd.rename': {
214
+ ru: '/rename <title> задать имя текущей сессии',
215
+ en: '/rename <title> set the current session title',
216
+ },
217
+ 'help.cmd.context': {
218
+ ru: '/context что загружено в промпт (AGENTS/MEMORY/skills/commands)',
219
+ en: '/context what is loaded into the prompt (AGENTS/MEMORY/skills/commands)',
220
+ },
221
+ 'help.cmd.copy': {
222
+ ru: '/copy скопировать последний ответ в буфер обмена',
223
+ en: '/copy copy the last answer to the clipboard',
224
+ },
203
225
  'help.cmd.cost': { ru: '/cost статистика сессии', en: '/cost session stats' },
204
226
  'help.cmd.export': {
205
227
  ru: '/export [file] выгрузить сессию в Markdown',
@@ -217,6 +239,26 @@ export const CATALOG = {
217
239
  ru: '/review [focus] ревью незакоммиченных изменений',
218
240
  en: '/review [focus] review uncommitted changes',
219
241
  },
242
+ 'help.cmd.improve': {
243
+ ru: '/improve [id] взять следующий пункт BACKLOG и довести до тестов',
244
+ en: '/improve [id] take the next BACKLOG item and drive it to green tests',
245
+ },
246
+ 'improve.no_backlog': {
247
+ ru: 'BACKLOG.md не найден в рабочей директории.',
248
+ en: 'BACKLOG.md was not found in the working directory.',
249
+ },
250
+ 'improve.all_done': {
251
+ ru: 'Открытых пунктов в BACKLOG нет.',
252
+ en: 'There are no open BACKLOG items.',
253
+ },
254
+ 'improve.not_found': {
255
+ ru: 'Пункт {v} не найден в BACKLOG.',
256
+ en: 'Item {v} was not found in BACKLOG.',
257
+ },
258
+ 'improve.start': {
259
+ ru: 'Улучшение {id}: {title}',
260
+ en: 'Improving {id}: {title}',
261
+ },
220
262
  'help.cmd.compact': {
221
263
  ru: '/compact сжать историю и открыть новый чат с резюме',
222
264
  en: '/compact compact the history and open a new chat with the summary',
@@ -241,6 +283,26 @@ export const CATALOG = {
241
283
  ru: '/goal [текст|clear] задать долгоживущую цель сессии',
242
284
  en: '/goal [text|clear] set a long-lived session goal',
243
285
  },
286
+ 'help.cmd.plan': {
287
+ ru: '/plan [on|off] режим плана: только чтение, без правок и команд',
288
+ en: '/plan [on|off] plan mode: read-only, no edits or commands',
289
+ },
290
+ 'plan.on': {
291
+ ru: 'Режим плана ВКЛ: доступны только инструменты чтения. Правки и команды запрещены. /plan off — выключить.',
292
+ en: 'Plan mode ON: only read tools are available. Edits and commands are disabled. /plan off to disable.',
293
+ },
294
+ 'plan.off': {
295
+ ru: 'Режим плана ВЫКЛ: агент снова может писать и запускать команды.',
296
+ en: 'Plan mode OFF: the agent can edit and run commands again.',
297
+ },
298
+ 'help.opt.output_format': {
299
+ ru: 'формат вывода: text|json|jsonl (для скриптов)',
300
+ en: 'output format: text|json|jsonl (for scripts)',
301
+ },
302
+ 'help.opt.plan': {
303
+ ru: 'стартовать в режиме плана (только чтение)',
304
+ en: 'start in plan mode (read-only)',
305
+ },
244
306
  'help.cmd.loop': {
245
307
  ru: '/loop <интервал> <задача> периодически повторять задачу (напр. 10m)',
246
308
  en: '/loop <interval> <task> repeat a task periodically (e.g. 10m)',
@@ -356,8 +418,8 @@ export const CATALOG = {
356
418
  en: 'No such directory: {v}',
357
419
  },
358
420
  'adddir.note': {
359
- ru: 'Директория существует: {v}. Песочница фиксируется при запуске.',
360
- en: 'Directory exists: {v}. The sandbox is fixed at startup.',
421
+ ru: 'Директория существует: {v}. Только проверка — песочница фиксируется при запуске, доступ не расширяется (перезапустите с --dir, чтобы работать там).',
422
+ en: 'Directory exists: {v}. Check only — the sandbox is fixed at startup, access is NOT granted (relaunch with --dir to work there).',
361
423
  },
362
424
  'help.cmd.config': {
363
425
  ru: '/config настройки: показать и изменить',
@@ -683,6 +745,62 @@ export const CATALOG = {
683
745
  en: '... [{n} more lines, use Bash git diff for the full output]',
684
746
  },
685
747
  // ---------- /cost ----------
748
+ // ---------- /context ----------
749
+ 'context.title': {
750
+ ru: 'Загружено в промпт:',
751
+ en: 'Loaded into the prompt:',
752
+ },
753
+ 'context.system_prompt': {
754
+ ru: ' системный промпт: {n} символов',
755
+ en: ' system prompt: {n} chars',
756
+ },
757
+ 'context.agents': {
758
+ ru: 'AGENTS.md / инструкции:',
759
+ en: 'AGENTS.md / instructions:',
760
+ },
761
+ 'context.memory': {
762
+ ru: 'MEMORY.md:',
763
+ en: 'MEMORY.md:',
764
+ },
765
+ 'context.skills': {
766
+ ru: 'Скиллы:',
767
+ en: 'Skills:',
768
+ },
769
+ 'context.commands': {
770
+ ru: 'Кастомные команды:',
771
+ en: 'Custom commands:',
772
+ },
773
+ 'context.none': { ru: '(нет)', en: '(none)' },
774
+ 'context.near_full': {
775
+ ru: '⚠ Контекст заполнен на {pct}% — подумайте о /compact, чтобы сжать историю.',
776
+ en: '⚠ Context is {pct}% full — consider /compact to shrink the history.',
777
+ },
778
+ // ---------- /retry, /rename ----------
779
+ 'retry.none': {
780
+ ru: 'Нет последней задачи для повтора.',
781
+ en: 'No last task to retry.',
782
+ },
783
+ 'copy.none': {
784
+ ru: 'Нет ответа для копирования.',
785
+ en: 'No answer to copy.',
786
+ },
787
+ 'copy.done': {
788
+ ru: 'Скопировано в буфер обмена ({tool}).',
789
+ en: 'Copied to clipboard ({tool}).',
790
+ },
791
+ 'copy.failed': {
792
+ ru: 'Не удалось скопировать (нет {tool} или он недоступен).',
793
+ en: 'Could not copy ({tool} missing or unavailable).',
794
+ },
795
+ 'retry.running': { ru: 'Повторяю задачу…', en: 'Retrying the task...' },
796
+ 'rename.usage': {
797
+ ru: 'Использование: /rename <title>',
798
+ en: 'Usage: /rename <title>',
799
+ },
800
+ 'rename.done': {
801
+ ru: 'Сессия переименована: {v}',
802
+ en: 'Session renamed: {v}',
803
+ },
686
804
  'cost.title': { ru: 'Статистика сессии:', en: 'Session stats:' },
687
805
  'cost.context_known': {
688
806
  ru: ' контекст: ~{n} токенов (DeepSeek accumulated_token_usage)',
@@ -772,17 +890,16 @@ export const CATALOG = {
772
890
  en: 'Clear: /queue clear',
773
891
  },
774
892
  'msg.agent_error': { ru: '✖ Ошибка агента:', en: '✖ Agent error:' },
893
+ 'msg.tool_loop': {
894
+ ru: 'Агент повторяет один и тот же вызов инструмента — переключаю на другой подход.',
895
+ en: 'The agent keeps repeating the same tool call — switching it to a different approach.',
896
+ },
775
897
  'msg.suspicious_stop': {
776
898
  ru: 'агент, похоже, остановился, не распознав вызов инструмента. Ответ сохранён в транскрипте (событие suspicious_final). Можно попросить продолжить или переотправить задачу.',
777
899
  en: 'the agent seems to have stopped without recognizing a tool call. The response is saved in the transcript (suspicious_final event). You can ask it to continue or resend the task.',
778
900
  },
779
901
  'msg.critical': { ru: 'Критическая ошибка:', en: 'Critical error:' },
780
902
  'msg.error': { ru: 'Ошибка:', en: 'Error:' },
781
- 'confirm.hint': {
782
- ru: 'всегда для этого типа',
783
- en: 'always for this kind',
784
- },
785
- 'confirm.ask_label': { ru: 'Разрешить', en: 'Allow' },
786
903
  'msg.reloaded': {
787
904
  ru: 'Перезагружено модулей: {n}. Браузер и чат не тронуты.',
788
905
  en: 'Modules reloaded: {n}. Browser and chat untouched.',
@@ -1139,6 +1256,11 @@ export const CATALOG = {
1139
1256
  },
1140
1257
  'undo.changed': { ru: 'изменён', en: 'changed' },
1141
1258
  'undo.created': { ru: 'создан', en: 'created' },
1259
+ 'undo.reason_empty': { ru: 'история пуста', en: 'history is empty' },
1260
+ 'undo.reason_disabled': {
1261
+ ru: 'undo отключён в конфиге',
1262
+ en: 'undo is disabled in the config',
1263
+ },
1142
1264
  'dom.saved': { ru: 'HTML сохранён: {v}', en: 'HTML saved: {v}' },
1143
1265
  'dom.selectors': { ru: 'Селекторы:', en: 'Selectors:' },
1144
1266
  'dom.save_error': {
@@ -1161,10 +1283,6 @@ export const CATALOG = {
1161
1283
  ru: 'Не удалось перейти: {v}',
1162
1284
  en: 'Failed to change directory: {v}',
1163
1285
  },
1164
- calibrate: {
1165
- ru: '\n🔧 Режим калибровки селекторов\n',
1166
- en: '\n🔧 Selector calibration mode\n',
1167
- },
1168
1286
  'reload.auto_partial': {
1169
1287
  ru: '⚠ авто-reload: часть модулей не загрузилась, работаю на прежней версии:',
1170
1288
  en: '⚠ auto-reload: some modules failed, using previous version:',