zames_pro 2.55.0 → 2.58.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/CHANGELOG.md +209 -0
- package/README.md +107 -2
- package/dist/agent-loop.js +67 -0
- package/dist/attachments.js +32 -0
- package/dist/commands.js +52 -0
- package/dist/config.js +3 -9
- package/dist/fsutil.js +18 -0
- package/dist/gitTools.js +37 -0
- package/dist/i18n.js +114 -16
- package/dist/index.js +231 -24
- package/dist/mcp.js +20 -0
- package/dist/sessions.js +4 -8
- package/dist/spinner.js +42 -3
- package/dist/system-prompt.js +13 -0
- package/dist/tools.js +38 -5
- package/dist/transcript.js +14 -4
- package/dist/undo.js +8 -6
- package/dist/web.js +31 -0
- package/package.json +13 -4
- package/scripts/clean-dist.mjs +17 -0
- package/scripts/coverage-gate.mjs +83 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [2.58.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `--output-format json|jsonl` for one-shot runs: transcript events are
|
|
15
|
+
streamed as JSON lines on stdout (human text goes to stderr), so zames
|
|
16
|
+
pipes into `jq` and CI pipelines. `Transcript` gained an `onLine` mirror.
|
|
17
|
+
- Loop detection: four identical tool calls in a row trigger a nudge to
|
|
18
|
+
change approach instead of repeating the same call forever.
|
|
19
|
+
- MCP configs pinned to `@latest`/`@next` now warn (in `/mcp` and at
|
|
20
|
+
startup) so the tool set does not silently change between runs.
|
|
21
|
+
- `/help` is grouped into sections (session / workspace / git / agent /
|
|
22
|
+
context / files) instead of one long list.
|
|
23
|
+
- `Grep` accepts a comma-separated `include` list.
|
|
24
|
+
- System prompt: a "Choosing the right tool" section (Edit vs MultiEdit vs
|
|
25
|
+
ApplyPatch, Grep vs Glob vs LS, Git tools vs Bash).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `coverage-gate` enforces floors for 8 more modules and prints the
|
|
30
|
+
lowest-covered modules in the CI log.
|
|
31
|
+
- README: Why zames?, an FAQ and a machine-readable-output section.
|
|
32
|
+
- Dependabot config for npm and GitHub Actions.
|
|
33
|
+
|
|
34
|
+
## [2.57.1]
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- CI workflows use actions/checkout@v5 and actions/setup-node@v5 (the v4
|
|
39
|
+
actions run on the deprecated Node 20 runtime and showed up as an
|
|
40
|
+
annotation on every run).
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- README badges (npm version/downloads, CI status, license, Node, PRs) and a
|
|
45
|
+
Features section, so the GitHub landing page and the npm page show what the
|
|
46
|
+
project is at a glance.
|
|
47
|
+
- Community files: issue forms (bug report, feature request), a PR template
|
|
48
|
+
and CODE_OF_CONDUCT.md.
|
|
49
|
+
- package.json: a fuller description and more keywords (npm search), and
|
|
50
|
+
CHANGELOG.md is now included in the published files.
|
|
51
|
+
|
|
52
|
+
## [2.57.0]
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- Plan mode (read-only): start with --plan or toggle with /plan [on|off].
|
|
57
|
+
In this mode mutating tools (Write/Edit/MultiEdit/ApplyPatch/Bash, GitAdd/
|
|
58
|
+
GitCommit/GitPush) are removed from the tool set entirely, so the agent can
|
|
59
|
+
investigate without touching the tree.
|
|
60
|
+
- src/fsutil.ts - one shared atomic writer (writeFileAtomic/writeJsonAtomic).
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- config, sessions and undo now share the single atomic writer (the
|
|
65
|
+
temp-file+rename logic used to be copy-pasted in three places).
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- BACKLOG.md is now git-ignored and untracked: it is the agent's own
|
|
70
|
+
improvement-notes file, rewritten constantly, and used to be published with
|
|
71
|
+
the package.
|
|
72
|
+
- GitPush validates the branch name before building the shell command, so a
|
|
73
|
+
model-supplied branch cannot smuggle shell metacharacters.
|
|
74
|
+
- SECURITY.md no longer promises a /permissions command / alwaysConfirm list
|
|
75
|
+
that was removed in 2.55.0; AGENTS.md cleaned of the same dead refs.
|
|
76
|
+
|
|
77
|
+
## [2.56.0]
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- `/context` — show what is loaded into the prompt (AGENTS.md, MEMORY.md,
|
|
82
|
+
skills, custom commands) plus the system-prompt size.
|
|
83
|
+
- `/retry` — resend the last task into the same chat (handy after a truncated
|
|
84
|
+
or empty answer).
|
|
85
|
+
- `/rename <title>` — set the current session title (shown in `/sessions`).
|
|
86
|
+
- `/diffstat` — a one-line `git diff --stat` change summary.
|
|
87
|
+
- `/copy` — copy the last assistant answer to the OS clipboard (wl-copy /
|
|
88
|
+
xclip / xsel / pbcopy / clip).
|
|
89
|
+
- `GitShow` and `GitBranchList` git tools (read-only).
|
|
90
|
+
- The status line now shows the elapsed time of the current phase ("45s",
|
|
91
|
+
"2m 05s") in BOTH the LineEditor and the non-TTY spinner, and the spinner
|
|
92
|
+
shows the same "tasks: 2/5" badge the editor does.
|
|
93
|
+
- A one-time hint when the context reaches ~80% while auto-compact is off,
|
|
94
|
+
pointing at `/compact`.
|
|
95
|
+
- Tests: dead-i18n-key guard, agent-facing-no-Cyrillic guard, CLI flag
|
|
96
|
+
consistency, binary content-type handling for `WebFetch`.
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- `WebFetch` returns a short human note for binary content types (PDF, images,
|
|
101
|
+
archives, ...) instead of dumping raw bytes into the model context.
|
|
102
|
+
- `npm run build` now wipes `dist/` first (`scripts/clean-dist.mjs`), so a
|
|
103
|
+
removed source file no longer leaves an orphan `.js` in the package.
|
|
104
|
+
|
|
105
|
+
### Fixed
|
|
106
|
+
|
|
107
|
+
- `/undo` failure reasons are localized: `src/undo.ts` returned hardcoded
|
|
108
|
+
Russian strings that showed up in an English UI. It now returns machine codes
|
|
109
|
+
('empty' / 'disabled') localized by the caller.
|
|
110
|
+
- Removed the dead `--project` flag (it only swallowed the following argument)
|
|
111
|
+
and the no-op `--calibrate` flag together with their help/i18n entries.
|
|
112
|
+
- Removed the orphaned `confirm.hint` / `confirm.ask_label` i18n keys left by
|
|
113
|
+
the confirm-subsystem removal.
|
|
114
|
+
- README Node requirement aligned with `engines` / `.nvmrc` (>= 20).
|
|
115
|
+
- `GitBranchList` quotes its `--format` argument so the POSIX shell does not
|
|
116
|
+
choke on the parentheses/brackets in the format string.
|
|
117
|
+
|
|
118
|
+
## [2.55.0]
|
|
119
|
+
|
|
120
|
+
### Added
|
|
121
|
+
|
|
122
|
+
- `!command` in the prompt runs a shell command directly, bypassing the model
|
|
123
|
+
(like Claude Code's bash mode). The same sandbox guard as the `Bash` tool
|
|
124
|
+
applies, so a direct command cannot leave the project.
|
|
125
|
+
- `/remember <text>` appends a durable note to the project `MEMORY.md`, so a
|
|
126
|
+
fact worth keeping across sessions is written to disk instead of living only
|
|
127
|
+
in the chat.
|
|
128
|
+
- `/skills`, `/memory`, `/remember`, `/init` and `/mcp` are now listed in the
|
|
129
|
+
`/help` output (they were only in the «/» completion list before).
|
|
130
|
+
|
|
131
|
+
### Changed
|
|
132
|
+
|
|
133
|
+
- The shell runner moved to `src/shell.ts`, shared by the `Bash` tool and the
|
|
134
|
+
new `!command` escape (one sandbox guard, one abort wiring).
|
|
135
|
+
- `/self-review` now copies root configs (`package.json`, `eslint.config.js`,
|
|
136
|
+
`tsconfig*.json`, `.prettierrc`) and `scripts/**` into a read-only `_context/`
|
|
137
|
+
folder of the snapshot, so the reviewer can read them. The editable set
|
|
138
|
+
(diff/apply) stays `.ts`-only.
|
|
139
|
+
- `package.json` gained `packageManager: npm@11.19.0`.
|
|
140
|
+
|
|
141
|
+
### Fixed
|
|
142
|
+
|
|
143
|
+
- The status line no longer prefixes a running-tool indicator with the
|
|
144
|
+
previous answer's "done" phase (e.g. `✓ done: running Bash`): `toolCall()`
|
|
145
|
+
clears the send-phase state before starting the tool animation.
|
|
146
|
+
|
|
147
|
+
### Removed
|
|
148
|
+
|
|
149
|
+
- The never-wired tool-confirmation subsystem: `src/confirm.ts`
|
|
150
|
+
(`ConfirmManager`/`formatDiffPreview`) was fully implemented and tested but
|
|
151
|
+
never called by the runtime, so `confirmation.write/edit/bash`,
|
|
152
|
+
`alwaysConfirm` and the `/permissions` command did nothing. Removed the
|
|
153
|
+
module, its tests, the config keys, the `/permissions` command and the
|
|
154
|
+
related i18n strings.
|
|
155
|
+
|
|
156
|
+
## [2.54.0]
|
|
157
|
+
|
|
158
|
+
### Added
|
|
159
|
+
|
|
160
|
+
- New unit tests for previously uncovered modules: `diff.ts` (unified/color
|
|
161
|
+
diff), `spinner.ts` (ellipsis/dots/UI surface), `confirm.ts`
|
|
162
|
+
(confirmation logic + alwaysConfirm fallback) and `markdown.ts`
|
|
163
|
+
(rendering of headings, lists, code, tables).
|
|
164
|
+
|
|
165
|
+
- `WebFetch` now blocks private/loopback/link-local addresses (SSRF guard,
|
|
166
|
+
including cloud metadata `169.254.169.254`) and retries transient network
|
|
167
|
+
errors and 5xx responses with exponential backoff.
|
|
168
|
+
- CI prints per-file test coverage (`node --experimental-test-coverage`) so
|
|
169
|
+
untested modules are visible in the log (informational, does not fail).
|
|
170
|
+
|
|
171
|
+
### Changed
|
|
172
|
+
|
|
173
|
+
- The `pre-push` git hook no longer runs the full test suite (it was fragile
|
|
174
|
+
and timed out on tag pushes); tests stay in `pre-commit` and CI.
|
|
175
|
+
|
|
176
|
+
## [2.53.1]
|
|
177
|
+
|
|
178
|
+
### Fixed
|
|
179
|
+
|
|
180
|
+
- `require('fs')` in ESM modules (`src/attachments.ts`, `src/self-review.ts`)
|
|
181
|
+
threw `ReferenceError` at runtime: WSL clipboard detection always returned
|
|
182
|
+
`false` and `/self-review` found no sources when run from the built `dist/`.
|
|
183
|
+
- Config, session and undo index writes are now atomic (temp file + rename),
|
|
184
|
+
so a crash or Ctrl+C mid-write can no longer corrupt `~/.zames/config.json`
|
|
185
|
+
or a saved session.
|
|
186
|
+
|
|
187
|
+
### Changed
|
|
188
|
+
|
|
189
|
+
- Removed the unused `marked` dependency (Markdown is rendered by `markdansi`).
|
|
190
|
+
- CI installs dependencies with `npm ci` for reproducible builds.
|
|
191
|
+
- The i18n test now validates the whole `CATALOG` instead of a small sample.
|
|
192
|
+
- Added `.nvmrc` (Node 24).
|
|
193
|
+
|
|
194
|
+
## [2.53.0]
|
|
195
|
+
|
|
196
|
+
### Added
|
|
197
|
+
|
|
198
|
+
- Session banner warns when no saved DeepSeek session exists.
|
|
199
|
+
- `--no-color` flag (explicit `NO_COLOR`).
|
|
200
|
+
|
|
201
|
+
[Unreleased]: https://github.com/Viqto0r/zames_pro/compare/v2.58.0...HEAD
|
|
202
|
+
[2.58.0]: https://github.com/Viqto0r/zames_pro/compare/v2.57.1...v2.58.0
|
|
203
|
+
[2.57.1]: https://github.com/Viqto0r/zames_pro/compare/v2.57.0...v2.57.1
|
|
204
|
+
[2.57.0]: https://github.com/Viqto0r/zames_pro/compare/v2.56.0...v2.57.0
|
|
205
|
+
[2.56.0]: https://github.com/Viqto0r/zames_pro/compare/v2.55.0...v2.56.0
|
|
206
|
+
[2.55.0]: https://github.com/Viqto0r/zames_pro/compare/v2.54.0...v2.55.0
|
|
207
|
+
[2.54.0]: https://github.com/Viqto0r/zames_pro/compare/v2.53.1...v2.54.0
|
|
208
|
+
[2.53.1]: https://github.com/Viqto0r/zames_pro/compare/v2.53.0...v2.53.1
|
|
209
|
+
[2.53.0]: https://github.com/Viqto0r/zames_pro/releases/tag/v2.53.0
|
package/README.md
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
1
|
# zames_pro
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/zames_pro)
|
|
4
|
+
[](https://www.npmjs.com/package/zames_pro)
|
|
5
|
+
[](https://github.com/Viqto0r/zames_pro/actions/workflows/test.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](package.json)
|
|
8
|
+
[](CONTRIBUTING.md)
|
|
9
|
+
|
|
3
10
|

|
|
4
11
|
|
|
5
12
|
A terminal coding agent that works on top of [chat.deepseek.com](https://chat.deepseek.com/) through Playwright.
|
|
6
13
|
In spirit it is similar to Claude Code / Codex CLI: it starts in the current
|
|
7
14
|
directory, reads and edits files, runs commands, and commits to git.
|
|
8
15
|
|
|
16
|
+
> No API key required — it drives the DeepSeek web chat like a regular user
|
|
17
|
+
> through a real (headless) browser.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Tools like Claude Code / Codex** — `Read`, `Write`, `Edit`, `Bash`,
|
|
22
|
+
`Glob`, `Grep`, plus `MultiEdit`, `ApplyPatch`, `LS`, `TodoWrite`, git and web
|
|
23
|
+
tools. Every edit is backed by `/undo`.
|
|
24
|
+
- **Runs while you keep typing** — queue messages during a task; they run right
|
|
25
|
+
after it, in the same chat (like typing during generation on the web).
|
|
26
|
+
- **Plan mode** — `/plan` (or `--plan`) drops all mutating tools, so the agent
|
|
27
|
+
can investigate the code without touching the tree.
|
|
28
|
+
- **Project context** — reads `AGENTS.md`, `MEMORY.md`, skills (`SKILL.md`) and
|
|
29
|
+
custom commands from the repo and `~/.zames`, the same idea as Codex / Claude
|
|
30
|
+
Code.
|
|
31
|
+
- **MCP support** — plug in external tool servers (e.g. `@playwright/mcp`).
|
|
32
|
+
- **Self-review** — `/self-review` snapshots `src/` so the agent can review and
|
|
33
|
+
fix itself in a sandbox (`/self-fix`, `/self-apply`).
|
|
34
|
+
- **Scheduling** — `/loop`, `/cron` and `/jobs` repeat tasks on a timer.
|
|
35
|
+
- **Bilingual UI** — Russian / English (`/config lang`).
|
|
36
|
+
|
|
37
|
+
## Why zames?
|
|
38
|
+
|
|
39
|
+
- **No API key, no per-token bill** — it uses your own DeepSeek chat account,
|
|
40
|
+
not the paid API. Good for long, tool-heavy tasks.
|
|
41
|
+
- **Same workflow as Claude Code / Codex** — tools, `AGENTS.md`, skills, MCP
|
|
42
|
+
and slash commands, so it feels familiar from day one.
|
|
43
|
+
- **Runs unattended** — headless by default, resumable sessions, and `/loop`
|
|
44
|
+
plus `/cron` for scheduled work.
|
|
45
|
+
- **Local-first** — the browser profile, credentials and logs never leave your
|
|
46
|
+
machine, and the agent is sandboxed to the project directory.
|
|
47
|
+
|
|
9
48
|
## Requirements
|
|
10
49
|
|
|
11
|
-
- Node.js >=
|
|
50
|
+
- Node.js >= 20 (CI and development use Node 24; see `.nvmrc`)
|
|
12
51
|
- A DeepSeek account. On first launch zames asks for your DeepSeek
|
|
13
52
|
login/password in the terminal (and stores them in `~/.zames/config.json`
|
|
14
53
|
after a successful sign-in, so a later logout is handled automatically
|
|
@@ -46,6 +85,13 @@ version). You do not need `--headed` just to log in.
|
|
|
46
85
|
Credentials and toggles can also be edited from `/config`
|
|
47
86
|
(`browser.auth.username`, `browser.auth.password`, `browser.auth.saveSession`).
|
|
48
87
|
|
|
88
|
+
## Links
|
|
89
|
+
|
|
90
|
+
- npm: <https://www.npmjs.com/package/zames_pro>
|
|
91
|
+
- Changelog: [`CHANGELOG.md`](CHANGELOG.md)
|
|
92
|
+
- Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
93
|
+
- Security policy: [`SECURITY.md`](SECURITY.md)
|
|
94
|
+
|
|
49
95
|
## Installation
|
|
50
96
|
|
|
51
97
|
```bash
|
|
@@ -127,11 +173,30 @@ zames --resend-prompt
|
|
|
127
173
|
zames --dir <path>
|
|
128
174
|
zames --headless
|
|
129
175
|
zames --headed
|
|
176
|
+
zames --plan
|
|
177
|
+
zames --output-format jsonl
|
|
130
178
|
zames --debug
|
|
131
179
|
zames --version
|
|
132
180
|
zames --help
|
|
133
181
|
```
|
|
134
182
|
|
|
183
|
+
### Machine-readable output (scripts / CI)
|
|
184
|
+
|
|
185
|
+
For one-shot runs (`--task`) the agent can emit its event stream as JSON lines
|
|
186
|
+
on stdout, keeping all human text on stderr:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
zames --task "summarize the diff" --output-format jsonl
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Each line is one event (`tool_call`, `tool_result`, `assistant_final`, ...), so
|
|
193
|
+
it pipes straight into `jq`:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
zames --task "..." --output-format jsonl \
|
|
197
|
+
| jq -r 'select(.type=="assistant_final") | .message'
|
|
198
|
+
```
|
|
199
|
+
|
|
135
200
|
## Tools
|
|
136
201
|
|
|
137
202
|
The agent has the same style of tools as Claude Code / Codex CLI:
|
|
@@ -142,7 +207,8 @@ The agent has the same style of tools as Claude Code / Codex CLI:
|
|
|
142
207
|
- **Extra tools** — `LS` (list a directory), `MultiEdit` (several edits to one
|
|
143
208
|
file applied atomically), `TodoWrite` (session task checklist), `ApplyPatch`
|
|
144
209
|
(multi-file patch in Codex's V4A format: `*** Begin Patch` … `*** End Patch`).
|
|
145
|
-
- **Git** — `GitStatus`, `GitDiff`, `GitLog`, `
|
|
210
|
+
- **Git** — `GitStatus`, `GitDiff`, `GitLog`, `GitShow`, `GitBranchList`,
|
|
211
|
+
`GitAdd`, `GitCommit`, `GitPush`.
|
|
146
212
|
- **Web** — `WebFetch`, `WebSearch`.
|
|
147
213
|
- **Service** — `respond` (final answer to the operator, ends the task).
|
|
148
214
|
|
|
@@ -158,6 +224,13 @@ config commands (/new, /chats, /resume, /cd, /status, /config,
|
|
|
158
224
|
Codex CLI:
|
|
159
225
|
|
|
160
226
|
- /diff [--staged] — show the working-tree git diff (--staged for the index).
|
|
227
|
+
- /diffstat — a one-line change summary (`git diff --stat`).
|
|
228
|
+
- /context — show what is loaded into the prompt (AGENTS.md, MEMORY.md,
|
|
229
|
+
skills, custom commands) and the system-prompt size.
|
|
230
|
+
- /retry — resend the last task into the same chat (handy after a truncated
|
|
231
|
+
or empty answer).
|
|
232
|
+
- /rename <title> — set the current session title (shown in /sessions).
|
|
233
|
+
- /copy — copy the last assistant answer to the OS clipboard.
|
|
161
234
|
- /cost (alias /usage) — session stats: tasks, tool calls, duration, and the
|
|
162
235
|
context size in tokens (DeepSeek's `accumulated_token_usage`).
|
|
163
236
|
- /export [file] — write the session transcript to a Markdown file
|
|
@@ -174,6 +247,10 @@ Codex CLI:
|
|
|
174
247
|
instead of the whole list.
|
|
175
248
|
- /review [focus] [--staged] — ask the agent to review uncommitted changes
|
|
176
249
|
and report findings (no code changes).
|
|
250
|
+
- /plan [on|off] — plan (read-only) mode. While it is on, the mutating tools
|
|
251
|
+
(Write/Edit/MultiEdit/ApplyPatch/Bash, GitAdd/GitCommit/GitPush) are removed
|
|
252
|
+
from the tool set, so the agent can investigate without touching the tree.
|
|
253
|
+
Start in it with `--plan`.
|
|
177
254
|
- /compact — ask DeepSeek to compress the current chat into a handover
|
|
178
255
|
summary, then open a NEW chat, resend the system prompt and post the summary
|
|
179
256
|
as the carried-over context. Use it when the context gets long.
|
|
@@ -305,6 +382,34 @@ locale lives in `ui.locale` in the config file.
|
|
|
305
382
|
|
|
306
383
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, self-review snapshots.
|
|
307
384
|
|
|
385
|
+
## FAQ
|
|
386
|
+
|
|
387
|
+
**Is this an official DeepSeek product?**
|
|
388
|
+
No. zames drives the public chat.deepseek.com web UI through a real browser,
|
|
389
|
+
like a regular user. It is not affiliated with DeepSeek.
|
|
390
|
+
|
|
391
|
+
**Do I need an API key?**
|
|
392
|
+
No. You sign in with your own DeepSeek account once; the session is stored in
|
|
393
|
+
`~/.zames/profile` and reused.
|
|
394
|
+
|
|
395
|
+
**Which model does it use?**
|
|
396
|
+
Whichever the DeepSeek web chat uses (DeepSeek-V3, or the reasoning model with
|
|
397
|
+
"Deep thinking" on). zames never calls the API directly.
|
|
398
|
+
|
|
399
|
+
**Does it work headless / on a server?**
|
|
400
|
+
Yes — headless is the default, and `--output-format jsonl` makes it scriptable.
|
|
401
|
+
`--headed` is only needed for a manual sign-in or selector debugging.
|
|
402
|
+
|
|
403
|
+
**Is automating the web UI allowed?**
|
|
404
|
+
That depends on DeepSeek's terms; automating a website may violate them. Use at
|
|
405
|
+
your own risk and keep the send throttle (15s by default) so you do not hammer
|
|
406
|
+
the rate limit.
|
|
407
|
+
|
|
408
|
+
**How is it different from Claude Code / Codex?**
|
|
409
|
+
Same shape (tools, `AGENTS.md`, skills, MCP, slash commands) but it runs on your
|
|
410
|
+
DeepSeek account instead of an API, as a browser automation rather than a
|
|
411
|
+
first-party API client.
|
|
412
|
+
|
|
308
413
|
## License
|
|
309
414
|
|
|
310
415
|
MIT
|
package/dist/agent-loop.js
CHANGED
|
@@ -146,6 +146,12 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
146
146
|
'Look at the uploaded images in the chat; file copies are in <project>/tmp.)';
|
|
147
147
|
}
|
|
148
148
|
transcript?.log('task', { task });
|
|
149
|
+
// Each guard keeps its OWN cap IN ADDITION to the shared budget below.
|
|
150
|
+
// Reason: the shared budget is replenished after every successful tool, so
|
|
151
|
+
// over a long chain of tools a single misbehaving guard could re-ask more
|
|
152
|
+
// than intended; the per-guard cap keeps any one failure mode bounded. The
|
|
153
|
+
// named counters are all reset together after a tool runs (see the reset
|
|
154
|
+
// block after the tool loop).
|
|
149
155
|
let malformedRetries = 0;
|
|
150
156
|
const MAX_MALFORMED_RETRIES = 3;
|
|
151
157
|
let stallRetries = 0;
|
|
@@ -190,6 +196,16 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
190
196
|
// Retry budget for a browser.ask() TIMEOUT (not for content): the send did
|
|
191
197
|
// not come back in time. This is separate from unparsedRetries because a
|
|
192
198
|
// timeout is an infrastructure failure, not a model protocol violation.
|
|
199
|
+
// Loop detection: the model sometimes repeats the SAME tool call with the
|
|
200
|
+
// SAME arguments (e.g. Read the same file) turn after turn. There was no
|
|
201
|
+
// explicit detector for that — the stale-answer guard only catches a
|
|
202
|
+
// repeated ANSWER, not a repeated call. Track the signature of the last
|
|
203
|
+
// batch of calls; after MAX_REPEAT_CALLS identical batches in a row, stop
|
|
204
|
+
// running it and nudge the model to change approach.
|
|
205
|
+
let lastCallSig = '';
|
|
206
|
+
let repeatCallCount = 0;
|
|
207
|
+
const MAX_REPEAT_CALLS = 4;
|
|
208
|
+
let repeatWarned = false;
|
|
193
209
|
let afterToolRetries = 0;
|
|
194
210
|
// toolsRanInTask: how many tools actually ran in THIS task. The protocol
|
|
195
211
|
// guard uses it to tell "started, then slipped into chat mode" (a reasoning
|
|
@@ -197,6 +213,10 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
197
213
|
// short answer. We key on the STRUCTURE (work already started), not words.
|
|
198
214
|
let toolsRanInTask = 0;
|
|
199
215
|
const MAX_AFTER_TOOL_RETRIES = Math.max(0, Math.floor(maxAfterToolRetries));
|
|
216
|
+
// One-time hint when the context nears full while auto-compact is OFF:
|
|
217
|
+
// the operator is told about /compact instead of silently degrading. Set
|
|
218
|
+
// once per task so the terminal is not spammed.
|
|
219
|
+
let contextHintShown = false;
|
|
200
220
|
// Token count at which the last auto-compact fired. Re-arm only after
|
|
201
221
|
// the (fresh) chat grows past this plus a margin, so a chat that starts
|
|
202
222
|
// above the threshold does not compact on every tool call.
|
|
@@ -639,6 +659,38 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
639
659
|
// message must NOT be delivered before the tools' results are known.
|
|
640
660
|
// The model will get the tool results and can call respond again.
|
|
641
661
|
const callsToRun = realCalls.length > 0 ? realCalls : calls;
|
|
662
|
+
// Stable signature of this batch (sorted keys) for loop detection.
|
|
663
|
+
const callSig = callsToRun
|
|
664
|
+
.map((c) => c.tool +
|
|
665
|
+
':' +
|
|
666
|
+
JSON.stringify(c.args, Object.keys(c.args || {}).sort()))
|
|
667
|
+
.join('|');
|
|
668
|
+
if (callSig && callSig === lastCallSig)
|
|
669
|
+
repeatCallCount++;
|
|
670
|
+
else {
|
|
671
|
+
lastCallSig = callSig;
|
|
672
|
+
repeatCallCount = 1;
|
|
673
|
+
repeatWarned = false;
|
|
674
|
+
}
|
|
675
|
+
if (repeatCallCount >= MAX_REPEAT_CALLS) {
|
|
676
|
+
transcript?.log('tool_loop_detected', {
|
|
677
|
+
sig: callSig.slice(0, 300),
|
|
678
|
+
count: repeatCallCount,
|
|
679
|
+
});
|
|
680
|
+
if (!repeatWarned) {
|
|
681
|
+
repeatWarned = true;
|
|
682
|
+
safeWarning(translate(locale)('msg.tool_loop'));
|
|
683
|
+
}
|
|
684
|
+
// Reset so a single nudge does not carry into the next different call.
|
|
685
|
+
repeatCallCount = 0;
|
|
686
|
+
lastCallSig = '';
|
|
687
|
+
message =
|
|
688
|
+
'You have called the SAME tool with the SAME arguments several times ' +
|
|
689
|
+
'in a row. Stop repeating it. Either try a different tool or approach, ' +
|
|
690
|
+
'or, if the task is done, call respond with the final message. Do NOT ' +
|
|
691
|
+
'repeat the identical call.';
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
642
694
|
for (const call of callsToRun) {
|
|
643
695
|
const tool = tools.find((t) => t.name === call.tool);
|
|
644
696
|
if (!tool) {
|
|
@@ -784,6 +836,21 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
784
836
|
}
|
|
785
837
|
}
|
|
786
838
|
}
|
|
839
|
+
else if (getTokenUsage && !contextHintShown) {
|
|
840
|
+
// Auto-compact is OFF (the default). The context can still fill up and
|
|
841
|
+
// silently degrade the answers, so tell the operator ONCE that /compact
|
|
842
|
+
// exists. Same 80% threshold the status line uses to turn red.
|
|
843
|
+
const tokens = getTokenUsage();
|
|
844
|
+
if (tokens !== null && Number.isFinite(tokens) && tokens > 0) {
|
|
845
|
+
const pct = (tokens / contextLimit) * 100;
|
|
846
|
+
if (pct >= 80) {
|
|
847
|
+
contextHintShown = true;
|
|
848
|
+
safeWarning(translate(locale)('context.near_full', {
|
|
849
|
+
pct: String(Math.round(pct)),
|
|
850
|
+
}));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
787
854
|
}
|
|
788
855
|
return 'Iteration limit reached.';
|
|
789
856
|
}
|
package/dist/attachments.js
CHANGED
|
@@ -367,6 +367,38 @@ function tryCommand(bin, args) {
|
|
|
367
367
|
return null;
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
|
+
// Write text to the OS clipboard. Mirrors readClipboardImageDetailed: every
|
|
371
|
+
// platform has its own tool, and a failure is returned (not thrown) so the
|
|
372
|
+
// caller can tell the operator which tool was missing.
|
|
373
|
+
export function writeClipboardText(text) {
|
|
374
|
+
const data = Buffer.from(String(text ?? ''), 'utf-8');
|
|
375
|
+
const win = process.platform === 'win32';
|
|
376
|
+
const mac = process.platform === 'darwin';
|
|
377
|
+
const candidates = win
|
|
378
|
+
? [{ bin: 'clip', args: [] }]
|
|
379
|
+
: mac
|
|
380
|
+
? [{ bin: 'pbcopy', args: [] }]
|
|
381
|
+
: [
|
|
382
|
+
{ bin: 'wl-copy', args: [] },
|
|
383
|
+
{ bin: 'xclip', args: ['-selection', 'clipboard'] },
|
|
384
|
+
{ bin: 'xsel', args: ['--clipboard', '--input'] },
|
|
385
|
+
];
|
|
386
|
+
for (const c of candidates) {
|
|
387
|
+
try {
|
|
388
|
+
execFileSync(c.bin, c.args, {
|
|
389
|
+
input: data,
|
|
390
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
391
|
+
timeout: 8000,
|
|
392
|
+
windowsHide: true,
|
|
393
|
+
});
|
|
394
|
+
return { ok: true, via: c.bin };
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// try the next tool
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return { ok: false, via: candidates.map((c) => c.bin).join('/') };
|
|
401
|
+
}
|
|
370
402
|
export async function findFileByName(workdir, name) {
|
|
371
403
|
const candidate = path.resolve(workdir, name);
|
|
372
404
|
if (await fs.stat(candidate).catch(() => null))
|
package/dist/commands.js
CHANGED
|
@@ -25,6 +25,58 @@ export function formatDiff(diffText, opts = {}, t = translate('en')) {
|
|
|
25
25
|
export function diffGitArgs(staged = false) {
|
|
26
26
|
return staged ? 'git diff --staged' : 'git diff';
|
|
27
27
|
}
|
|
28
|
+
// ---------- /diffstat ----------
|
|
29
|
+
/** Render `git diff --stat` output (a compact change summary) for display. */
|
|
30
|
+
export function formatDiffStat(statText, opts = {}, t = translate('en')) {
|
|
31
|
+
const maxLines = opts.maxLines ?? 60;
|
|
32
|
+
const trimmed = String(statText ?? '')
|
|
33
|
+
.split(CR + NL)
|
|
34
|
+
.join(NL)
|
|
35
|
+
.trimEnd();
|
|
36
|
+
if (!trimmed || trimmed === '(command produced no output)') {
|
|
37
|
+
return t('diff.no_changes');
|
|
38
|
+
}
|
|
39
|
+
const lines = trimmed.split(NL);
|
|
40
|
+
if (lines.length <= maxLines)
|
|
41
|
+
return trimmed;
|
|
42
|
+
const head = lines.slice(0, maxLines).join(NL);
|
|
43
|
+
return (head + NL + t('diff.more_lines', { n: String(lines.length - maxLines) }));
|
|
44
|
+
}
|
|
45
|
+
// ---------- /context ----------
|
|
46
|
+
/** One renderable line per loaded context source (file + char count). */
|
|
47
|
+
export function formatContextSources(agents, memory, skills, commands, opts = {}, t = translate('en')) {
|
|
48
|
+
const lines = [];
|
|
49
|
+
lines.push(t('context.title'));
|
|
50
|
+
if (typeof opts.systemPromptChars === 'number') {
|
|
51
|
+
lines.push(t('context.system_prompt', { n: String(opts.systemPromptChars) }));
|
|
52
|
+
}
|
|
53
|
+
const fileLine = (p, content) => ' ' + p + ' (' + content.length + ' chars)';
|
|
54
|
+
lines.push(t('context.agents'));
|
|
55
|
+
if (agents.length)
|
|
56
|
+
for (const a of agents)
|
|
57
|
+
lines.push(fileLine(a.path, a.content));
|
|
58
|
+
else
|
|
59
|
+
lines.push(' ' + t('context.none'));
|
|
60
|
+
lines.push(t('context.memory'));
|
|
61
|
+
if (memory.length)
|
|
62
|
+
for (const m of memory)
|
|
63
|
+
lines.push(fileLine(m.path, m.content));
|
|
64
|
+
else
|
|
65
|
+
lines.push(' ' + t('context.none'));
|
|
66
|
+
lines.push(t('context.skills'));
|
|
67
|
+
if (skills.length)
|
|
68
|
+
for (const s of skills)
|
|
69
|
+
lines.push(' ' + s.name + (s.path ? ' ' + s.path : ''));
|
|
70
|
+
else
|
|
71
|
+
lines.push(' ' + t('context.none'));
|
|
72
|
+
lines.push(t('context.commands'));
|
|
73
|
+
if (commands.length)
|
|
74
|
+
for (const c of commands)
|
|
75
|
+
lines.push(' /' + c.name);
|
|
76
|
+
else
|
|
77
|
+
lines.push(' ' + t('context.none'));
|
|
78
|
+
return lines.join(NL);
|
|
79
|
+
}
|
|
28
80
|
export function summarizeTranscript(entries) {
|
|
29
81
|
const stats = {
|
|
30
82
|
turns: 0,
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|