zames_pro 2.66.1 → 2.67.1

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,16 +7,98 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.67.1] - 2026-10-08
11
+
12
+ ### Changed
13
+
14
+ - **web:** the headless renderer reuses ONE BrowserContext for every
15
+ `WebFetch` with `render=true` (it used to build a fresh context per call);
16
+ the page is closed after each fetch and the context in `closeWeb()`.
17
+ - **docs:** `npm run self-smoke` is now documented as a REQUIRED manual
18
+ pre-release step in DEVELOPING.md — it is the only gate that catches an
19
+ integration regression (login, send, attachments) and needs live creds,
20
+ so it stays out of CI.
21
+ - **packaging:** `README.ru.md` is no longer generated or published. The
22
+ Russian translation already lives inside README.md as a `<details>` block
23
+ (visible on npm), and the language switcher is now an in-page `#readme-ru`
24
+ anchor — the second ~15 KB file only duplicated it and could drift.
25
+
26
+ ## [2.67.0] - 2026-10-08
27
+
28
+ ### Fixed
29
+
30
+ - **tools:** Edit/MultiEdit no longer corrupt dollar patterns (`$&`, `$$`, `$1`)
31
+ in `new_string` — the replacement now uses the functional form instead of a
32
+ string replacer.
33
+ - **tools:** Grep and findstr are run through `execFile` with an argument
34
+ array instead of a shell string, so a `$(...)` pattern is no longer executed
35
+ as a command.
36
+ - **git:** GitShow/GitDiff/GitAdd/GitPush/GitCommit now use `execFile`; `ref`
37
+ and `path` are validated (a leading `-` is rejected) — command/option
38
+ injection through the arguments of read-only tools is closed.
39
+ - **sandbox:** file tools resolve the real path (`fs.realpath`) and reject a
40
+ symlink pointing outside the working directory.
41
+ - **shell:** `assertCommandInsideRoot` expands `~`/`$HOME`, sees a `cd` inside
42
+ a subshell/backticks and rejects `eval` with `cd`.
43
+ - **plan mode:** MCP tools are now filtered for read-only too (mutating verbs
44
+ are dropped).
45
+ - **self-review:** the snapshot and diff/apply walk src/ subdirectories
46
+ recursively (`i18n/`, `input/`) — they used to be silently missing from the
47
+ snapshot.
48
+ - **one-shot `--task`:** on an agent error / iteration limit / watchdog the
49
+ process now exits with code 1 instead of 0.
50
+ - **input:** Ctrl+Delete and Delete now work under the CSI-u (kitty) protocol:
51
+ `CSI 3;5u`/`3;3u`/`3u`.
52
+ - **input:** Up/Down move the cursor across VISUAL rows of a wrapped line, not
53
+ only across logical lines.
54
+ - **input:** a lone ESC no longer aborts the generation when a control
55
+ sequence arrives in two chunks — a short timeout disambiguation.
56
+ - **input:** Ctrl+L clears the screen and PageUp/PageDown page the suggestion
57
+ list; an unknown control sequence is logged in debug instead of being
58
+ swallowed silently.
59
+ - **MCP:** duplicate tool names are disambiguated with a numeric suffix so the
60
+ second tool is not silently unreachable.
61
+ - **MCP:** a tool result is truncated with an explicit marker — a single call
62
+ (browser_snapshot and the like) no longer floods the context.
63
+ - **transcript:** `close()` waits for the flush (it returns a promise) so the
64
+ final events reach the file on the exit paths.
65
+ - **web:** `WebFetch` re-validates every redirect hop against the SSRF guard
66
+ and checks the final URL of a headless render.
67
+ - **permissions:** a `path` rule now also matches the file headers of an
68
+ `ApplyPatch` and path-looking arguments of MCP tools.
69
+ - **scheduler:** `nextCronTime` parses the cron expression once instead of on
70
+ every scanned minute.
71
+ - **git:** `formatGitContext` picks a code fence longer than any backtick run
72
+ in the preview, so a filename with backticks cannot break the markdown.
73
+ - **fsutil:** secrets and state are written with 0600 permissions;
74
+ `writeFileAtomic` fsyncs the file before rename (and the directory after)
75
+ for durability on a power loss.
76
+
77
+ ### Changed
78
+
79
+ - **docs:** SECURITY.md updated for the permissions policy, the symlink guard
80
+ and the plan-mode MCP caveat; DEVELOPING.md module sizes refreshed.
81
+ - **cleanup:** removed the dead exports `listConfiguredServers`,
82
+ `builtinSkills` and `findFileByName`.
83
+ - **tests:** the MCP result cap, `htmlToText` and the DuckDuckGo parser are
84
+ now covered, and `web.ts`/`spinner.ts` gained coverage floors.
85
+
86
+ ### Security
87
+
88
+ - Shared `shellQuote` helper for arguments passed to the shell (single quotes
89
+ with internal quotes doubled).
90
+
10
91
  ## [2.66.1] - 2026-10-07
