zames_pro 2.49.3 → 2.49.5

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/browser.js CHANGED
@@ -605,7 +605,7 @@ export class DeepSeekBrowser {
605
605
  // busy, by a clear error in _launchOnce().
606
606
  if (await profileLooksLocked()) {
607
607
  if (this.debug)
608
- console.error('profile: удаляю Singleton-файлы');
608
+ console.error('profile: removing Singleton files');
609
609
  await cleanSingletonFiles();
610
610
  }
611
611
  // Headless: if we already know the sanitized UA for THIS engine build, use
@@ -641,13 +641,13 @@ export class DeepSeekBrowser {
641
641
  // uses it immediately (no relaunch). Keyed by executable path.
642
642
  void writeCachedHeadlessUA(headlessEngineKey(), fixed);
643
643
  if (this.debug)
644
- console.error('profile: headless UA → перезапуск с обычным Chrome UA');
644
+ console.error('profile: headless UA -> relaunching with a plain Chrome UA');
645
645
  await this.context.close().catch(() => { });
646
646
  await this._launchOnce();
647
647
  }
648
648
  catch (e) {
649
649
  if (this.debug)
650
- console.error('profile: UA-фикс не удался:', e.message);
650
+ console.error('profile: UA fix failed:', e.message);
651
651
  }
652
652
  }
