zames_pro 2.82.49 → 2.82.51

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +159 -26
  3. package/SECURITY.md +50 -0
  4. package/dist/agent-loop.js +35 -5
  5. package/dist/answer-parse.js +1 -1
  6. package/dist/attach-flow.js +7 -3
  7. package/dist/attach-refs.js +30 -6
  8. package/dist/attachments.js +9 -0
  9. package/dist/browser-chats.js +9 -2
  10. package/dist/browser-dom.js +1 -1
  11. package/dist/browser-pure.js +9 -3
  12. package/dist/browser.js +1 -1
  13. package/dist/changelog.js +1 -1
  14. package/dist/checkpoint.js +1 -1
  15. package/dist/cli-args.js +1 -1
  16. package/dist/config-command.js +1 -1
  17. package/dist/config-commands.js +5 -2
  18. package/dist/config-live.js +10 -0
  19. package/dist/config.js +13 -1
  20. package/dist/context-command.js +6 -0
  21. package/dist/context.js +27 -4
  22. package/dist/diff.js +52 -9
  23. package/dist/extraTools.js +7 -0
  24. package/dist/glm-chats.js +18 -1
  25. package/dist/glm-net-capture.js +23 -5
  26. package/dist/goal.js +6 -1
  27. package/dist/i18n/deepseek.js +3 -3
  28. package/dist/i18n/self-review.js +4 -0
  29. package/dist/i18n.js +8 -2
  30. package/dist/index.js +90 -101
  31. package/dist/input/layout.js +33 -8
  32. package/dist/input/suggest.js +1 -1
  33. package/dist/input.js +1 -1
  34. package/dist/limits-command.js +42 -0
  35. package/dist/mcp.js +23 -4
  36. package/dist/model-command.js +21 -0
  37. package/dist/path-token.js +36 -9
  38. package/dist/permissions.js +32 -3
  39. package/dist/provider-command.js +29 -0
  40. package/dist/provider-handoff.js +18 -1
  41. package/dist/queue-command.js +15 -0
  42. package/dist/qwen-chats.js +13 -6
  43. package/dist/qwen-net-capture.js +13 -6
  44. package/dist/resume-command.js +38 -0
  45. package/dist/send-telemetry.js +15 -3
  46. package/dist/send-throttle.js +3 -1
  47. package/dist/status-command.js +68 -0
  48. package/dist/subagent.js +5 -12
  49. package/dist/tool-summary.js +8 -1
  50. package/dist/tools.js +13 -1
  51. package/dist/tui/StatusBar.js +1 -1
  52. package/dist/tui/log-window.js +41 -4
  53. package/dist/undo.js +11 -1
  54. package/dist/web.js +16 -6
  55. package/llms-full.txt +679 -0
  56. package/llms.txt +102 -0
  57. package/package.json +84 -23
package/CHANGELOG.md CHANGED
@@ -7,6 +7,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.82.51] - 2026-10-23
11
+
12
+ ### Added
13
+
14
+ - **seo:** make the project findable by search engines and AI crawlers
15
+ - **scripts:** glm-warmup opens a visible z.ai window to pass the captcha once
16
+
17
+ ### Fixed
18
+
19
+ - **subagent:** do not log subagent_start twice per Task call
20
+ - **web:** parse DuckDuckGo results whose attributes are single-quoted
21
+ - **mcp:** keep a schema whose type is a list in the tool description
22
+ - **attach:** honour the @ref total budget instead of inlining a whole file over it
23
+ - **context:** clip AGENTS.md/MEMORY.md without splitting a surrogate pair
24
+ - **loop:** a plain-text answer is re-asked with a real reason, watchdog retry no longer resends the task
25
+ - **tools:** reject a non-string path instead of writing [object Object]
26
+
27
+ ### Changed
28
+
29
+ - **comments:** drop dead backlog ids from source comments
30
+
31
+ ## [2.82.50] - 2026-10-23
32
+
33
+ ### Fixed
34
+
35
+ - **i18n:** a prototype member is not a translation
36
+ - **path-token:** split semicolon-separated @file lists
37
+ - **tui:** keep a tall log entry inside the viewport when scrolling
38
+ - **resume:** refuse a session owned by another provider
39
+ - **tui:** skip OSC sequences when measuring log rows
40
+ - **layout:** wrapToWidth keeps every line within the width
41
+ - **tools:** trim allowed-tools entries before matching
42
+ - **config:** getByPath must not walk the prototype chain
43
+ - **context:** strip a BOM before parsing the frontmatter
44
+ - **telemetry:** a provider id must not escape the log directory
45
+ - **throttle:** a non-finite retryAfterMs must not block forever
46
+ - **auth:** do not migrate a legacy GLM marker to deepseek
47
+ - **qwen:** accept a bare array under chat.history
48
+ - **qwen:** a quota_limit mention inside an answer is not a rate limit
49
+ - **glm:** a quota phrase inside an answer is not a quota error
50
+ - **glm:** order the restored dialogue by an ISO timestamp too
51
+ - **attach:** a throwing preview lookup must not abort the attach
52
+ - **handoff:** clear the side handoff files too
53
+ - **loop:** resolve a padded or upper-cased tool name
54
+ - **mcp:** honour a profile flag given inside the command
55
+ - **config:** re-derive the ask deadline when the answer timeout changes live
56
+ - **diff:** compare lines with an LCS instead of by position
57
+ - **goal:** a verdict must not match the head of a word
58
+ - **telemetry:** ignore a send event dated in the future
59
+ - **tui:** show a sandbox refusal as a failed tool call
60
+ - **config:** the list does not mask a whitespace-only password
61
+ - **attach:** never treat a URL as a local file path
62
+ - **chats:** reject a negative accumulated_token_usage on restore
63
+ - **qwen:** never return a thinking-phase object as the answer
64
+ - **glm:** reject a negative token usage
65
+ - **undo:** tolerate a corrupt undo index
66
+ - **permissions:** match a command rule in nested MCP args
67
+ - **status:** fall back to the default context window on an invalid limit
68
+ - **input:** count blank lines at the end of a paste
69
+
10
70
  ## [2.82.49] - 2026-10-22