11
92
 
12
93
  ### Changed
13
94
 
14
- - README: русский перевод теперь свёрнут в `<details>` прямо в `README.md`,
15
- поэтому его можно читать на странице npm, не уходя на GitHub. npm рендерит
16
- readme тем же GitHub Flavored Markdown, что и GitHub, а отдельный
17
- `README.ru.md` на npm-странице не виден (у npm один readme на пакет).
18
- Обе языковые версии собираются из `docs/readme.{en,ru}.md` скриптом
19
- `npm run build:readme`; `test/readme-built.test.ts` следит за рассинхроном.
95
+ - README: the Russian translation is now folded into `<details>` directly in
96
+ `README.md`, so it can be read on the npm page without leaving GitHub. npm
97
+ renders the readme with the same GitHub Flavored Markdown as GitHub, and a
98
+ separate `README.ru.md` is not visible on the npm page (npm has one readme
99
+ per package). Both language versions are assembled from
100
+ `docs/readme.{en,ru}.md` by `npm run build:readme`;
101
+ `test/readme-built.test.ts` guards against drift.
20
102
 
21
103
  ## [2.66.0] - 2026-10-07
22
104
 
@@ -24,8 +106,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
106
 
25
107
  - **release:** add scripts/release.mts for one-command release prep (G4)
26
108
 
27
- ### Fixed
28
-
29
109
  - **reload:** include commands, run-task and attach-refs in hot-reload (G2)
30
110
 
31
111
  ### Changed
@@ -34,153 +114,144 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
114
 
35
115
  ## [2.65.1] - 2026-10-07
36
116
 
37
- ### Fixed
38
-
39
- - Длинные строки preview больше не создают горизонтальный скролл в
40
- терминале: `toolCall`/`toolResult` (LineEditor и спиннер) теперь режут
41
- превью по ВИДИМОЙ ШИРИНЕ терминала (`truncateToWidth`), а не по числу
42
- символов. Раньше длинная команда/результат печатались шире экрана.
43
- - Ctrl+Delete / Ctrl+Backspace (и Alt+Delete / Alt+Backspace) теперь удаляют
44
- слово в строке ввода. Раньше эти escape-последовательности попадали в
45
- общий пропуск клавиш и ничего не делали. Поддержаны варианты: `ESC[3;5~`
46
- (xterm Ctrl+Delete), `ESC[3;3~` (Alt+Delete), `ESC DEL`/`ESC 8`
47
- (Alt/Ctrl+Backspace) и kitty-протокол `ESC[127;5u` / `ESC[127;3u`.
48
- Новый метод `_deleteWordRight()` + тест `test/editor-word-delete.test.ts`.
49
- - Меню `/config` теперь держит ВЫБРАННЫЙ пункт на экране: окно строится
50
- «назад» от курсора с учётом строк, которые занимают заголовки групп
51
- (раньше индексный `scrollTop` мог вытолкнуть выбранную строку вниз за
52
- пределы окна).
53
- - `/new` сбрасывает счётчик контекста: `_lastTokenUsage` копится НА ЧАТ,
54
- и без сброса в пустом чате оставался старый `ctx: 302k · 30%` до первой
55
- отправки. Тест в `test/newchat-verify.test.ts`.
117
+ - Long preview lines no longer create a horizontal scrollbar in the terminal:
118
+ `toolCall`/`toolResult` (LineEditor and spinner) now clip the preview by the
119
+ VISIBLE terminal width (`truncateToWidth`) instead of by character count. A
120
+ long command/result used to print wider than the screen.
121
+ - Ctrl+Delete / Ctrl+Backspace (and Alt+Delete / Alt+Backspace) now delete a
122
+ word in the input line. These escape sequences used to fall through the
123
+ generic key skip and did nothing. Supported variants: `ESC[3;5~` (xterm
124
+ Ctrl+Delete), `ESC[3;3~` (Alt+Delete), `ESC DEL`/`ESC 8`
125
+ (Alt/Ctrl+Backspace) and the kitty protocol `ESC[127;5u` / `ESC[127;3u`.
126
+ New `_deleteWordRight()` method + `test/editor-word-delete.test.ts`.
127
+ - The `/config` menu now keeps the SELECTED item on screen: the window is
128
+ built "backwards" from the cursor, taking into account the rows the group
129
+ headings occupy (previously an index-based `scrollTop` could push the
130
+ selected row below the window).
131
+ - `/new` resets the context counter: `_lastTokenUsage` accumulates PER CHAT,
132
+ and without a reset an empty chat kept showing the old `ctx: 302k · 30%`
133
+ until the first send. Test in `test/newchat-verify.test.ts`.
56
134
 