653
653
  async _launchOnce() {
@@ -669,7 +669,7 @@ export class DeepSeekBrowser {
669
669
  catch (e) {
670
670
  if (/Target page, context or browser has been closed|profile.*in use/i.test(e.message)) {
671
671
  if (this.debug)
672
- console.error('profile: занят, перезапускаю после очистки');
672
+ console.error('profile: busy, relaunching after cleanup');
673
673
  // Do NOT kill a foreign chrome (see AGENTS.md): only clear the
674
674
  // Singleton files and retry. If the profile is genuinely in use by a
675
675
  // live browser, nukeProfile() below fails safely and the error
@@ -680,7 +680,7 @@ export class DeepSeekBrowser {
680
680
  }
681
681
  catch {
682
682
  if (this.debug)
683
- console.error('profile: сношу целиком');
683
+ console.error('profile: nuking the profile');
684
684
  await nukeProfile();
685
685
  this.context = await chromium.launchPersistentContext(USER_DATA_DIR, options);
686
686
  }
@@ -826,7 +826,7 @@ export class DeepSeekBrowser {
826
826
  // 1) Already signed in (session cookie in the persistent profile)?
827
827
  if (await this.isLoggedIn()) {
828
828
  if (this.debug)
829
- console.error('auth: сессия уже активна');
829
+ console.error('auth: session already active');
830
830
  return;
831
831
  }
832
832
  // 2) Try to sign in automatically with the saved credentials. The
@@ -906,7 +906,7 @@ export class DeepSeekBrowser {
906
906
  // Prompt only when we have a login field to fill.
907
907
  if (!(await this._loginFormVisible())) {
908
908
  if (this.debug)
909
- console.error('auth: форма входа не найдена');
909
+ console.error('auth: login form not found');
910
910
  return false;
911
911
  }
912
912
  console.log('\n' + theme.warn(this._t('auth.need_login')));
package/dist/config.js CHANGED
@@ -4,7 +4,12 @@ import os from 'os';
4
4
  import { DEFAULT_LOCALE } from './i18n.js';
5
5
  const ZAMES_HOME = path.join(os.homedir(), '.zames');
6
6
  const HOME_CONFIG = path.join(ZAMES_HOME, 'config.json');
7
- const PROJECT_CONFIG = path.join(process.cwd(), '.zamesrc.json');
7
+ // The PROJECT config path is resolved LAZILY: `/cd` changes process.cwd() at
8
+ // runtime, so freezing it at import time meant a `.zamesrc.json` in the new
9
+ // working directory was never read (and writes went to the OLD directory).
10
+ export function projectConfigPath() {
11
+ return path.join(process.cwd(), '.zamesrc.json');
12
+ }
8
13
  export const DEFAULTS = {
9
14
  maxIterations: 0,
10
15
  // Headless by default: the browser runs without a visible window. Set
@@ -68,7 +73,7 @@ export const DEFAULTS = {
68
73
  };
69
74
  export function loadConfig() {
70
75
  const merged = JSON.parse(JSON.stringify(DEFAULTS));
71
- for (const file of [HOME_CONFIG, PROJECT_CONFIG]) {
76
+ for (const file of [HOME_CONFIG, projectConfigPath()]) {
72
77
  try {
73
78
  const data = JSON.parse(fs.readFileSync(file, 'utf-8'));
74
79
  deepMerge(merged, data);
@@ -76,7 +81,7 @@ export function loadConfig() {
76
81
  catch (e) {
77
82
  const err = e;
78
83
  if (err.code !== 'ENOENT') {
79
- console.error(`config: не удалось прочитать ${file}: ${err.message}`);
84
+ console.error(`config: failed to read ${file}: ${err.message}`);
80
85
  }
81
86
  }
82
87
  }
@@ -395,7 +400,7 @@ export function validateConfigValue(field, raw) {
395
400
  return parseConfigValue(field, raw);
396
401
  }
397
402
  export function configPathFor(scope) {
398
- return scope === 'project' ? PROJECT_CONFIG : HOME_CONFIG;
403
+ return scope === 'project' ? projectConfigPath() : HOME_CONFIG;
399
404
  }
400
405
  /**
401
406
  * Read only the specified config file (without merging with defaults), so that
@@ -455,5 +460,12 @@ export function resetConfigValue(scope, key) {
455
460
  fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n', 'utf-8');
456
461
  return file;
457
462
  }
458
- export const CONFIG_PATHS = { HOME_CONFIG, PROJECT_CONFIG };
463
+ // 'PROJECT_CONFIG' stays a getter so existing callers that only DISPLAY the
464
+ // path (help, /config) keep working while still reflecting the current cwd.
465
+ export const CONFIG_PATHS = {
466
+ HOME_CONFIG,
467
+ get PROJECT_CONFIG() {
468
+ return projectConfigPath();
469
+ },
470
+ };
459
471
  export { ZAMES_HOME };
package/dist/i18n.js CHANGED
@@ -814,6 +814,66 @@ const CATALOG = {
814
814
  ru: 'Использование: /self-apply <name>',
815
815
  en: 'Usage: /self-apply <name>',
816
816
  },
817
+ 'self.no_src': {
818
+ ru: 'Самообзор отменён: в {dir} нет .ts файлов. Проверь, что src/ не пуст и ты запускаешь агента из корня проекта.',
819
+ en: 'Self-review aborted: no .ts files in {dir}. Check that src/ is not empty and you run the agent from the project root.',
820
+ },
821
+ 'self.no_src_err': {
822
+ ru: 'SRC_DIR пуст — нечего ревьюить',
823
+ en: 'SRC_DIR is empty - nothing to review',
824
+ },
825
+ 'self.snapshot': { ru: '📸 Снапшот: {dir}', en: '📸 Snapshot: {dir}' },
826
+ 'self.files': { ru: 'Файлов: {n} — {list}', en: 'Files: {n} — {list}' },
827
+ 'self.starting': {
828
+ ru: 'Начинаю самообзор...',
829
+ en: 'Starting self-review...',
830
+ },
831
+ 'self.report': { ru: '📋 Отчёт:', en: '📋 Report:' },
832
+ 'self.report_empty': { ru: '(пусто)', en: '(empty)' },
833
+ 'self.report_path': { ru: 'Отчёт: {v}', en: 'Report: {v}' },
834
+ 'self.snapshot_path': { ru: 'Снапшот: {v}', en: 'Snapshot: {v}' },
835
+ 'self.changed': {
836
+ ru: 'Изменено: {n} файл(ов): {list}',
837
+ en: 'Changed: {n} file(s): {list}',
838
+ },
839
+ 'self.changed_none': {
840
+ ru: 'Изменено: (ничего — только отчёт)',
841
+ en: 'Changed: (nothing - report only)',
842
+ },
843
+ 'self.next': { ru: 'Дальше:', en: 'Next:' },
844
+ 'self.diff_hint': {
845
+ ru: ' /self-diff {name} — посмотреть различия',
846
+ en: ' /self-diff {name} — view the diff',
847
+ },
848
+ 'self.apply_hint': {
849
+ ru: ' /self-apply {name} — применить к живому src/',
850
+ en: ' /self-apply {name} — apply to the live src/',
851
+ },
852
+ 'self.no_diff': { ru: 'Различий нет.', en: 'No differences.' },
853
+ 'self.apply_no_ts': {
854
+ ru: 'В снапшоте {dir} нет .ts файлов. Apply отменён, чтобы не стирать src/.',
855
+ en: 'Snapshot {dir} has no .ts files. Apply aborted to avoid wiping src/.',
856
+ },
857
+ 'self.applied': {
858
+ ru: '✅ Применено: {n} файл(ов)',
859
+ en: '✅ Applied: {n} file(s)',
860
+ },
861
+ 'self.backup': { ru: 'Бэкап: {dir}', en: 'Backup: {dir}' },
862
+ 'self.restart_hint': {
863
+ ru: 'Перезапусти агента, чтобы изменения вступили в силу.',
864
+ en: 'Restart the agent for the changes to take effect.',
865
+ },
866
+ 'self.no_snapshots': { ru: 'Снапшотов нет.', en: 'No snapshots.' },
867
+ 'self.list_title': {
868
+ ru: 'Снапшоты самообзора:',
869
+ en: 'Self-review snapshots:',
870
+ },
871
+ 'self.list_changed': { ru: '[{n} изменено]', en: '[{n} changed]' },
872
+ 'self.list_unchanged': { ru: '[без правок]', en: '[no changes]' },
873
+ 'self.list_commands': {
874
+ ru: 'Команды: /self-diff <name>, /self-apply <name>',
875
+ en: 'Commands: /self-diff <name>, /self-apply <name>',
876
+ },
817
877
  'self.fix_hint': {
818
878
  ru: '\n💡 Режим ревью по снапшоту {name}. Пиши агенту задачу или /self-done.\n',
819
879
  en: '\n💡 Review mode on snapshot {name}. Send the agent a task or /self-done.\n',
package/dist/index.js CHANGED
@@ -2149,6 +2149,7 @@ async function main() {
2149
2149
  config,
2150
2150
  focus: focus || undefined,
2151
2151
  transcript,
2152
+ locale: currentLocale,
2152
2153
  });
2153
2154
  // Switch to review mode:
2154
2155
  // - working directory = snapshot
@@ -2262,7 +2263,7 @@ async function main() {
2262
2263
  }
2263
2264
  if (lower === '/self-list') {
2264
2265
  try {
2265
- await mod.selfList({ config });
2266
+ await mod.selfList({ config, locale: currentLocale });
2266
2267
  }
2267
2268
  catch (e) {
2268
2269
  console.error(theme.error(t('msg.error')), e.message);
package/dist/mcp.js CHANGED
@@ -1,3 +1,21 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ // The MCP client advertises a name+version to servers. Read the real version
5
+ // from package.json (the previous hardcoded '1.0.0' silently drifted from the
6
+ // package). Best-effort: an unreadable package.json must never break MCP.
7
+ function packageVersion() {
8
+ try {
9
+ const here = path.dirname(fileURLToPath(import.meta.url));
10
+ const pkg = JSON.parse(fs.readFileSync(path.join(here, '..', 'package.json'), 'utf-8'));
11
+ if (pkg && typeof pkg.version === 'string')
12
+ return pkg.version;
13
+ }
14
+ catch {
15
+ // fall through
16
+ }
17
+ return '0.0.0';
18
+ }
1
19
  /** Names of the servers found in a config, before any connection. */
2
20
  export async function listConfiguredServers(workdir) {
3
21
  const files = await discoverConfigFiles(workdir);
@@ -226,7 +244,7 @@ export function qualifyToolName(server, tool) {
226
244
  async function connectServer(name, cfg, debug) {
227
245
  try {
228
246
  const { Client } = await import('@modelcontextprotocol/sdk/client/index.js');
229
- const client = new Client({ name: 'zames', version: '1.0.0' }, { capabilities: {} });
247
+ const client = new Client({ name: 'zames', version: packageVersion() }, { capabilities: {} });
230
248
  let transport;
231
249
  if (cfg.url) {
232
250
  if (cfg.transport === 'sse') {
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs/promises';
3
3
  import { theme } from './theme.js';
4
+ import { translate, DEFAULT_LOCALE } from './i18n.js';
4
5
  import { fileURLToPath } from 'url';
5
6
  import { ZAMES_HOME } from './config.js';
6
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -22,6 +23,12 @@ function resolveSrcDir() {
22
23
  }
23
24
  const SRC_DIR = resolveSrcDir();
24
25
  const SNAP_ROOT = path.join(ZAMES_HOME, 'snapshots');
26
+ // Localization for everything the OPERATOR sees. The locale is passed in from
27
+ // the caller (src/index.ts holds currentLocale); without it we fall back to the
28
+ // default so a direct call never breaks.
29
+ function translator(locale) {
30
+ return translate(locale || DEFAULT_LOCALE);
31
+ }
25
32
  // Dynamic import of the logic with a timestamp — so that on /reload (or
26
33
  // auto-reload) self-review uses FRESH tools/agent-loop, not the ones cached on
27
34
  // first load. Otherwise reload wouldn't reach self-review's dependencies.
@@ -54,7 +61,8 @@ async function copyDirJsFiles(from, to) {
54
61
  return copied;
55
62
  }
56
63
  // ---------- /self-review ----------
57
- export async function selfReview({ browser, focus, transcript, }) {
64
+ export async function selfReview({ browser, focus, transcript, locale, }) {
65
+ const t = translator(locale);
58
66
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
59
67
  const snapRoot = path.join(SNAP_ROOT);
60
68
  const snapDir = path.join(snapRoot, `run-${stamp}`);
@@ -62,9 +70,8 @@ export async function selfReview({ browser, focus, transcript, }) {
62
70
  await ensureDir(snapDir);
63
71
  const copied = await copyDirJsFiles(SRC_DIR, snapDir);
64
72
  if (copied.length === 0) {
65
- console.error(theme.error(`\n✖ Самообзор отменён: в ${SRC_DIR} нет .ts файлов.\n` +
66
- `Проверь, что src/ не пуст и ты запускаешь агента из корня проекта.\n`));
67
- throw new Error('SRC_DIR пуст — нечего ревьюить');
73
+ console.error(theme.error('\n✖ ' + t('self.no_src', { dir: SRC_DIR })));
74
+ throw new Error(t('self.no_src_err'));
68
75
  }
69
76
  // Copy package.json for context — so the agent sees the dependencies
70
77
  try {
@@ -72,9 +79,9 @@ export async function selfReview({ browser, focus, transcript, }) {
72
79
  await fs.writeFile(path.join(snapDir, 'package.json'), pkg, 'utf-8');
73
80
  }
74
81
  catch { }
75
- console.log(theme.system(`\n📸 Снапшот: ${snapDir}`));
76
- console.log(theme.system(`Файлов: ${copied.length} — ${copied.join(', ')}`));
77
- console.log(theme.system('Начинаю самообзор...\n'));
82
+ console.log(theme.system('\n' + t('self.snapshot', { dir: snapDir })));
83
+ console.log(theme.system(t('self.files', { n: copied.length, list: copied.join(', ') })));
84
+ console.log(theme.system(t('self.starting') + '\n'));
78
85
  const taskPrompt = buildReviewPrompt({ focus, snapDir });
79
86
  const { createTools, runAgentLoop } = await loadFresh();
80
87
  const tools = createTools(snapDir, { undo: null });
@@ -94,42 +101,43 @@ export async function selfReview({ browser, focus, transcript, }) {
94
101
  console.log(theme.warn(`🔧 ${name}`), theme.system(preview));
95
102
  },
96
103
  onToolResult: (r) => {
97
- const t = typeof r === 'string' ? r : JSON.stringify(r);
98
- console.log(theme.system(` → ${t.slice(0, 200).replace(/\n/g, ' ↵ ')}\n`));
104
+ const text = typeof r === 'string' ? r : JSON.stringify(r);
105
+ console.log(theme.system(` → ${text.slice(0, 200).replace(/\n/g, ' ↵ ')}\n`));
99
106
  },
100
107
  onAssistantMessage: (msg) => {
101
108
  finalMessage = msg;
102
- console.log(theme.assistant('\n📋 Отчёт:\n'));
109
+ console.log(theme.assistant('\n' + t('self.report') + '\n'));
103
110
  console.log(msg);
104
111
  console.log();
105
112
  },
106
113
  });
107
114
  // Save the report
108
115
  const reportPath = path.join(snapDir, '_report.md');
109
- await fs.writeFile(reportPath, finalMessage || '(пусто)', 'utf-8');
116
+ await fs.writeFile(reportPath, finalMessage || t('self.report_empty'), 'utf-8');
110
117
  // Compute what changed
111
118
  const changed = await diffFiles(SRC_DIR, snapDir);
112
119
  console.log(theme.system('─'.repeat(60)));
113
- console.log(theme.system(`Отчёт: ${reportPath}`));
114
- console.log(theme.system(`Снапшот: ${snapDir}`));
120
+ console.log(theme.system(t('self.report_path', { v: reportPath })));
121
+ console.log(theme.system(t('self.snapshot_path', { v: snapDir })));
115
122
  if (changed.length) {
116
- console.log(theme.assistant(`Изменено: ${changed.length} файл(ов): ${changed.join(', ')}`));
123
+ console.log(theme.assistant(t('self.changed', { n: changed.length, list: changed.join(', ') })));
117
124
  }
118
125
  else {
119
- console.log(theme.system('Изменено: (ничего — только отчёт)'));
126
+ console.log(theme.system(t('self.changed_none')));
120
127
  }
121
- console.log(theme.user(`\nДальше:`));
122
- console.log(theme.user(` /self-diff ${path.basename(snapDir)} — посмотреть различия`));
123
- console.log(theme.user(` /self-apply ${path.basename(snapDir)} — применить к живому src/`));
128
+ console.log(theme.user('\n' + t('self.next')));
129
+ console.log(theme.user(t('self.diff_hint', { name: path.basename(snapDir) })));
130
+ console.log(theme.user(t('self.apply_hint', { name: path.basename(snapDir) })));
124
131
  console.log();
125
132
  return { snapDir, reportPath, changed };
126
133
  }
127
134
  // ---------- /self-diff ----------
128
- export async function selfDiff({ name, }) {
135
+ export async function selfDiff({ name, locale, }) {
136
+ const t = translator(locale);
129
137
  const snapDir = path.join(SNAP_ROOT, name);
130
138
  const snapStat = await fs.stat(snapDir).catch(() => null);
131
139
  if (!snapStat)
132
- throw new Error(`Снапшот не найден: ${snapDir}`);
140
+ throw new Error(t('self.snapshot_not_found', { dir: snapDir }));
133
141
  const { unifiedDiff, colorDiff } = await import('./diff.js');
134
142
  const files = await fs.readdir(snapDir);
135
143
  let anyDiff = false;
@@ -150,15 +158,16 @@ export async function selfDiff({ name, }) {
150
158
  console.log(colorDiff(diff));
151
159
  }
152
160
  if (!anyDiff) {
153
- console.log(theme.system('Различий нет.'));
161
+ console.log(theme.system(t('self.no_diff')));
154
162
  }
155
163
  }
156
164
  // ---------- /self-apply ----------
157
- export async function selfApply({ name, }) {
165
+ export async function selfApply({ name, locale, }) {
166
+ const t = translator(locale);
158
167
  const snapDir = path.join(SNAP_ROOT, name);
159
168
  const snapStat = await fs.stat(snapDir).catch(() => null);
160
169
  if (!snapStat)
161
- throw new Error(`Снапшот не найден: ${snapDir}`);
170
+ throw new Error(t('self.snapshot_not_found', { dir: snapDir }));
162
171
  // Back up the current src before overwriting
163
172
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
164
173
  const backupDir = path.join(SNAP_ROOT, `backup-before-apply-${stamp}`);
@@ -166,7 +175,7 @@ export async function selfApply({ name, }) {
166
175
  const files = await fs.readdir(snapDir);
167
176
  const jsFiles = files.filter((f) => f.endsWith('.ts'));
168
177
  if (jsFiles.length === 0) {
169
- throw new Error(`В снапшоте ${snapDir} нет .ts файлов. Apply отменён, чтобы не стирать src/.`);
178
+ throw new Error(t('self.apply_no_ts', { dir: snapDir }));
170
179
  }
171
180
  const applied = [];
172
181
  for (const f of jsFiles) {
@@ -174,28 +183,29 @@ export async function selfApply({ name, }) {
174
183
  await fs.writeFile(path.join(SRC_DIR, f), data, 'utf-8');
175
184
  applied.push(f);
176
185
  }
177
- console.log(theme.assistant(`\n✅ Применено: ${applied.length} файл(ов)`));
186
+ console.log(theme.assistant('\n' + t('self.applied', { n: applied.length })));
178
187
  console.log(theme.system(applied.join(', ')));
179
- console.log(theme.system(`Бэкап: ${backupDir}`));
180
- console.log(theme.warn(`\nПерезапусти агента, чтобы изменения вступили в силу.\n`));
188
+ console.log(theme.system(t('self.backup', { dir: backupDir })));
189
+ console.log(theme.warn('\n' + t('self.restart_hint') + '\n'));
181
190
  }
182
191
  // ---------- /self-list ----------
183
- export async function selfList({ config: _config, } = {}) {
192
+ export async function selfList({ locale, } = {}) {
193
+ const t = translator(locale);
184
194
  const snapRoot = path.join(SNAP_ROOT);
185
195
  let entries;
186
196
  try {
187
197
  entries = await fs.readdir(snapRoot);
188
198
  }
189
199
  catch {
190
- console.log(theme.system('Снапшотов нет.'));
200
+ console.log(theme.system(t('self.no_snapshots')));
191
201
  return;
192
202
  }
193
203
  if (!entries.length) {
194
- console.log(theme.system('Снапшотов нет.'));
204
+ console.log(theme.system(t('self.no_snapshots')));
195
205
  return;
196
206
  }
197
207
  entries.sort();
198
- console.log(theme.system('Снапшоты самообзора:'));
208
+ console.log(theme.system(t('self.list_title')));
199
209
  for (const e of entries) {
200
210
  const full = path.join(snapRoot, e);
201
211
  const stat = await fs.stat(full).catch(() => null);
@@ -208,13 +218,13 @@ export async function selfList({ config: _config, } = {}) {
208
218
  .catch(() => false);
209
219
  const changedCount = (await diffFiles(SRC_DIR, full)).length;
210
220
  const tag = changedCount
211
- ? theme.assistant(`[${changedCount} изменено]`)
212
- : theme.system('[без правок]');
221
+ ? theme.assistant(t('self.list_changed', { n: changedCount }))
222
+ : theme.system(t('self.list_unchanged'));
213
223
  const rep = hasReport ? theme.user('📋') : ' ';
214
224
  console.log(` ${rep} ${e} ${tag}`);
215
225
  }
216
226
  console.log();
217
- console.log(theme.system('Команды: /self-diff <name>, /self-apply <name>\n'));
227
+ console.log(theme.system(t('self.list_commands') + '\n'));
218
228
  }
219
229
  // ---------- helpers ----------
220
230
  async function diffFiles(origDir, newDir) {
package/dist/sessions.js CHANGED
@@ -7,6 +7,12 @@ import os from 'os';
7
7
  // opened session for a specific working directory.
8
8
  const SESSIONS_DIR = path.join(os.homedir(), '.zames', '.sessions');
9
9
  const INDEX_FILE = path.join(SESSIONS_DIR, 'last.json');
10
+ export const SESSIONS_FORMAT_VERSION = 1;
11
+ function atomicWriteJson(file, value) {
12
+ const tmp = file + '.tmp-' + process.pid;
13
+ fs.writeFileSync(tmp, JSON.stringify(value, null, 2), 'utf-8');
14
+ fs.renameSync(tmp, file);
15
+ }
10
16
  function ensureDir() {
11
17
  fs.mkdirSync(SESSIONS_DIR, { recursive: true });
12
18
  }
@@ -33,13 +39,14 @@ export function saveSession({ id, title = '', workdir = '', todos, }) {
33
39
  workdir: workdir || prev.workdir || '',
34
40
  createdAt: prev.createdAt || now,
35
41
  updatedAt: now,
42
+ version: SESSIONS_FORMAT_VERSION,
36
43
  };
37
44
  // Keep the previous checklist unless a new one is explicitly passed —
38
45
  // saveLastChat() calls this for title/workdir only and must not wipe it.
39
46
  const nextTodos = todos !== undefined ? todos : prev.todos;
40
47
  if (nextTodos !== undefined)
41
48
  data.todos = nextTodos;
42
- fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf-8');
49
+ atomicWriteJson(file, data);
43
50
  writeIndex({ id, workdir: data.workdir });
44
51
  return data;
45
52
  }
@@ -64,7 +71,7 @@ function writeIndex({ id, workdir }) {
64
71
  index.byWorkdir[key] = id;
65
72
  index.last = id;
66
73
  index.updatedAt = new Date().toISOString();
67
- fs.writeFileSync(INDEX_FILE, JSON.stringify(index, null, 2), 'utf-8');
74
+ atomicWriteJson(INDEX_FILE, index);
68
75
  }
69
76
  catch { }
70
77
  }
@@ -20,12 +20,12 @@ export class Transcript {
20
20
  // Write errors (disk full, file deleted, etc.) arrive as an 'error'
21
21
  // event; without a listener this is an uncaught exception.
22
22
  this.stream.on('error', (e) => {
23
- console.error(`transcript: ошибка записи: ${e.message}`);
23
+ console.error(`transcript: write error: ${e.message}`);
24
24
  this.enabled = false;
25
25
  });
26
26
  }
27
27
  catch (e) {
28
- console.error(`transcript: не удалось создать файл: ${e.message}`);
28
+ console.error(`transcript: failed to create file: ${e.message}`);
29
29
  this.enabled = false;
30
30
  }
31
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.49.3",
3
+ "version": "2.49.5",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "license": "MIT",
40
40
  "engines": {
41
- "node": ">=18"
41
+ "node": ">=20"
42
42
  },
43
43
  "dependencies": {
44
44
  "@modelcontextprotocol/sdk": "^1.30.1",