11
71
 
12
72
  ### Fixed
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/logo.jpg" alt="zames logo" width="430">
2
+ <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/logo.jpg" alt="zames_pro logo — terminal coding agent for DeepSeek, Qwen and GLM without an API key" width="430">
3
3
  </p>
4
4
 
5
5
  <h1 align="center">zames_pro</h1>
@@ -31,8 +31,14 @@ directory, reads and edits files, runs commands, and commits to git.
31
31
  > No API key required — it drives a DeepSeek, Qwen or GLM web chat like a
32
32
  > regular user through a real (headless) browser.
33
33
 
34
+ **zames is a free, open-source terminal coding agent and AI code assistant**
35
+ that turns the web chat you already use — DeepSeek, Qwen or GLM — into a
36
+ Claude Code-style command-line agent. No API key, no per-token bill, no vendor
37
+ lock-in. Install with `npm install -g zames_pro`, run `zames` in any project
38
+ folder, and describe the task in plain language.
39
+
34
40
  <p align="center">
35
- <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/demo.png" alt="zames session in the terminal" width="860">
41
+ <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/demo.png" alt="zames terminal coding agent session — an AI coding agent working in the command line without an API key" width="860">
36
42
  </p>
37
43
 
38
44
  ## Quick start
@@ -75,7 +81,7 @@ one in place, no restart needed. Each keeps its own persistent profile
75
81
  switching never signs the other one out.
76
82
 
77
83
  <p align="center">
78
- <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="how zames works" width="860">
84
+ <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="how zames works: a terminal coding agent sends the task to a DeepSeek, Qwen or GLM web chat through Playwright and runs the tool calls locally" width="860">
79
85
  </p>
80
86
 
81
87
  In text: you type a task → zames sends the system prompt plus the task into