57
135
  ## [2.65.0] - 2026-10-07
58
136
 
59
137
  ### Changed
60
138
 
61
- - README-скриншот `docs/demo.png` перегенерирован на английском языке
62
- (раньше был на русском). Воспроизводимый генератор
63
- `npm run render-demo` (`scripts/render-demo.mjs`) рендерит реальную
64
- раскладку редактора и палитру темы через headless Chromium с
65
- изолированным профилем.
66
- - README: ASCII-схема «как это работает» заменена на диаграмму Mermaid
67
- (стрелки не «плывут») плюс текстовое описание словами.
68
- - Убраны неиспользуемые файлы: `logo.jpg` (в README используется только
69
- `logo-small.jpg`) и заготовка `docs/demo.tape` (VHS-рендер так и не был
70
- сгенерирован). `logo.jpg` удалён и из списка `files` в package.json.
71
-
72
- ### Fixed
73
-
74
- - Перерисовка терминала после ресайза окна: относительный стирающий
75
- `ESC[n A` после изменения размера попадал на устаревшую строку (терминал
76
- переформатировал переносы), из-за чего статус/инпут рисовались не там.
77
- Теперь после ресайза блок заново «прибивается» к низу (home + padding),
78
- история скроллбэка сохраняется. Тест в `test/terminal-drift.test.ts`.
79
- - Живая смена языка (`/config lang`) не доходила до двух «одноразово
80
- созданных» объектов: спиннер думающих фраз брал язык по умолчанию
81
- (`randomThinkingPhrase()` вместо `randomThinkingPhrase(this.locale)`),
82
- а `DeepSeekBrowser` печатал свои уведомления (`ds.*`) на языке,
83
- зафиксированном при старте. Из-за этого при английском UI всплывали
84
- русские «Структурирую мысли…» и «⏳ DeepSeek оборвал ответ…». Добавлен
85
- `DeepSeekBrowser.setLocale()`, вызываемый из `setConfigRuntime`.
86
- - Меню `/config` теперь рисуется ОКНОМ по высоте терминала (раньше весь
87
- список из 49 полей выводился одним блоком). На невысоком терминале блок
88
- уходил верхними строками за экран, из-за чего восстановление курсора
89
- `ESC[lastRows A` попадало не на ту строку и навигация стрелками «не
90
- скроллила» список. Заодно исправлена мёртвая регулярка снятия ANSI
91
- (лишний обратный апостроф) в `src/config-menu.ts`.
139
+ - The README screenshot `docs/demo.png` was regenerated in English (it used to
140
+ be in Russian). The reproducible generator `npm run render-demo`
141
+ (`scripts/render-demo.mjs`) renders the real editor layout and theme palette
142
+ through headless Chromium with an isolated profile.
143
+ - README: the ASCII "how it works" diagram was replaced by a Mermaid diagram
144
+ (the arrows no longer "float") plus a textual description in words.
145
+ - Removed unused files: `logo.jpg` (only `logo-small.jpg` is used in the
146
+ README) and the `docs/demo.tape` draft (the VHS render was never generated).
147
+ `logo.jpg` was also removed from the `files` list in package.json.
148
+
149
+ - Terminal repaint after a window resize: the relative erase `ESC[n A` after a
150
+ size change landed on a stale row (the terminal re-flowed the wraps), so the
151
+ status/input were drawn in the wrong place. After a resize the block is
152
+ re-pinned to the bottom (home + padding), and the scrollback history is kept.
153
+ Test in `test/terminal-drift.test.ts`.
154
+ - A live language change (`/config lang`) did not reach two "created once"
155
+ objects: the thinking-phrase spinner took the default language
156
+ (`randomThinkingPhrase()` instead of `randomThinkingPhrase(this.locale)`),
157
+ and `DeepSeekBrowser` printed its notices (`ds.*`) in the language fixed at
158
+ startup. So with an English UI Russian thinking/notice phrases surfaced.
159
+ Added `DeepSeekBrowser.setLocale()`, called from `setConfigRuntime`.
160
+ - The `/config` menu is now drawn as a WINDOW sized to the terminal height
161
+ (previously the whole list of 49 fields was printed as one block). On a short
162
+ terminal the block scrolled off the top, so restoring the cursor with
163
+ `ESC[lastRows A` landed on the wrong row and arrow navigation "did not
164
+ scroll" the list. Also fixed a dead ANSI-strip regex (a stray backtick) in
165
+ `src/config-menu.ts`.
92
166
 
93
167
  ## [2.64.1] - 2026-10-07
94
168
 
95
169
  ### Changed
96
170
 
97
- - README: добавлен скриншот терминальной сессии (`docs/demo.png`) под
98
- шапкой, чтобы интерфейс был виден сразу; скрипт воспроизводимого
99
- VHS-демо (`docs/demo.tape`) для будущего GIF.
171
+ - README: added a terminal session screenshot (`docs/demo.png`) under the
172
+ header so the interface is visible immediately; a reproducible VHS demo
173
+ script (`docs/demo.tape`) for a future GIF.
100
174
 
101
175
  ## [2.64.0] - 2026-10-06
102
176
 
103
177
  ### Added
104
178
 
105
- - Генератор черновика CHANGELOG из conventional-commits (BACKLOG D2):
106
- `npm run changelog:draft [ref]` печатает готовый блок для «Unreleased» из
107
- коммитов после последнего тега (или указанного ref). Чистый модуль
179
+ - A CHANGELOG draft generator from conventional commits (BACKLOG D2):
180
+ `npm run changelog:draft [ref]` prints a ready "Unreleased" block from the
181
+ commits after the last tag (or the given ref). Pure module
108
182
  `src/changelog.ts` (`parseConventionalCommit` / `groupCommits` /
109
- `renderChangelogDraft`, покрыт тестами). Файл `CHANGELOG.md` НЕ
110
- перезаписывается автоматически — черновик ревьюится вручную.
183
+ `renderChangelogDraft`, unit-tested). The `CHANGELOG.md` file is NOT
184
+ overwritten automatically — the draft is reviewed by hand.
111
185
 
112
186
  ### Changed
113
187
 
114
- - Рефакторинг (BACKLOG C3, шаг 1): разрешение путей вложения и инлайн
115
- `@file`-ссылок вынесены из `src/index.ts` в `src/attach-refs.ts`
116
- (`resolveAttachPath` / `inlineAtRefs`) — теперь это отдельный тестируемый
117
- модуль.
188
+ - Refactor (BACKLOG C3, step 1): attachment path resolution and `@file`
189
+ reference inlining were moved out of `src/index.ts` into
190
+ `src/attach-refs.ts` (`resolveAttachPath` / `inlineAtRefs`) — now a separate
191
+ testable module.
118
192
 
119
193
  ## [2.63.1] - 2026-10-06
120
194
 