@@ -103,7 +109,7 @@ Key pieces:
103
109
  - [Switching providers (DeepSeek / Qwen / GLM)](#switching-providers-deepseek--qwen--glm)
104
110
  - [Tools](#tools) · [Slash commands](#slash-commands)
105
111
  - [Project context, skills and memory](#project-context-skills-and-memory) · [MCP (external tools)](#mcp-external-tools) · [Configuration](#configuration)
106
- - [FAQ](#faq) · [Links](#links) · [License](#license)
112
+ - [FAQ](#faq) · [Keywords and alternatives](#keywords-and-alternatives) · [Links](#links) · [License](#license)
107
113
 
108
114
  ## Features
109
115
 
@@ -526,22 +532,28 @@ and Qwen backends; the MCP layer is provider-agnostic, so they work with GLM
526
532
  too):
527
533
 
528
534
  - **Context7** (`@upstash/context7-mcp`) — fetches CURRENT, version-specific
529
- documentation for a library before the agent writes code, instead of relying
530
- on a stale training snapshot. Tools: `resolve_library_id` then `query_docs`.
531
- Verified live on DeepSeek AND Qwen: the agent called both tools and got the
532
- current Zod API. This is the one to add first.
535
+ documentation for a library before the agent writes code, instead of relying
536
+ on a stale training snapshot. Tools: `resolve_library_id` then `query_docs`.
537
+ Verified live on DeepSeek AND Qwen: the agent called both tools and got the
538
+ current Zod API. This is the one to add first.
533
539
 
534
540
  ```json
535
- { "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"] } }
541
+ {
542
+ "context7": {
543
+ "command": "npx",
544
+ "args": ["-y", "@upstash/context7-mcp@latest"]
545
+ }
546
+ }
536
547
  ```
537
548
 
538
549
  - **Sequential Thinking** (`@modelcontextprotocol/server-sequential-thinking`)
539
- — one `sequentialthinking` tool that gives the model an explicit, revisable
540
- plan before it acts; useful for long multi-step tasks.
550
+ — one `sequentialthinking` tool that gives the model an explicit, revisable
551
+ plan before it acts; useful for long multi-step tasks.
541
552
 
542
553
  - **Memory** (`@modelcontextprotocol/server-memory`) — a local knowledge graph
543
- (9 tools) for durable facts across chats. NOTE: zames already has `MEMORY.md`
544
- + `AGENTS.md`; add this only if you want a queryable graph.
554
+ (9 tools) for durable facts across chats. NOTE: zames already has `MEMORY.md`
555
+
556
+ * `AGENTS.md`; add this only if you want a queryable graph.
545
557
 
546
558
  Keep the stack SMALL: every server loads its tool definitions into the model's
547
559
  context. Add one when you feel its absence, not before.
@@ -618,6 +630,57 @@ Same shape (tools, `AGENTS.md`, skills, MCP, slash commands) but it runs on your
618
630
  DeepSeek, Qwen or GLM account instead of an API, as a browser automation rather
619
631
  than a first-party API client.
620
632
 
633
+ **Is zames free? Is there a free AI coding agent without an API key?**
634
+ Yes. zames is MIT-licensed and free to install (`npm install -g zames_pro`).
635
+ It sends no API requests of its own and bills nothing per token: it drives the
636
+ web chat you are already signed in to. Your only cost is whatever your DeepSeek,
637
+ Qwen or GLM account already costs — and Qwen even works in guest mode for a few
638
+ messages a day.
639
+
640
+ **Can I use it with DeepSeek? With Qwen? With GLM?**
641
+ Yes, all three, and you can switch at runtime with `/provider deepseek`,
642
+ `/provider qwen` or `/provider glm` — no restart, each backend keeps its own
643
+ signed-in browser profile.
644
+
645
+ **How do I install a terminal AI coding agent?**
646
+
647
+ ```bash
648
+ npm install -g zames_pro # Node.js >= 20; Chromium is fetched automatically
649
+ cd your-project
650
+ zames
651
+ ```
652
+
653
+ The first launch signs you in once; after that just describe the task.
654
+
655
+ **Does it work offline / with a local LLM?**
656
+ Not out of the box: zames talks to a hosted chat web app (DeepSeek, Qwen or
657
+ GLM) through a browser, so it needs a network connection and a signed-in
658
+ account. The agent itself, its tools, sessions and profiles are local.
659
+
660
+ **Can it read my repository, edit files and commit?**
661
+ Yes. It ships the usual agent tools (`Read`, `Write`, `Edit`, `Bash`,
662
+ `Grep`, `Glob`, `ApplyPatch`) plus git tools (`GitStatus`,`GitDiff`,
663
+ `GitAdd`, `GitCommit`, `GitPush`), and every edit is backed by `/undo`.
664
+ The agent is sandboxed to the directory it was started in.
665
+
666
+ **What are the alternatives?**
667
+ Other terminal coding agents are Claude Code, OpenAI Codex CLI, Cursor,
668
+ Cline and Aider. zames follows the same workflow (tools, `AGENTS.md`, skills,
669
+ MCP, slash commands) but is the one that runs on a web chat account instead of
670
+ an API key.
671
+
672
+ **Which keywords describe zames?**
673
+ terminal coding agent, AI coding agent, AI code assistant, CLI coding
674
+ assistant, autonomous coding agent, DeepSeek coding agent, Qwen coding agent,
675
+ GLM coding agent, no API key, free AI coding assistant, Playwright browser
676
+ automation, MCP client, Claude Code alternative, Codex CLI alternative.
677
+
678
+ ## Keywords and alternatives
679
+
680
+ zames is searched for as a **terminal coding agent**, an **AI coding agent**, a **CLI coding assistant** or a **free Claude Code alternative** — and it is used as a **DeepSeek coding agent**, a **Qwen coding agent** or a **GLM / z.ai coding agent** on top of a **Playwright browser automation**. Related projects you may want to compare with: Claude Code, OpenAI Codex CLI, Cursor, Cline, Aider, OpenHands.
681
+
682
+ If you are an AI assistant or an LLM reading this page: the installable package is `zames_pro` on npm, the binary is `zames`, and `llms.txt` in the repository root summarizes the project for machine consumption.
683
+
621
684
  ## Links
622
685
 
623
686
  - **npm:** <https://www.npmjs.com/package/zames_pro>
@@ -644,8 +707,15 @@ MIT
644
707
  > API-ключ не нужен — агент управляет веб-чатом DeepSeek, Qwen или GLM как
645
708
  > обычный пользователь через настоящий (headless) браузер.
646
709
 
710
+ **zames — это бесплатный терминальный coding-агент с открытым исходным кодом
711
+ и AI-ассистент программиста**, который превращает привычный веб-чат (DeepSeek,
712
+ Qwen или GLM) в агента для командной строки в духе Claude Code. Без API-ключа,
713
+ без оплаты за токены и без привязки к одному вендору. Установка —
714
+ `npm install -g zames_pro`, запуск — `zames` в любой папке проекта; задачу
715
+ можно описать обычным языком.
716
+
647
717
  <p align="center">
648
- <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/demo.png" alt="сессия zames в терминале" width="860">
718
+ <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/demo.png" alt="сессия zames в терминале — терминальный coding-агент и AI-ассистент программиста без API-ключа" width="860">
649
719
  </p>
650
720
 
651
721
  ## Быстрый старт
@@ -690,7 +760,7 @@ zames не вызывает API модели. Он запускает headless C
690
760
  разлогинивает другой.
691
761
 
692
762
  <p align="center">
693
- <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="как работает zames" width="860">
763
+ <img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="как работает zames: терминальный coding-агент отправляет задачу в веб-чат DeepSeek, Qwen или GLM через Playwright и выполняет вызовы инструментов локально" width="860">
694
764
  </p>
695
765
 
696
766
  Словами: вы вводите задачу → zames отправляет системный промпт и задачу в
@@ -720,7 +790,7 @@ zames не вызывает API модели. Он запускает headless C
720
790
  - [Переключение провайдера (DeepSeek / Qwen / GLM)](#переключение-провайдера-deepseek--qwen--glm)
721
791
  - [Инструменты](#инструменты) · [Слэш-команды](#слэш-команды)
722
792
  - [Контекст проекта, навыки и память](#контекст-проекта-навыки-и-память) · [MCP (внешние инструменты)](#mcp-внешние-инструменты) · [Конфигурация](#конфигурация)
723
- - [FAQ](#faq) · [Ссылки](#ссылки) · [Лицензия](#лицензия)
793
+ - [FAQ](#faq) · [Ключевые слова и аналоги](#ключевые-слова-и-аналоги) · [Ссылки](#ссылки) · [Лицензия](#лицензия)
724
794
 
725
795
  ## Возможности
726
796
 
@@ -1147,23 +1217,28 @@ MCP-браузер и браузер агента подерутся за оди
1147
1217
  (на бэкендах DeepSeek, Qwen и GLM):
1148
1218
 
1149
1219
  - **Context7** (`@upstash/context7-mcp`) — подтягивает АКТУАЛЬНУЮ
1150
- документацию библиотеки (для конкретной версии) до того, как агент пишет код,
1151
- вместо устаревших знаний из обучения. Инструменты: `resolve_library_id`, затем
1152
- `query_docs`. Проверено вживую на DeepSeek и Qwen (слой MCP не зависит от провайдера,
1153
- поэтому работает и с GLM): агент вызвал оба инструмента
1154
- и получил актуальный API Zod. Именно его стоит подключить первым.
1220
+ документацию библиотеки (для конкретной версии) до того, как агент пишет код,
1221
+ вместо устаревших знаний из обучения. Инструменты: `resolve_library_id`, затем
1222
+ `query_docs`. Проверено вживую на DeepSeek и Qwen (слой MCP не зависит от провайдера,
1223
+ поэтому работает и с GLM): агент вызвал оба инструмента
1224
+ и получил актуальный API Zod. Именно его стоит подключить первым.
1155
1225
 
1156
1226
  ```json
1157
- { "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"] } }
1227
+ {
1228
+ "context7": {
1229
+ "command": "npx",
1230
+ "args": ["-y", "@upstash/context7-mcp@latest"]
1231
+ }
1232
+ }
1158
1233
  ```
1159
1234
 
1160
1235
  - **Sequential Thinking** (`@modelcontextprotocol/server-sequential-thinking`)
1161
- — один инструмент `sequentialthinking`, дающий модели явный пересматриваемый
1162
- план перед действием; полезно для длинных многошаговых задач.
1236
+ — один инструмент `sequentialthinking`, дающий модели явный пересматриваемый
1237
+ план перед действием; полезно для длинных многошаговых задач.
1163
1238
 
1164
1239
  - **Memory** (`@modelcontextprotocol/server-memory`) — локальный граф знаний
1165
- (9 инструментов) для долговременных фактов между чатами. ВНИМАНИЕ: у zames уже
1166
- есть `MEMORY.md` + `AGENTS.md`; подключайте, только если нужен граф с запросами.
1240
+ (9 инструментов) для долговременных фактов между чатами. ВНИМАНИЕ: у zames уже
1241
+ есть `MEMORY.md` + `AGENTS.md`; подключайте, только если нужен граф с запросами.
1167
1242
 
1168
1243
  Держите набор МАЛЕНЬКИМ: каждый сервер грузит описания своих инструментов в
1169
1244
  контекст модели. Добавляйте, когда почувствовали нехватку, а не заранее.
@@ -1242,6 +1317,64 @@ reasoning-модель при включённом «Deep thinking»). В Qwen
1242
1317
  на вашем аккаунте DeepSeek, Qwen или GLM вместо API, как автоматизация браузера, а
1243
1318
  не first-party API-клиент.
1244
1319
 
1320
+ **zames бесплатный? Есть ли бесплатный AI coding-агент без API-ключа?**
1321
+ Да. zames распространяется по лицензии MIT и ставится бесплатно
1322
+ (`npm install -g zames_pro`). Он не делает собственных API-запросов и не
1323
+ считает токены: агент управляет веб-чатом, в который вы уже вошли. Платить
1324
+ приходится только за то, что и так стоит ваш аккаунт DeepSeek, Qwen или GLM,
1325
+ а Qwen ещё и работает в гостевом режиме несколько сообщений в день.
1326
+
1327
+ **Работает с DeepSeek? С Qwen? С GLM?**
1328
+ Да, со всеми тремя, и переключаться можно на ходу: `/provider deepseek`,
1329
+ `/provider qwen` или `/provider glm` — без перезапуска, у каждого бэкенда
1330
+ свой вошедший в аккаунт профиль браузера.
1331
+
1332
+ **Как установить терминальный AI coding-агент?**
1333
+
1334
+ ```bash
1335
+ npm install -g zames_pro # Node.js >= 20; Chromium скачается автоматически
1336
+ cd your-project
1337
+ zames
1338
+ ```
1339
+
1340
+ Первый запуск один раз попросит войти, дальше достаточно описывать задачу.
1341
+
1342
+ **Работает офлайн / с локальной LLM?**
1343
+ Из коробки — нет: zames общается с облачным веб-чатом (DeepSeek, Qwen или
1344
+ GLM) через браузер, поэтому нужны сеть и вход в аккаунт. Сам агент, его
1345
+ инструменты, сессии и профили — локальные.
1346
+
1347
+ **Он умеет читать репозиторий, править файлы и коммитить?**
1348
+ Да. В комплекте обычные инструменты агента (`Read`, `Write`, `Edit`,
1349
+ `Bash`, `Grep`, `Glob`, `ApplyPatch`) и git-инструменты (`GitStatus`,
1350
+ `GitDiff`, `GitAdd`, `GitCommit`, `GitPush`), а каждая правка
1351
+ подкреплена `/undo`. Агент запесочен в директорию запуска.
1352
+
1353
+ **Какие есть аналоги?**
1354
+ Другие терминальные coding-агенты — Claude Code, OpenAI Codex CLI, Cursor,
1355
+ Cline и Aider. zames повторяет тот же workflow (инструменты, `AGENTS.md`,
1356
+ навыки, MCP, слэш-команды), но работает на аккаунте веб-чата, а не на
1357
+ API-ключе.
1358
+
1359
+ **Какими ключевыми словами описывают zames?**
1360
+ терминальный coding-агент, AI coding-агент, AI-ассистент программиста,
1361
+ coding-агент для терминала, автономный coding-агент, DeepSeek coding-агент,
1362
+ Qwen coding-агент, GLM coding-агент, без API-ключа, бесплатный AI-ассистент,
1363
+ automation на Playwright, MCP-клиент, аналог Claude Code, аналог Codex CLI.
1364
+
1365
+ ## Ключевые слова и аналоги
1366
+
1367
+ zames ищут как **терминальный coding-агент**, **AI coding-агент**,
1368
+ **AI-ассистент программиста** или **бесплатный аналог Claude Code** — и
1369
+ используют как **DeepSeek coding-агент**, **Qwen coding-агент** или
1370
+ **GLM / z.ai coding-агент** поверх **автоматизации браузера на Playwright**.
1371
+ Сравнить стоит с Claude Code, OpenAI Codex CLI, Cursor, Cline, Aider и
1372
+ OpenHands.
1373
+
1374
+ Если вы AI-ассистент или LLM и читаете эту страницу: устанавливаемый пакет —
1375
+ `zames_pro` в npm, бинарник — `zames`, а `llms.txt` в корне репозитория
1376
+ кратко описывает проект для машинного чтения.
1377
+
1245
1378
  ## Ссылки
1246
1379
 
1247
1380
  - **npm:** <https://www.npmjs.com/package/zames_pro>
package/SECURITY.md ADDED
@@ -0,0 +1,50 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please do NOT open a public issue for security problems. Report them privately
6
+ to the maintainer (open a GitHub security advisory on the repository, or email
7
+ the address in `package.json`). Include a description, steps to reproduce and
8
+ the affected version.
9
+
10
+ ## Scope and threat model
11
+
12
+ zames is a local CLI that drives a browser and runs tools on your machine. It
13
+ is not a sandbox:
14
+
15
+ - File tools are confined to the working directory: `../` traversal is rejected
16
+ AND a symlink pointing outside the root is rejected (the real path is
17
+ resolved with `fs.realpath`). This is a guard against accidents and the
18
+ common escape vectors, not full OS isolation — `Bash` runs a real shell as
19
+ your user. Treat every task you give the agent as code you are willing to
20
+ run.
21
+ - The chat-provider credentials in `~/.zames/config.json` and the browser
22
+ profiles (`~/.zames/profile`, `~/.zames/qwen-profile`) are sensitive. They
23
+ never leave your machine. Files
24
+ written by the agent's own state/config writers are created with `0600`
25
+ permissions, but files created by your own tools or by `Bash` follow your
26
+ umask.
27
+ - `WebFetch` blocks private/loopback/link-local addresses (including the cloud
28
+ metadata endpoint `169.254.169.254`) to limit SSRF, and it RE-VALIDATES every
29
+ redirect hop. This remains best-effort (a DNS rebind between the check and
30
+ the fetch is out of scope).
31
+ - **Permissions** (`.zames/permissions.json`): an optional policy file with
32
+ `deny` / `ask` / `allow` rules on the tool name, a Bash command or a file
33
+ path. `deny` blocks a call, `ask` prompts you in the terminal (a non-TTY run
34
+ denies). If you do not create the file, every tool runs without asking — the
35
+ default is `allow`. Path rules are matched against the `path`-style args of
36
+ the built-in tools, against the file headers of an `ApplyPatch`, and against
37
+ path-looking string arguments of MCP tools; a rule cannot see inside an
38
+ arbitrary Bash command.
39
+ - **Plan / read-only mode** (`--plan`, `/plan`) removes the mutating built-in
40
+ tools from the model's tool set. Since this release it also drops MCP tools
41
+ whose names look mutating (click/type/write/...); the classification is a
42
+ heuristic deny-list, so treat plan mode as a strong hint rather than a hard
43
+ guarantee for third-party MCP servers.
44
+ - `Write`/`Edit` and the multi-file tools keep a backup (`/undo`,
45
+ `/undo-list`), but that is an undo safety net, not a security boundary. Give
46
+ the agent only tasks you are willing to run.
47
+
48
+ ## Supported versions
49
+
50
+ Only the latest published version receives fixes.
@@ -367,6 +367,18 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
367
367
  }));
368
368
  if (afterToolRetries < MAX_AFTER_TOOL_RETRIES) {
369
369
  afterToolRetries++;
370
+ // The retry used to `continue` with the UNCHANGED `message`: a bare
371
+ // re-send of the same task text. The model then saw the task arrive a
372
+ // second time and answered it CONVERSATIONALLY ("you already have the
373
+ // prompt above…") instead of resuming the work — a real transcript
374
+ // showed exactly that after a watchdog timeout. The retry must say
375
+ // that the turn was cut off and demand a tool call.
376
+ message =
377
+ 'The previous turn was cut off (the answer did not arrive in time), so the work is NOT finished. ' +
378
+ 'Continue from where you stopped: reply with EXACTLY one JSON tool-call object, ' +
379
+ 'no text before or after, for example: ' +
380
+ '{"tool": "Bash", "args": {"command": "..."}}. ' +
381
+ 'If the task is really done — call respond with the final message.';
370
382
  await new Promise((r) => setTimeout(r, 1500));
371
383
  continue;
372
384
  }
@@ -485,6 +497,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
485
497
  : 'For example: a small JSON tool-call object';
486
498
  message =
487
499
  'Your previous answer was not recognized as a tool call. ' +
500
+ 'Do NOT reply with prose, explanations or the task text — the operator never sees them and the work does not move. ' +
488
501
  'Reply with EXACTLY one JSON tool-call object, no text before or after. ' +
489
502
  'Do NOT use XML/DSML tags — plain JSON only. ' +
490
503
  truncHint;
@@ -527,6 +540,8 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
527
540
  message =
528
541
  'Continue the task. If you need a tool — reply with EXACTLY ' +
529
542
  'one JSON tool-call object: {"tool": "...", "args": {...}}. ' +
543
+ 'Do NOT repeat the task text or describe what you are about to do — ' +
544
+ 'act with a tool call. ' +
530
545
  'If the task is done — call the respond tool with the final message.';
531
546
  continue;
532
547
  }
@@ -554,6 +569,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
554
569
  'It looks like you meant to call a tool but did not. ' +
555
570
  'If the task is not finished — reply with EXACTLY one JSON ' +
556
571
  'tool-call object, no text before or after. ' +
572
+ 'Do NOT restate the task or explain your plan in prose. ' +
557
573
  'If the task is really done — call respond with the final ' +
558
574
  'message to the operator.';
559
575
  continue;
@@ -573,7 +589,11 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
573
589
  // once per task, otherwise the retry budget spams the terminal.
574
590
  if (!toolRetryWarned) {
575
591
  toolRetryWarned = true;
576
- safeWarning(tr('ds.tool_retry', {
592
+ // The notice names the ACTUAL problem: the model answered in prose
593
+ // instead of calling a tool. It used to say "the answer was not
594
+ // recognized", which made the operator suspect a parsing bug while
595
+ // the model had simply not called anything.
596
+ safeWarning(tr('ds.text_no_tool', {
577
597
  attempt: unparsedRetries,
578
598
  max: MAX_UNPARSED_RETRIES,
579
599
  }));
@@ -586,7 +606,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
586
606
  'The task is not finished. Reply with EXACTLY one JSON tool-call ' +
587
607
  'object, no text before or after, for example: ' +
588
608
  '{\"tool\": \"Bash\", \"args\": {\"command\": \"...\"}}. ' +
589
- 'Do NOT explain, plan or narrate — call a tool. ' +
609
+ 'Do NOT explain, plan, narrate or repeat the task text — call a tool. ' +
590
610
  'If the task is really done — call respond with the final message.';
591
611
  continue;
592
612
  }
@@ -958,7 +978,17 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
958
978
  }
959
979
  continue;
960
980
  }
961
- const tool = tools.find((t) => t.name === call.tool);
981
+ // Resolve the tool name TOLERANTLY: the model pads it with spaces or
982
+ // a newline (`"tool": " Bash "`) and sometimes changes its case
983
+ // (`bash`). An exact match reported `Unknown tool: Bash ` and the model
984
+ // simply repeated the same call, burning the retry budget. The first
985
+ // exact match still wins, so a name that really is distinct is not
986
+ // merged.
987
+ const wantName = String(call.tool ?? '')
988
+ .trim()
989
+ .toLowerCase();
990
+ const tool = tools.find((t) => t.name === call.tool) ??
991
+ tools.find((t) => t.name.trim().toLowerCase() === wantName);
962
992
  if (!tool) {
963
993
  const err = `Unknown tool: ${call.tool}`;
964
994
  safeToolResult(err);
@@ -1260,6 +1290,6 @@ ${diag}`;
1260
1290
  return 'Iteration limit reached.';
1261
1291
  }
1262
1292
  // The pure helpers (tool-call parser + answer heuristics) live in
1263
- // answer-parse.ts (BACKLOG C3) and are imported here for runAgentLoop. They are
1264
- // NOT re-exported: importers get them from answer-parse.js directly (C3b).
1293
+ // answer-parse.ts and are imported here for runAgentLoop. They are
1294
+ // NOT re-exported: importers get them from answer-parse.js directly .
1265
1295
  import { parseToolCall, responseLooksLikeToolCall, truncateToolResult, normForStale, looksLikeUnfinishedWork, isMeaningfulRespond, extractPreToolText, } from './answer-parse.js';
@@ -1,6 +1,6 @@
1
1
  import { parseXmlToolCalls } from './xml-toolcall.js';
2
2
  import { normText } from './browser-pure.js';
3
- // Pure, state-free helpers extracted from agent-loop.ts (BACKLOG C3): the
3
+ // Pure, state-free helpers extracted from agent-loop.ts : the
4
4
  // tool-call PARSER (JSON / permissive / XML-DSML repair) and the small answer
5
5
  // heuristics. None of them touch the browser or the loop state, so this
6
6
  // module is safe to import on its own. agent-loop.ts imports the ones the
@@ -36,9 +36,13 @@ export async function awaitUploadPreview(page, names, timeoutMs) {
36
36
  const deadline = Date.now() + timeoutMs;
37
37
  while (Date.now() < deadline) {
38
38
  for (const name of clean) {
39
- const n = await page
40
- .getByText(name, { exact: false })
41
- .count()
39
+ // The lookup itself can THROW synchronously (Playwright: "Execution
40
+ // context was destroyed" while the page navigates). `.catch` handles a
41
+ // rejected promise, not a synchronous throw — without the try/catch the
42
+ // error escaped into `_attachFiles` and the whole attach failed. A
43
+ // lookup error means "not visible yet", exactly like a failed count.
44
+ const n = await Promise.resolve()
45
+ .then(() => page.getByText(name, { exact: false }).count())
42
46
  .catch(() => 0);
43
47
  if (n > 0)
44
48
  return;
@@ -2,7 +2,7 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { looksLikeFilePath, isImageName } from './attachments.js';
4
4
  import { extractAtFileRefs } from './path-token.js';
5
- // Path resolution and `@file` reference inlining (BACKLOG B5, C3).
5
+ // Path resolution and `@file` reference inlining .
6
6
  // Extracted from src/index.ts so the logic is unit-testable without the whole
7
7
  // CLI. This module touches only the filesystem (no browser/terminal).
8
8
  const NL = String.fromCharCode(10);
@@ -27,6 +27,14 @@ export async function resolveAttachPath(workdir, raw) {
27
27
  s = s.slice(1, -1);
28
28
  }
29
29
  s = s.replace(/\\ /g, ' ');
30
+ // A URL is never a local file. `isImageName` alone would still accept
31
+ // `https://example.com/a.png` (it only looks at the extension), and the
32
+ // RELATIVE candidate `path.resolve(workdir, 'https://…')` then becomes
33
+ // `https:/…` — a project that happens to have such a directory silently
34
+ // attached the URL as a file. A URL preceded by prose words is covered too
35
+ // (the same rule as looksLikeFilePath).
36
+ if (/(?:^|\s)[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(s))
37
+ return null;
30
38
  if (!looksLikeFilePath(s) && !isImageName(s))
31
39
  return null;
32
40
  const candidates = path.isAbsolute(s)
@@ -62,12 +70,12 @@ async function isInside(workdir, target) {
62
70
  const rel = path.relative(root, real);
63
71
  return !rel.startsWith('..') && !path.isAbsolute(rel);
64
72
  }
65
- // Per-file and total caps for `@path` inlining (B5). A reference should SAVE
73
+ // Per-file and total caps for `@path` inlining . A reference should SAVE
66
74
  // round-trips, not flood the context: a huge file is truncated with a note
67
75
  // telling the agent to read the rest itself.
68
76
  export const AT_REF_FILE_CAP = 60 * 1024;
69
77
  export const AT_REF_TOTAL_CAP = 200 * 1024;
70
- // Inline `@path` references found in a task text (B5). Returns the augmented
78
+ // Inline `@path` references found in a task text . Returns the augmented
71
79
  // text plus the list of files actually inlined (for the operator notice).
72
80
  // Unknown/unreadable references are left in place untouched -- they may be
73
81
  // part of the prose; we never fail the task over a missing file.
@@ -93,8 +101,17 @@ export async function inlineAtRefs(workdir, text) {
93
101
  }
94
102
  let body = raw;
95
103
  let note = '';
96
- if (body.length > AT_REF_FILE_CAP) {
97
- let cut = AT_REF_FILE_CAP;
104
+ // The per-file cap alone lets the TOTAL budget be overshot: a file that
105
+ // fits AT_REF_FILE_CAP was inlined WHOLE even when only a few KB of the
106
+ // total were left, so four references produced a 245 KB section while the
107
+ // cap promises 200 KB. Budget the cut by what is left, and stop adding
108
+ // files once the pool is spent.
109
+ const room = AT_REF_TOTAL_CAP - total;
110
+ if (room <= 0)
111
+ break;
112
+ const cap = Math.min(AT_REF_FILE_CAP, room);
113
+ if (body.length > cap) {
114
+ let cut = cap;
98
115
  // Never end the inlined block on a lone HIGH surrogate: the cut is in
99
116
  // UTF-16 units, so an emoji / astral CJK char on the boundary would
100
117
  // reach the model as a broken code point (same rule as
@@ -103,7 +120,14 @@ export async function inlineAtRefs(workdir, text) {
103
120
  if (last >= 0xd800 && last <= 0xdbff)
104
121
  cut--;
105
122
  body = body.slice(0, cut);
106
- note = NL + NL + '(truncated at ' + AT_REF_FILE_CAP + ' chars)';
123
+ note =
124
+ NL +
125
+ NL +
126
+ '(truncated at ' +
127
+ cap +
128
+ ' chars; the task total cap is ' +
129
+ AT_REF_TOTAL_CAP +
130
+ ')';
107
131
  }
108
132
  total += body.length;
109
133
  inlined.push(path.relative(workdir, file) || file);
@@ -186,6 +186,15 @@ export function looksLikeFilePath(text) {
186
186
  return false;
187
187
  if (/[\r\n]/.test(s))
188
188
  return false;
189
+ // A URL is not a file path. It contains `/` and ends in a short extension,
190
+ // so it used to pass, and resolveAttachPath then resolved it RELATIVELY
191
+ // (`https://…` -> `https:/…`): a project that happened to contain such a
192
+ // directory silently attached the URL as a local file instead of keeping
193
+ // the text. Any `scheme://` head is rejected here — INCLUDING one that sits
194
+ // after prose words (`see https://x/a.png`), since the whole trimmed text is
195
+ // handed to this check.
196
+ if (/(?:^|\s)[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(s))
197
+ return false;
189
198
  // A bare file name with an image/known extension.
190
199
  if (looksLikeFileName(s))
191
200
  return true;
@@ -1,4 +1,4 @@
1
- // PURE parsing of the DeepSeek history/message payloads (BACKLOG C3). These
1
+ // PURE parsing of the DeepSeek history/message payloads . These
2
2
  // helpers are extracted from DeepSeekBrowser.fetchChatMessages: they used to be
3
3
  // copy-pasted INLINE twice (the in-page evaluate AND the Playwright-request
4
4
  // fallback), so the same role/fragment extraction lived in two places and could
@@ -27,7 +27,14 @@ export function parseHistoryMessages(messages) {
27
27
  const list = [];
28
28
  let usage = null;
29
29
  for (const raw of messages) {
30
- if (raw && typeof raw.accumulated_token_usage === 'number') {
30
+ // A token counter is a FINITE NON-NEGATIVE number. A negative value is a
31
+ // nonsense payload; it used to become browser._lastTokenUsage as-is, so
32
+ // the context badge vanished (tokenStatusLevel rejects a negative count)
33
+ // and the auto-compact percentage went negative and never fired.
34
+ if (raw &&
35
+ typeof raw.accumulated_token_usage === 'number' &&
36
+ Number.isFinite(raw.accumulated_token_usage) &&
37
+ raw.accumulated_token_usage >= 0) {
31
38
  usage = raw.accumulated_token_usage;
32
39
  }
33
40
  // Roles are matched case-insensitively, and a turn that is neither the
@@ -1,4 +1,4 @@
1
- // Pure in-page DOM helpers extracted from DeepSeekBrowser (BACKLOG C3).
1
+ // Pure in-page DOM helpers extracted from DeepSeekBrowser .
2
2
  //
3
3
  // Every function here runs INSIDE page.evaluate, so it MUST be fully
4
4
  // self-contained: Playwright serializes the function SOURCE and evaluates it in