121
- ### Fixed
122
-
123
- - Мигание таймера в статусе паузы перед отправкой: `sendPause()` (и
124
- `LineEditor`, и non-TTY `SpinnerUI`) перерисовывал статус БЕЗ хвоста
125
- «elapsed»/бейджа задач, а тик анимации рисовал его С ним — раз в секунду
126
- подпись `· 1m 50s` исчезала и появлялась. Теперь обновление собирает тот же
127
- хвост, что и тик. Регрессионный тест в test/compact-statusline.test.ts.
195
+ - Timer flicker in the pre-send pause status: `sendPause()` (both `LineEditor`
196
+ and the non-TTY `SpinnerUI`) repainted the status WITHOUT the
197
+ "elapsed"/task-badge tail, while the animation tick drew it WITH it — once a
198
+ second the `· 1m 50s` label disappeared and reappeared. The update now builds
199
+ the same tail as the tick. Regression test in test/compact-statusline.test.ts.
128
200
 
129
201
  ### Changed
130
202
 
131
- - README: центрированная шапка, логотип крупнее (640px-ассет, 430px показ),
132
- диаграмма «How it works», оглавление и секция Links.
133
- - Dependabot: мажорные обновления игнорируются (`semver-major`), расписание
134
- раз в месяц, авто-мерж только для patch/minor и только для PR бота
135
- (проверка автора и ветки); workflow авто-мержа. Ветка `master` защищена
136
- ruleset: PR обязателен, required-check — стабильный job `ci`.
203
+ - README: centered header, larger logo (640px asset, 430px display), a "How
204
+ it works" diagram, a table of contents and a Links section.
205
+ - Dependabot: major updates are ignored (`semver-major`), monthly schedule,
206
+ auto-merge only for patch/minor and only for bot PRs (author and branch
207
+ checks); auto-merge workflow. The `master` branch is protected by a ruleset:
208
+ PR required, required check is the stable `ci` job.
137
209
 
138
210
  ## [2.63.0] - 2026-10-06
139
211
 
140
212
  ### Added
141
213
 
142
- - Реальные подтверждения действий (BACKLOG C1): политика approval в
143
- `.zames/permissions.json` (`default` + `rules` с regexp по `tool`/`command`/
144
- `path` и действием `allow`|`deny`|`ask`). Вызывается в `agent-loop.ts` ПЕРЕД
145
- каждым инструментом: `deny` блокирует вызов, `ask` спрашивает оператора через
146
- `onAskPermission` (в TTY — интерактивный промпт над инпут-линией, в non-TTY —
147
- запрет). Новый чистый модуль `src/permissions.ts` + тесты
148
- (test/permissions.test.ts, test/permissions-loop.test.ts).
149
-
150
- - Кастомные команды: аргументы и подсказки (BACKLOG B7). Frontmatter
151
- `argument-hint:` показывается в списке «/» и в `/help` (не вставляется в
152
- строку ввода), а `arguments:` объявляет обязательные позиционные аргументы.
153
- В теле команды подставляются `$1 $2`, именованные `$name`, а также прежние
154
- `{{args}}`/`$ARGUMENTS`. Если обязательные аргументы не переданы — команда
155
- не уходит в чат, печатается подсказка. Новые чистые хелперы
214
+ - Real action approvals (BACKLOG C1): an approval policy in
215
+ `.zames/permissions.json` (`default` + `rules` with regexp on
216
+ `tool`/`command`/`path` and an `allow`|`deny`|`ask` action). Evaluated in
217
+ `agent-loop.ts` BEFORE every tool: `deny` blocks the call, `ask` prompts the
218
+ operator via `onAskPermission` (in a TTY — an interactive prompt above the
219
+ input line, in non-TTY — denied). New pure module `src/permissions.ts` +
220
+ tests (test/permissions.test.ts, test/permissions-loop.test.ts).
221
+
222
+ - Custom commands: arguments and hints (BACKLOG B7). Frontmatter
223
+ `argument-hint:` is shown in the "/" list and in `/help` (not inserted into
224
+ the input line), and `arguments:` declares mandatory positional arguments.
225
+ `$1 $2`, named `$name` and the previous `{{args}}`/`$ARGUMENTS` are
226
+ substituted into the command body. If the required arguments are missing the
227
+ command is not sent to the chat and a hint is printed. New pure helpers
156
228
  `splitCommandArgs()` / `expandCommandArgs()` / `missingCommandArgs()`
157
- (src/commands.ts, покрыты тестами).
158
-
159
- - Path-scoped правила (BACKLOG B6): вложенные `AGENTS.md`/`MEMORY.md` из
160
- подпапок, которых касается задача, подтягиваются автоматически. Текст задачи
161
- сканируется на path-токены, для найденных директорий (и их предков ниже
162
- рабочей) читаются ближайшие инструкции и рендерятся отдельной секцией
163
- `## Scoped instructions (...)` — явно помечены как действующие только для
164
- этих файлов. `loadProjectContext(workdir, touchPaths?)` и
165
- `renderContextSection()` (src/context.ts, src/system-prompt.ts; покрыто
166
- тестами).
167
-
168
- - `@file`-ссылки в задаче (BACKLOG B5): `реши задачу @src/browser.ts`
169
- подставляет содержимое указанного файла прямо в задачу, экономя отдельный
170
- ход агента на чтение. Распознаётся `@path` на границе слова (в начале строки
171
- или после пробела/скобки/кавычки), с расширением файла; `user@host` и
172
- декораторы (`@Component`) не трогаются. Существующие файлы инлайнятся
173
- (лимит 60 КБ на файл, 200 КБ суммарно — сверх этого усечение с пометкой),
174
- несуществующие остаются как есть. Хелпер `extractAtFileRefs()` в
175
- `src/path-token.ts` (чистый, покрыт тестами).
229
+ (src/commands.ts, unit-tested).
230
+
231
+ - Path-scoped rules (BACKLOG B6): nested `AGENTS.md`/`MEMORY.md` from the
232
+ subdirectories the task touches are pulled in automatically. The task text is
233
+ scanned for path tokens, the nearest instructions are read for the found
234
+ directories (and their ancestors below the working dir) and rendered as a
235
+ separate `## Scoped instructions (...)` section — explicitly marked as
236
+ applying only to those files. `loadProjectContext(workdir, touchPaths?)` and
237
+ `renderContextSection()` (src/context.ts, src/system-prompt.ts; unit-tested).
238
+
239
+ - `@file` references in a task (BACKLOG B5): `solve the task @src/browser.ts`
240
+ inlines the contents of the referenced file right into the task, saving a
241
+ separate agent turn to read it. `@path` is recognized at a word boundary (at
242
+ the start of a line or after a space/bracket/quote) with a file extension;
243
+ `user@host` and decorators (`@Component`) are not touched. Existing files are
244
+ inlined (60 KB per file, 200 KB total — beyond that a truncation marker),
245
+ missing ones stay as-is. Helper `extractAtFileRefs()` in `src/path-token.ts`
246
+ (pure, unit-tested).
176
247
 
177
248
  ### Changed
178
249
 
179
- - `AGENTS.md` уменьшен (BACKLOG C2): глубокие root-cause разборы («агент
180
- остановился», чтение ответа из DOM, send-хуки, `LineEditor`, вложения) и
181
- терминальная механика вынесены в `docs/DESIGN-NOTES.md` (progressive
182
- disclosure — не грузится в каждую задачу). В AGENTS.md остались действующие
183
- правила и краткая выжимка со ссылкой.
250
+ - `AGENTS.md` trimmed (BACKLOG C2): the deep root-cause write-ups ("the agent
251
+ stopped", reading the answer from the DOM, send hooks, `LineEditor`,
252
+ attachments) and the terminal mechanics were moved to
253
+ `docs/DESIGN-NOTES.md` (progressive disclosure — not loaded into every task).
254
+ AGENTS.md keeps the acting rules plus a short summary with a pointer.
184
255
 
185
256
  ## [2.62.0]
186
257
 
@@ -305,8 +376,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
305
376
  - config, sessions and undo now share the single atomic writer (the
306
377
  temp-file+rename logic used to be copy-pasted in three places).
307
378
 
308
- ### Fixed
309
-
310
379
  - BACKLOG.md is now git-ignored and untracked: it is the agent's own
311
380
  improvement-notes file, rewritten constantly, and used to be published with
312
381
  the package.
@@ -343,8 +412,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
343
412
  - `npm run build` now wipes `dist/` first (`scripts/clean-dist.mjs`), so a
344
413
  removed source file no longer leaves an orphan `.js` in the package.
345
414
 
346
- ### Fixed
347
-
348
415
  - `/undo` failure reasons are localized: `src/undo.ts` returned hardcoded
349
416
  Russian strings that showed up in an English UI. It now returns machine codes
350
417
  ('empty' / 'disabled') localized by the caller.
@@ -379,8 +446,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
379
446
  (diff/apply) stays `.ts`-only.
380
447
  - `package.json` gained `packageManager: npm@11.19.0`.
381
448
 
382
- ### Fixed
383
-
384
449
  - The status line no longer prefixes a running-tool indicator with the
385
450
  previous answer's "done" phase (e.g. `✓ done: running Bash`): `toolCall()`
386
451
  clears the send-phase state before starting the tool animation.
@@ -416,8 +481,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
416
481
 
417
482
  ## [2.53.1]
418
483
 
419
- ### Fixed
420
-
421
484
  - `require('fs')` in ESM modules (`src/attachments.ts`, `src/self-review.ts`)
422
485
  threw `ReferenceError` at runtime: WSL clipboard detection always returned
423
486
  `false` and `/self-review` found no sources when run from the built `dist/`.
package/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
  </p>
20
20
 
21
21
  <p align="center">
22
- <strong>English</strong> | <a href="https://github.com/Viqto0r/zames_pro/blob/master/README.ru.md">Русский</a>
22
+ <strong>English</strong> | <a href="#readme-ru">Русский</a>
23
23
  </p>
24
24
 
25
25
  A terminal coding agent that works on top of [chat.deepseek.com](https://chat.deepseek.com/) through Playwright.
@@ -486,6 +486,7 @@ first-party API client.
486
486
 
487
487
  MIT
488
488
 
489
+ <a id="readme-ru" name="readme-ru"></a>
489
490
  <details>
490
491
  <summary>🇷🇺 Читать по-русски (Russian)</summary>
491
492
 
@@ -399,23 +399,6 @@ export function writeClipboardText(text) {
399
399
  }
400
400
  return { ok: false, via: candidates.map((c) => c.bin).join('/') };
401
401
  }
402
- export async function findFileByName(workdir, name) {
403
- const candidate = path.resolve(workdir, name);
404
- if (await fs.stat(candidate).catch(() => null))
405
- return candidate;
406
- try {
407
- const { glob } = await import('fs/promises');
408
- for await (const f of glob('**/' + name, { cwd: workdir })) {
409
- const abs = path.resolve(workdir, f);
410
- const rel = path.relative(workdir, abs);
411
- if (rel.startsWith('..') || path.isAbsolute(rel))
412
- continue;
413
- return abs;
414
- }
415
- }
416
- catch { }
417
- return null;
418
- }
419
402
  // Read file paths from the Windows clipboard (copied files, CF_HDROP) and
420
403
  // translate them to WSL paths. On WSL the user often copies a FILE, not an
421
404
  // image. Returns [] on non-WSL / when nothing is there.
package/dist/context.js CHANGED
@@ -58,9 +58,6 @@ const BUILTIN_SKILLS = [
58
58
  '4. Do NOT change code. Answer in the operator language.',
59
59
  },
60
60
  ];
61
- export function builtinSkills() {
62
- return BUILTIN_SKILLS;
63
- }
64
61
  const MAX_FILE_BASE = 60000;
65
62
  const MAX_TOTAL = 240000;
66
63
  const CRLF_RE = /\r\n/g;
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
+ import { safePath } from './sandbox.js';
3
4
  // Extra tools that bring zames closer to Claude Code / Codex CLI:
4
5
  // * LS - list a directory (Claude Code has LS; Codex has list_dir)
5
6
  // * MultiEdit - several edits to ONE file applied atomically (Claude Code)
@@ -171,14 +172,7 @@ export function applyUpdateHunk(content, hunkLines) {
171
172
  export function createExtraTools(workdir, { undo, todos } = {}) {
172
173
  const todoStore = todos || defaultTodoStore;
173
174
  const root = path.resolve(workdir);
174
- const safe = (p) => {
175
- const resolved = path.resolve(root, p);
176
- const rel = path.relative(root, resolved);
177
- if (rel.startsWith('..') || path.isAbsolute(rel)) {
178
- throw new Error('Access outside the working directory is forbidden: ' + p);
179
- }
180
- return resolved;
181
- };
175
+ const safe = (p) => safePath(root, p);
182
176
  // Guard against missing/placeholder required args: a missing `path` becomes
183
177
  // String(undefined) === "undefined" and would create a file literally named
184
178
  // "undefined" in the working directory.
@@ -271,9 +265,14 @@ export function createExtraTools(workdir, { undo, todos } = {}) {
271
265
  throw new Error(`edits[${i}]: string occurs ${occurrences} times. ` +
272
266
  'Make old_string more specific or pass replace_all=true.');
273
267
  }
274
- content = e.replace_all
275
- ? content.split(oldStr).join(newStr)
276
- : content.replace(oldStr, newStr);
268
+ if (e.replace_all) {
269
+ content = content.split(oldStr).join(newStr);
270
+ }
271
+ else {
272
+ // Functional replacer: a string replacer would interpret $-patterns
273
+ // ($&, $$, $1) inside newStr and corrupt the written text.
274
+ content = content.replace(oldStr, () => newStr);
275
+ }
277
276
  }
278
277
  if (undo)
279
278
  await undo.backup(file);
package/dist/fsutil.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs';
2
+ import path from 'path';
2
3
  // One atomic writer shared by config, sessions and undo. Writing to a temp
3
4
  // file in the SAME directory and renaming over the target is atomic on POSIX
4
5
  // and Windows (same volume), so a crash or Ctrl+C mid-write can never leave a
@@ -8,10 +9,46 @@ import fs from 'fs';
8
9
  // Kept in its own module because the pattern used to be copy-pasted in at
9
10
  // least three places; drift between the copies is exactly how one of them
10
11
  // silently loses its atomicity.
12
+ //
13
+ // The file is created 0o600: ~/.zames/config.json holds the
14
+ // browser password in clear text alongside sessions/undo history, and the
15
+ // default umask (022) would make them readable by other users on a
16
+ // multi-user machine. We also chmod the target after rename because a
17
+ // pre-existing file keeps its old mode (rename does not change the inode).
11
18
  export function writeFileAtomic(file, data) {
12
19
  const tmp = file + '.tmp-' + process.pid;
13
- fs.writeFileSync(tmp, data);
20
+ // fsync the file BEFORE rename: without it a power loss can leave the
21
+ // renamed name pointing at an empty/partial inode (rename itself is atomic,
22
+ // but the data blocks may not have reached the disk yet).
23
+ const fd = fs.openSync(tmp, 'w', 0o600);
24
+ try {
25
+ fs.writeFileSync(fd, data);
26
+ fs.fsyncSync(fd);
27
+ }
28
+ finally {
29
+ fs.closeSync(fd);
30
+ }
14
31
  fs.renameSync(tmp, file);
32
+ // fsync the DIRECTORY so the rename entry itself is durable.
33
+ try {
34
+ const dirFd = fs.openSync(path.dirname(file), 'r');
35
+ try {
36
+ fs.fsyncSync(dirFd);
37
+ }
38
+ finally {
39
+ fs.closeSync(dirFd);
40
+ }
41
+ }
42
+ catch {
43
+ // Some platforms/filesystems do not allow opening a directory; best-effort.
44
+ }
45
+ // Best-effort: on Windows chmod is largely a no-op and can throw.
46
+ try {
47
+ fs.chmodSync(file, 0o600);
48
+ }
49
+ catch {
50
+ // ignore
51
+ }
15
52
  }
16
53
  export function writeJsonAtomic(file, value) {
17
54
  writeFileAtomic(file, JSON.stringify(value, null, 2) + String.fromCharCode(10));