autoclaw 1.3.4 โ†’ 1.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,291 +1,303 @@
1
- # AutoClaw ๐Ÿฆž
2
-
3
- [![NPM Version](https://img.shields.io/npm/v/autoclaw.svg?style=flat-square)](https://www.npmjs.com/package/autoclaw)
4
- [![NPM Downloads](https://img.shields.io/npm/dm/autoclaw.svg?style=flat-square)](https://www.npmjs.com/package/autoclaw)
5
- [![GitHub](https://img.shields.io/badge/GitHub-Repository-blue?logo=github&style=flat-square)](https://github.com/tsingliuwin/autoclaw)
6
- [![License](https://img.shields.io/npm/l/autoclaw.svg?style=flat-square)](https://github.com/tsingliuwin/autoclaw/blob/main/LICENSE)
7
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
8
-
9
- **The Engineering-First Headless Agent Framework: Stable, Scalable Automation for the Post-Vision Era.**
10
-
11
- English | [็ฎ€ไฝ“ไธญๆ–‡](./README.zh-CN.md)
12
-
13
- ---
14
-
15
- ๐Ÿ”— **GitHub Repository**: [https://github.com/tsingliuwin/autoclaw](https://github.com/tsingliuwin/autoclaw)
16
-
17
- ---
18
-
19
- AutoClaw is a high-stability, open-source automation framework specifically engineered for **headless systems**.
20
-
21
- Unlike "screen-seeing" agents (such as OpenClaw) that rely on visual interpretation, AutoClaw is built on a foundation of precise command-driven execution. This makes it significantly more **stable**, **robust from an engineering perspective**, and **easier to scale** across complex environmentsโ€”whether it's a local server, a CI/CD pipeline, or thousands of containerized nodes.
22
-
23
- ## Why AutoClaw?
24
- - ๐Ÿณ **Docker Native**: Built to run safely inside containers. Minimal footprint (Node.js/Alpine friendly).
25
- - ๐Ÿš€ **Better Engineering**: Operates via precise system APIs and shell commands rather than unstable visual recognition, ensuring deterministic outcomes.
26
- - ๐Ÿ›ก๏ธ **Superior Stability**: Immune to issues like UI rendering, screen resolution, or network lag that plague vision-based agents.
27
- - ๐Ÿ“ˆ **Massive Scalability**: Low resource consumption allows orchestrating thousands of instances (e.g., in K8s) for true automation swarms.
28
- - ๐Ÿ”Œ **Swarm Ready**: Stateless design allows for easy orchestration via K8s, Docker Swarm, or simple shell loops.
29
- - ๐Ÿงฉ **Extensible Integrations**: Built-in support for Web Search (Tavily), Email (SMTP), and Notification Webhooks (Feishu, DingTalk, WeCom).
30
-
31
- ## Features
32
-
33
- - ๐Ÿ“œ **Headless Execution**: No GUI required โ€” pure terminal efficiency. Core operation is shell + file I/O; the optional web tools run in headless Chromium.
34
- - ๐Ÿค– **Non-Interactive Mode**: Intelligent flag handling (`-y`, `--no-interactive`) for zero-touch automation.
35
- - ๐Ÿ“‚ **Universal Control**: From simple file I/O to complex system administration.
36
- - ๐Ÿ›ก๏ธ **Runaway Protection**: Max-step cap per task, API retries with exponential backoff, shell command timeouts, and tool output truncation to keep the model context bounded.
37
- - ๐Ÿง  **Context Aware**: Provides accurate OS, system and time context so relative dates ("today", "next Monday") are handled correctly.
38
- - ๐ŸŒ **Web Search**: Integrated with Tavily for real-time information retrieval.
39
- - ๐ŸŒ **Web Reading & Screenshots**: Extract article content and capture page screenshots (requires `npx playwright install chromium`).
40
- - ๐ŸŽจ **Image Generation**: DALL-E compatible image generation via any OpenAI-compatible images API.
41
- - ๐Ÿ•’ **Time Accuracy**: Built-in tool to get precise system date and time for correct temporal context.
42
- - ๐Ÿ“ง **Communication**: Send emails and push notifications to chat groups automatically.
43
-
44
- ## Tech Stack
45
- - **Runtime**: Node.js
46
- - **Language**: TypeScript
47
- - **Framework**: Commander.js
48
- - **UI**: Inquirer (interactivity), Chalk (styling), Ora (spinners)
49
- - **AI**: OpenAI SDK (any OpenAI-compatible endpoint: DeepSeek, Kimi, Qwen, GLM, Ollama, โ€ฆ)
50
- - **Web tools**: Playwright (headless Chromium for `read_website` / `take_screenshot`)
51
-
52
- ## Installation
53
-
54
- ### User Installation
55
- Install globally via npm:
56
- ```bash
57
- npm install -g autoclaw
58
- ```
59
-
60
- ### Development Installation
61
- 1. Clone the repository:
62
- ```bash
63
- git clone https://github.com/tsingliuwin/autoclaw.git
64
- cd autoclaw
65
- ```
66
- 2. Install dependencies:
67
- ```bash
68
- npm install
69
- ```
70
- 3. Build the project:
71
- ```bash
72
- npm run build
73
- ```
74
- 4. Link globally (optional):
75
- ```bash
76
- npm link
77
- ```
78
-
79
- ## Quick Start
80
-
81
- 1. **Setup**: Run the interactive setup wizard to configure your API keys and integrations. The wizard runs a live connection test (failures map to the likely wrong field: 401 = key, 404 = base URL, 400 = model name) and can list the provider's models for you to pick from.
82
- ```bash
83
- autoclaw setup
84
- ```
85
- 2. **Run**: Start the agent in interactive mode.
86
- ```bash
87
- autoclaw
88
- ```
89
-
90
- ## Usage
91
-
92
- ### Interactive Mode
93
- Simply run `autoclaw` to enter the chat loop.
94
- ```bash
95
- autoclaw
96
- > List all TypeScript files in the src folder.
97
- ```
98
- Interactive commands: `exit` / `quit` to leave, and `/view` to open the full output of the last tool result in a pager โ€” tool output longer than 20 lines is folded on screen and saved to `~/.autoclaw/output/`.
99
-
100
- ### Headless Mode (One-Shot)
101
- Run a single command and exit.
102
- ```bash
103
- autoclaw "Check disk usage and save the report to usage.txt" --no-interactive
104
- ```
105
- The exit code reports the outcome for orchestrators: `0` completed, `1` hard failure (e.g. API error), `2` step cap reached (task unfinished).
106
-
107
- ### Machine-Readable Output (--json)
108
- Add `--json` to print one JSON event per line on stdout (run_start, tool_call, tool_result, usage, run_end); human output moves to stderr, including anything tools print themselves.
109
- ```bash
110
- autoclaw "Deploy and report" -y -n --json
111
- ```
112
- Token usage is collected only when `AUTOCLOW_INCLUDE_USAGE=1` (or `true`) is set โ€” it is opt-in because not every OpenAI-compatible provider accepts `stream_options.include_usage`.
113
-
114
- ### Batch Mode (Swarm Worker)
115
- Feed a JSONL manifest of tasks; each task runs in a fresh, isolated agent (one task's context never leaks into another) and per-task results are written as JSONL:
116
- ```bash
117
- autoclaw batch tasks.jsonl -y # results -> tasks.results.jsonl
118
- autoclaw batch tasks.jsonl -o out.jsonl --fail-fast
119
- ```
120
- Manifest lines are `{"id": "...", "task": "..."}` โ€” `id` is optional (defaults to `task-N`); blank lines and `#` comments are skipped. Optional per-task overrides: `maxSteps`, `model`, `provider`.
121
-
122
- One failing task does not stop the batch (use `--fail-fast` for that). The process exits `0` when every task completed, `1` otherwise, so cron and K8s Jobs can detect bad batches. Task output stays human-readable on stdout โ€” the results file is the machine-readable contract, with `status`, `steps`, `message`, `error` and `usage` per task.
123
-
124
- Long batches can stop and pick up where they left off, and can use local parallelism:
125
- ```bash
126
- autoclaw batch big.jsonl -y --resume # skip tasks already completed in the results file
127
- autoclaw batch big.jsonl -y -c 4 # run up to 4 tasks in parallel
128
- ```
129
- Unattempted tasks are simply absent from the results file, so `--fail-fast` followed by `--resume` is a natural retry loop.
130
-
131
- AutoClaw also keeps its own prompt lean: optional tools (web search, email, group notifications, image generation) only register once their credentials are configured, and in long loops older tool results in the model context are replaced by short excerpts.
132
-
133
- ### Recipes
134
-
135
- Daily ops sweep on Linux (crontab):
136
- ```cron
137
- 0 9 * * * autoclaw batch /opt/ops/daily.jsonl -y -n --resume >> /var/log/autoclaw.log 2>&1
138
- ```
139
-
140
- Scheduled sweep on Windows (Task Scheduler):
141
- ```bash
142
- schtasks /create /tn "AutoClaw Daily" /tr "autoclaw batch C:\ops\daily.jsonl -y -n" /sc daily /st 09:00
143
- ```
144
-
145
- Pipeline inside one manifest โ€” each task writes files the next task reads:
146
- ```jsonl
147
- {"id": "sweep", "task": "ๆฃ€ๆŸฅ็ฃ็›˜ไธŽๅ…ณ้”ฎๆœๅŠก็Šถๆ€,ๆŠฅๅ‘Šๅ†™ๅ…ฅ report/sweep.md"}
148
- {"id": "notify", "task": "่ฏปๅ– report/sweep.md,็”จไธ‰ๅฅ่ฏๆ€ป็ป“ๅŽๆŽจ้€ๅˆฐ้ฃžไนฆ"}
149
- ```
150
-
151
- Diagnostics on a fresh machine or in CI:
152
- ```bash
153
- autoclaw doctor # exit 0 = ready; exit 1 = what's missing is printed
154
- ```
155
-
156
- ### Auto-Confirm (CI/CD)
157
- Automatically approve all tool executions (dangerous, use with caution or in sandboxes).
158
- ```bash
159
- autoclaw "Refactor src/index.ts to use ES modules" -y
160
- ```
161
-
162
- ### CLI Options
163
- - `-m, --model <model>`: Specify the LLM model (default: `gpt-5.6`).
164
- - `-P, --provider <name>`: Use a provider preset (see [Providers](#providers)).
165
- - `-n, --no-interactive`: Exit after processing the initial query (Headless mode).
166
- - `-y, --yes`: Auto-confirm all tool executions (e.g., shell commands).
167
- - `--allow-dangerous`: Let `-y` run clearly destructive commands (rm -rf, format, shutdown, ...) that the built-in safety gate would block.
168
- - `--json`: Emit NDJSON events on stdout (for orchestrators; use with `-n`).
169
-
170
- ### Diagnostics
171
- `autoclaw doctor` checks everything headlessly and prints โœ“/โœ— per item: config files, resolved provider/baseUrl/model, API key, a live connection test, resolved shell, registered tools, and playwright browser status. Exit `0` = ready, `1` = a critical item failed (the failing item is printed). Ideal for CI or a fresh machine.
172
-
173
- ### Providers
174
- AutoClaw works with any OpenAI-compatible endpoint. Built-in presets fill in the base URL and a default model for you:
175
- ```bash
176
- autoclaw -P deepseek "Check disk usage and save a report" -y -n
177
- ```
178
- Available presets: `openai`, `deepseek`, `moonshot` (Kimi), `dashscope` (Qwen), `zhipu` (GLM), `ark` (Volcano Ark), `siliconflow`, `openrouter`, `ollama` (local). You can still override the model with `-m` or config. When `OPENAI_API_KEY` is not set, the API key is read from the provider's own env var (e.g. `DEEPSEEK_API_KEY`, `MOONSHOT_API_KEY`, `DASHSCOPE_API_KEY`, `ZHIPU_API_KEY`, `ARK_API_KEY`, `SILICONFLOW_API_KEY`, `OPENROUTER_API_KEY`).
179
-
180
- ## Configuration
181
-
182
- AutoClaw uses a hierarchical configuration system.
183
-
184
- **Priority Order (Highest to Lowest):**
185
- 1. **CLI Arguments**: (e.g., `-m gpt-5.6`)
186
- 2. **Environment Variables**: (`OPENAI_API_KEY`, `.env` file)
187
- 3. **Project Config**: (`./.autoclaw/setting.json` in current directory)
188
- 4. **Global Config**: (`~/.autoclaw/setting.json`)
189
-
190
- ### Supported Configuration Keys (JSON)
191
- - `provider`: Provider preset name (e.g. `deepseek`).
192
- - `apiKey`: Your OpenAI API Key.
193
- - `baseUrl`: Custom Base URL (e.g., for DeepSeek or LocalLLM).
194
- - `model`: Default model to use.
195
- - `maxSteps`: Max LLM turns per task before the agent stops (default: `25`).
196
- - `shellTimeout`: Shell command timeout in milliseconds (default: `120000`).
197
- - `taskTimeoutMs`: Whole-task wall-clock timeout in milliseconds (off by default; aborts in-flight API calls and stops with `timeout` status).
198
- - `shell`: Force a shell for `execute_shell_command` (`bash`, `powershell`, `cmd`, `sh`; default: auto-detect โ€” Git Bash > PowerShell > cmd on Windows).
199
- - `tavilyApiKey`: API Key for Tavily Web Search.
200
- - `smtpHost`, `smtpPort`, `smtpUser`, `smtpPass`, `smtpFrom`: SMTP Email settings.
201
- - `feishuWebhook`, `dingtalkWebhook`, `wecomWebhook`: Notification webhooks.
202
-
203
- ### Project-Level Config Example
204
- Create a file at `.autoclaw/setting.json`:
205
- ```json
206
- {
207
- "model": "gpt-5.6",
208
- "baseUrl": "https://api.deepseek.com/v1"
209
- }
210
- ```
211
-
212
- > **โš ๏ธ Security Warning**: If you store your `apiKey` or secrets in `.autoclaw/setting.json`, make sure to add `.autoclaw/` to your `.gitignore` file to prevent leaking secrets!
213
-
214
- ### Environment Variables
215
- - `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_MODEL`: main LLM settings.
216
- - `AUTOCLOW_PROVIDER`: provider preset used when `-P` is not passed.
217
- - `AUTOCLOW_MAX_STEPS`, `AUTOCLOW_SHELL_TIMEOUT`: reliability limits (max LLM turns per task; shell timeout in ms).
218
- - `AUTOCLOW_TASK_TIMEOUT_MS`: whole-task wall-clock timeout in ms.
219
- - `AUTOCLOW_SHELL`: force the shell for shell commands (`bash`, `powershell`, `cmd`, `sh`).
220
- - `AUTOCLOW_INCLUDE_USAGE`: set to `1`/`true` to request token usage from the API (opt-in).
221
- - `TAVILY_API_KEY`, `SMTP_HOST`/`SMTP_PORT`/`SMTP_USER`/`SMTP_PASS`, `FEISHU_WEBHOOK`/`FEISHU_KEYWORD`, `DINGTALK_WEBHOOK`/`DINGTALK_KEYWORD`, `WECOM_WEBHOOK`/`WECOM_KEYWORD`: tool credentials as an alternative to setup.
222
-
223
- ## Integrations
224
-
225
- ### Web Search (Tavily)
226
- AutoClaw can search the web if you provide a Tavily API Key during setup or in config.
227
- - **Usage**: "Search for the latest Node.js release notes."
228
-
229
- ### Email (SMTP)
230
- Configure SMTP settings to let the agent send emails.
231
- - **Usage**: "Send an email to user@example.com with the summary of the log file."
232
-
233
- ### Notifications (Feishu/DingTalk/WeCom)
234
- Configure webhooks to receive alerts or reports in your team chat apps.
235
- - **Usage**: "Notify the team on Feishu that the build has finished."
236
-
237
- ### Date & Time
238
- Built-in utility to provide the agent with the current system time, ensuring accurate handling of relative time requests.
239
- - **Usage**: "What's the date today?" or "Remind me to check the logs next Monday."
240
-
241
- ## Docker Support
242
-
243
- ### Build & Run
244
- The repository ships a multi-stage `Dockerfile` (node:22-alpine, browser downloads skipped to keep the image slim). The container runs headless one-shot tasks against the mounted directory:
245
- ```bash
246
- docker build -t autoclaw .
247
- docker run --rm -v "$PWD":/workspace -w /workspace -e OPENAI_API_KEY=sk-... autoclaw "Check disk usage and save a report" -y -n
248
- ```
249
- Note: browser-based tools (`read_website` / `take_screenshot`) are not functional in the default image since browsers are not bundled โ€” they return a friendly install hint instead.
250
-
251
- ### Chinese Font Issues in Screenshots
252
- When running AutoClaw inside a Docker container (especially Alpine or Debian Slim), screenshots of Chinese websites may display text as square boxes ("tofu") due to missing fonts. Emojis (e.g., ๐Ÿ”ฅ) may also appear as squares.
253
-
254
- **Solution:** Install CJK (Chinese/Japanese/Korean) and Emoji fonts in your container.
255
-
256
- **For Debian/Ubuntu:**
257
- ```bash
258
- apt-get update && apt-get install -y fonts-noto-cjk fonts-wqy-zenhei fonts-noto-color-emoji
259
- ```
260
-
261
- **For Alpine Linux:**
262
- ```bash
263
- apk add font-noto-cjk font-noto-emoji
264
- ```
265
-
266
- ## License
267
-
268
- MIT
269
-
270
- ## Contributing
271
-
272
- Contributions are welcome! Please feel free to submit a Pull Request.
273
-
274
- 1. Fork the Project
275
- 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
276
- 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
277
- 4. Push to the Branch (`git push origin feature/AmazingFeature`)
278
- 5. Open a Pull Request
279
-
280
- ---
281
- GitHub: [https://github.com/tsingliuwin/autoclaw](https://github.com/tsingliuwin/autoclaw)
282
-
283
- ## Star History
284
-
285
- <a href="https://www.star-history.com/?repos=tsingliuwin%2Fautoclaw&type=date&legend=top-left">
286
- <picture>
287
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=tsingliuwin/autoclaw&type=date&theme=dark&legend=top-left" />
288
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=tsingliuwin/autoclaw&type=date&legend=top-left" />
289
- <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=tsingliuwin/autoclaw&type=date&legend=top-left" />
290
- </picture>
291
- </a>
1
+ # AutoClaw ๐Ÿฆž
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/autoclaw.svg?style=flat-square)](https://www.npmjs.com/package/autoclaw)
4
+ [![NPM Downloads](https://img.shields.io/npm/dm/autoclaw.svg?style=flat-square)](https://www.npmjs.com/package/autoclaw)
5
+ [![GitHub](https://img.shields.io/badge/GitHub-Repository-blue?logo=github&style=flat-square)](https://github.com/tsingliuwin/autoclaw)
6
+ [![License](https://img.shields.io/npm/l/autoclaw.svg?style=flat-square)](https://github.com/tsingliuwin/autoclaw/blob/main/LICENSE)
7
+ [![Safety](https://img.shields.io/badge/Safety-Notice-yellow?style=flat-square)](./SAFETY.md)
8
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
9
+
10
+ **The Engineering-First Headless Agent Framework: Stable, Scalable Automation for the Post-Vision Era.**
11
+
12
+ English | [็ฎ€ไฝ“ไธญๆ–‡](./README.zh-CN.md)
13
+
14
+ ---
15
+
16
+ ๐Ÿ”— **GitHub Repository**: [https://github.com/tsingliuwin/autoclaw](https://github.com/tsingliuwin/autoclaw)
17
+
18
+ ---
19
+
20
+ AutoClaw is a high-stability, open-source automation framework specifically engineered for **headless systems**.
21
+
22
+ Unlike "screen-seeing" agents (such as OpenClaw) that rely on visual interpretation, AutoClaw is built on a foundation of precise command-driven execution. This makes it significantly more **stable**, **robust from an engineering perspective**, and **easier to scale** across complex environmentsโ€”whether it's a local server, a CI/CD pipeline, or thousands of containerized nodes.
23
+
24
+ ## Why AutoClaw?
25
+ - ๐Ÿณ **Docker Native**: Built to run safely inside containers. Minimal footprint (Node.js/Alpine friendly).
26
+ - ๐Ÿš€ **Better Engineering**: Operates via precise system APIs and shell commands rather than unstable visual recognition, ensuring deterministic outcomes.
27
+ - ๐Ÿ›ก๏ธ **Superior Stability**: Immune to issues like UI rendering, screen resolution, or network lag that plague vision-based agents.
28
+ - ๐Ÿ“ˆ **Massive Scalability**: Low resource consumption allows orchestrating thousands of instances (e.g., in K8s) for true automation swarms.
29
+ - ๐Ÿ”Œ **Swarm Ready**: Stateless design allows for easy orchestration via K8s, Docker Swarm, or simple shell loops.
30
+ - ๐Ÿงฉ **Extensible Integrations**: Built-in support for Web Search (Tavily), Email (SMTP), and Notification Webhooks (Feishu, DingTalk, WeCom).
31
+
32
+ ## Features
33
+
34
+ - ๐Ÿ“œ **Headless Execution**: No GUI required โ€” pure terminal efficiency. Core operation is shell + file I/O; the optional web tools run in headless Chromium.
35
+ - ๐Ÿค– **Non-Interactive Mode**: Intelligent flag handling (`-y`, `--no-interactive`) for zero-touch automation.
36
+ - ๐Ÿ“‚ **Universal Control**: From simple file I/O to complex system administration.
37
+ - ๐Ÿ–ฅ๏ธ **Background Processes**: start long-lived commands (dev servers, watchers) detached and poll their output without blocking the run.
38
+ - ๐Ÿ›ก๏ธ **Safety Rails**: destructive-command gate, credential-file guard, step cap, wall-clock timeout, and API retries โ€” designed for machines nobody is watching.
39
+ - ๐Ÿง  **Context Aware**: Provides accurate OS, system and time context so relative dates ("today", "next Monday") are handled correctly.
40
+ - ๐ŸŒ **Web Search**: Integrated with Tavily for real-time information retrieval.
41
+ - ๐ŸŒ **Web Reading & Screenshots**: Extract article content and capture page screenshots (requires `npx playwright install chromium`).
42
+ - ๐ŸŽจ **Image Generation**: DALL-E compatible image generation via any OpenAI-compatible images API.
43
+ - ๐Ÿ•’ **Time Accuracy**: Built-in tool to get precise system date and time for correct temporal context.
44
+ - ๐Ÿ“ง **Communication**: Send emails and push notifications to chat groups automatically.
45
+
46
+ ## Tech Stack
47
+ - **Runtime**: Node.js
48
+ - **Language**: TypeScript
49
+ - **Framework**: Commander.js
50
+ - **UI**: Inquirer (interactivity), Chalk (styling), Ora (spinners)
51
+ - **AI**: OpenAI SDK (any OpenAI-compatible endpoint: DeepSeek, Kimi, Qwen, GLM, Ollama, โ€ฆ)
52
+ - **Web tools**: Playwright (headless Chromium for `read_website` / `take_screenshot`)
53
+
54
+ ## Installation
55
+
56
+ ### User Installation
57
+ Install globally via npm:
58
+ ```bash
59
+ npm install -g autoclaw
60
+ ```
61
+
62
+ ### Development Installation
63
+ 1. Clone the repository:
64
+ ```bash
65
+ git clone https://github.com/tsingliuwin/autoclaw.git
66
+ cd autoclaw
67
+ ```
68
+ 2. Install dependencies:
69
+ ```bash
70
+ npm install
71
+ ```
72
+ 3. Build the project:
73
+ ```bash
74
+ npm run build
75
+ ```
76
+ 4. Link globally (optional):
77
+ ```bash
78
+ npm link
79
+ ```
80
+
81
+ ## Quick Start
82
+
83
+ 1. **Setup**: Run the interactive setup wizard to configure your API keys and integrations. The wizard runs a live connection test (failures map to the likely wrong field: 401 = key, 404 = base URL, 400 = model name) and can list the provider's models for you to pick from.
84
+ ```bash
85
+ autoclaw setup
86
+ ```
87
+ 2. **Run**: Start the agent in interactive mode.
88
+ ```bash
89
+ autoclaw
90
+ ```
91
+
92
+ ## Usage
93
+
94
+ ### Interactive Mode
95
+ Simply run `autoclaw` to enter the chat loop.
96
+ ```bash
97
+ autoclaw
98
+ > List all TypeScript files in the src folder.
99
+ ```
100
+ Interactive commands: `exit` / `quit` to leave, and `/view` to open the full output of the last tool result in a pager โ€” tool output longer than 20 lines is folded on screen and saved to `~/.autoclaw/output/`.
101
+
102
+ ### Headless Mode (One-Shot)
103
+ Run a single command and exit.
104
+ ```bash
105
+ autoclaw "Check disk usage and save the report to usage.txt" --no-interactive
106
+ ```
107
+ The exit code reports the outcome for orchestrators: `0` completed, `1` hard failure (e.g. API error), `2` step cap reached (task unfinished).
108
+
109
+ ### Machine-Readable Output (--json)
110
+ Add `--json` to print one JSON event per line on stdout (run_start, tool_call, tool_result, usage, run_end); human output moves to stderr, including anything tools print themselves.
111
+ ```bash
112
+ autoclaw "Deploy and report" -y -n --json
113
+ ```
114
+ Token usage is collected only when `AUTOCLOW_INCLUDE_USAGE=1` (or `true`) is set โ€” it is opt-in because not every OpenAI-compatible provider accepts `stream_options.include_usage`.
115
+
116
+ ### Batch Mode (Swarm Worker)
117
+ Feed a JSONL manifest of tasks; each task runs in a fresh, isolated agent (one task's context never leaks into another) and per-task results are written as JSONL:
118
+ ```bash
119
+ autoclaw batch tasks.jsonl -y # results -> tasks.results.jsonl
120
+ autoclaw batch tasks.jsonl -o out.jsonl --fail-fast
121
+ ```
122
+ Manifest lines are `{"id": "...", "task": "..."}` โ€” `id` is optional (defaults to `task-N`); blank lines and `#` comments are skipped. Optional per-task overrides: `maxSteps`, `model`, `provider`.
123
+
124
+ One failing task does not stop the batch (use `--fail-fast` for that). The process exits `0` when every task completed, `1` otherwise, so cron and K8s Jobs can detect bad batches. Task output stays human-readable on stdout โ€” the results file is the machine-readable contract, with `status`, `steps`, `message`, `error` and `usage` per task.
125
+
126
+ Long batches can stop and pick up where they left off, and can use local parallelism:
127
+ ```bash
128
+ autoclaw batch big.jsonl -y --resume # skip tasks already completed in the results file
129
+ autoclaw batch big.jsonl -y -c 4 # run up to 4 tasks in parallel
130
+ ```
131
+ Unattempted tasks are simply absent from the results file, so `--fail-fast` followed by `--resume` is a natural retry loop.
132
+
133
+ AutoClaw also keeps its own prompt lean: optional tools (web search, email, group notifications, image generation) only register once their credentials are configured, and in long loops older tool results in the model context are replaced by short excerpts.
134
+
135
+ ### Recipes
136
+
137
+ Daily ops sweep on Linux (crontab):
138
+ ```cron
139
+ 0 9 * * * autoclaw batch /opt/ops/daily.jsonl -y -n --resume >> /var/log/autoclaw.log 2>&1
140
+ ```
141
+
142
+ Scheduled sweep on Windows (Task Scheduler):
143
+ ```bash
144
+ schtasks /create /tn "AutoClaw Daily" /tr "autoclaw batch C:\ops\daily.jsonl -y -n" /sc daily /st 09:00
145
+ ```
146
+
147
+ Pipeline inside one manifest โ€” each task writes files the next task reads:
148
+ ```jsonl
149
+ {"id": "sweep", "task": "ๆฃ€ๆŸฅ็ฃ็›˜ไธŽๅ…ณ้”ฎๆœๅŠก็Šถๆ€,ๆŠฅๅ‘Šๅ†™ๅ…ฅ report/sweep.md"}
150
+ {"id": "notify", "task": "่ฏปๅ– report/sweep.md,็”จไธ‰ๅฅ่ฏๆ€ป็ป“ๅŽๆŽจ้€ๅˆฐ้ฃžไนฆ"}
151
+ ```
152
+
153
+ Diagnostics on a fresh machine or in CI:
154
+ ```bash
155
+ autoclaw doctor # exit 0 = ready; exit 1 = what's missing is printed
156
+ ```
157
+
158
+ ### Auto-Confirm (CI/CD)
159
+ Automatically approve all tool executions (dangerous, use with caution or in sandboxes).
160
+ ```bash
161
+ autoclaw "Refactor src/index.ts to use ES modules" -y
162
+ ```
163
+
164
+ ### CLI Options
165
+ - `-m, --model <model>`: Specify the LLM model (default: `gpt-5.6`).
166
+ - `-P, --provider <name>`: Use a provider preset (see [Providers](#providers)).
167
+ - `-n, --no-interactive`: Exit after processing the initial query (Headless mode).
168
+ - `-y, --yes`: Auto-confirm all tool executions (e.g., shell commands).
169
+ - `--allow-dangerous`: Let `-y` run clearly destructive commands (rm -rf, format, shutdown, ...) that the built-in safety gate would block.
170
+ - `--json`: Emit NDJSON events on stdout (for orchestrators; use with `-n`).
171
+
172
+ ### Diagnostics
173
+ `autoclaw doctor` checks everything headlessly and prints โœ“/โœ— per item: config files, resolved provider/baseUrl/model, API key, a live connection test, resolved shell, registered tools, and playwright browser status. Exit `0` = ready, `1` = a critical item failed (the failing item is printed). Ideal for CI or a fresh machine.
174
+
175
+ ### Sandbox
176
+ Command execution can be confined with `config.sandbox` / `AUTOCLOW_SANDBOX` (vocabulary borrowed from DeepSeek Harness):
177
+ - `danger-full-access` (default): commands run unconstrained.
178
+ - `workspace-write`: commands can write only inside the current working directory and `/tmp`.
179
+ - `read-only`: commands cannot write anywhere.
180
+
181
+ Backends: bubblewrap on Linux (`apt install bubblewrap`), `sandbox-exec` on macOS. **Windows has no backend yet** โ€” non-default modes fail closed (commands are refused with a clear error) instead of pretending to confine; run with `danger-full-access` there for now. Reads and network are not confined by this vocabulary.
182
+
183
+ ### Providers
184
+ AutoClaw works with any OpenAI-compatible endpoint. Built-in presets fill in the base URL and a default model for you:
185
+ ```bash
186
+ autoclaw -P deepseek "Check disk usage and save a report" -y -n
187
+ ```
188
+ Available presets: `openai`, `deepseek`, `moonshot` (Kimi), `dashscope` (Qwen), `zhipu` (GLM), `ark` (Volcano Ark), `siliconflow`, `openrouter`, `ollama` (local). You can still override the model with `-m` or config. When `OPENAI_API_KEY` is not set, the API key is read from the provider's own env var (e.g. `DEEPSEEK_API_KEY`, `MOONSHOT_API_KEY`, `DASHSCOPE_API_KEY`, `ZHIPU_API_KEY`, `ARK_API_KEY`, `SILICONFLOW_API_KEY`, `OPENROUTER_API_KEY`).
189
+
190
+ ## Configuration
191
+
192
+ AutoClaw uses a hierarchical configuration system.
193
+
194
+ **Priority Order (Highest to Lowest):**
195
+ 1. **CLI Arguments**: (e.g., `-m gpt-5.6`)
196
+ 2. **Environment Variables**: (`OPENAI_API_KEY`, `.env` file)
197
+ 3. **Project Config**: (`./.autoclaw/setting.json` in current directory)
198
+ 4. **Global Config**: (`~/.autoclaw/setting.json`)
199
+
200
+ ### Supported Configuration Keys (JSON)
201
+ - `provider`: Provider preset name (e.g. `deepseek`).
202
+ - `apiKey`: Your OpenAI API Key.
203
+ - `baseUrl`: Custom Base URL (e.g., for DeepSeek or LocalLLM).
204
+ - `model`: Default model to use.
205
+ - `maxSteps`: Max LLM turns per task before the agent stops (default: `25`).
206
+ - `shellTimeout`: Shell command timeout in milliseconds (default: `120000`).
207
+ - `taskTimeoutMs`: Whole-task wall-clock timeout in milliseconds (off by default; aborts in-flight API calls and stops with `timeout` status).
208
+ - `sandbox`: Confine shell commands (`read-only`, `workspace-write`, `danger-full-access`; default: `danger-full-access`).
209
+ - `shell`: Force a shell for `execute_shell_command` (`bash`, `powershell`, `cmd`, `sh`; default: auto-detect โ€” Git Bash > PowerShell > cmd on Windows).
210
+ - `tavilyApiKey`: API Key for Tavily Web Search.
211
+ - `smtpHost`, `smtpPort`, `smtpUser`, `smtpPass`, `smtpFrom`: SMTP Email settings.
212
+ - `feishuWebhook`, `dingtalkWebhook`, `wecomWebhook`: Notification webhooks.
213
+
214
+ ### Project-Level Config Example
215
+ Create a file at `.autoclaw/setting.json`:
216
+ ```json
217
+ {
218
+ "model": "gpt-5.6",
219
+ "baseUrl": "https://api.deepseek.com/v1"
220
+ }
221
+ ```
222
+
223
+ > **โš ๏ธ Security Warning**: If you store your `apiKey` or secrets in `.autoclaw/setting.json`, make sure to add `.autoclaw/` to your `.gitignore` file to prevent leaking secrets!
224
+
225
+ ### Environment Variables
226
+ - `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_MODEL`: main LLM settings.
227
+ - `AUTOCLOW_PROVIDER`: provider preset used when `-P` is not passed.
228
+ - `AUTOCLOW_MAX_STEPS`, `AUTOCLOW_SHELL_TIMEOUT`: reliability limits (max LLM turns per task; shell timeout in ms).
229
+ - `AUTOCLOW_TASK_TIMEOUT_MS`: whole-task wall-clock timeout in ms.
230
+ - `AUTOCLOW_SANDBOX`: confine shell commands (`read-only`, `workspace-write`, `danger-full-access`).
231
+ - `AUTOCLOW_SHELL`: force the shell for shell commands (`bash`, `powershell`, `cmd`, `sh`).
232
+ - `AUTOCLOW_INCLUDE_USAGE`: set to `1`/`true` to request token usage from the API (opt-in).
233
+ - `TAVILY_API_KEY`, `SMTP_HOST`/`SMTP_PORT`/`SMTP_USER`/`SMTP_PASS`, `FEISHU_WEBHOOK`/`FEISHU_KEYWORD`, `DINGTALK_WEBHOOK`/`DINGTALK_KEYWORD`, `WECOM_WEBHOOK`/`WECOM_KEYWORD`: tool credentials as an alternative to setup.
234
+
235
+ ## Integrations
236
+
237
+ ### Web Search (Tavily)
238
+ AutoClaw can search the web if you provide a Tavily API Key during setup or in config.
239
+ - **Usage**: "Search for the latest Node.js release notes."
240
+
241
+ ### Email (SMTP)
242
+ Configure SMTP settings to let the agent send emails.
243
+ - **Usage**: "Send an email to user@example.com with the summary of the log file."
244
+
245
+ ### Notifications (Feishu/DingTalk/WeCom)
246
+ Configure webhooks to receive alerts or reports in your team chat apps.
247
+ - **Usage**: "Notify the team on Feishu that the build has finished."
248
+
249
+ ### Date & Time
250
+ Built-in utility to provide the agent with the current system time, ensuring accurate handling of relative time requests.
251
+ - **Usage**: "What's the date today?" or "Remind me to check the logs next Monday."
252
+
253
+ ## Docker Support
254
+
255
+ ### Build & Run
256
+ The repository ships a multi-stage `Dockerfile` (node:22-alpine, browser downloads skipped to keep the image slim). The container runs headless one-shot tasks against the mounted directory:
257
+ ```bash
258
+ docker build -t autoclaw .
259
+ docker run --rm -v "$PWD":/workspace -w /workspace -e OPENAI_API_KEY=sk-... autoclaw "Check disk usage and save a report" -y -n
260
+ ```
261
+ Note: browser-based tools (`read_website` / `take_screenshot`) are not functional in the default image since browsers are not bundled โ€” they return a friendly install hint instead.
262
+
263
+ ### Chinese Font Issues in Screenshots
264
+ When running AutoClaw inside a Docker container (especially Alpine or Debian Slim), screenshots of Chinese websites may display text as square boxes ("tofu") due to missing fonts. Emojis (e.g., ๐Ÿ”ฅ) may also appear as squares.
265
+
266
+ **Solution:** Install CJK (Chinese/Japanese/Korean) and Emoji fonts in your container.
267
+
268
+ **For Debian/Ubuntu:**
269
+ ```bash
270
+ apt-get update && apt-get install -y fonts-noto-cjk fonts-wqy-zenhei fonts-noto-color-emoji
271
+ ```
272
+
273
+ **For Alpine Linux:**
274
+ ```bash
275
+ apk add font-noto-cjk font-noto-emoji
276
+ ```
277
+
278
+ ## License
279
+
280
+ MIT
281
+
282
+ ## Contributing
283
+
284
+ Contributions are welcome! Please feel free to submit a Pull Request.
285
+
286
+ 1. Fork the Project
287
+ 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
288
+ 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
289
+ 4. Push to the Branch (`git push origin feature/AmazingFeature`)
290
+ 5. Open a Pull Request
291
+
292
+ ---
293
+ GitHub: [https://github.com/tsingliuwin/autoclaw](https://github.com/tsingliuwin/autoclaw)
294
+
295
+ ## Star History
296
+
297
+ <a href="https://www.star-history.com/?repos=tsingliuwin%2Fautoclaw&type=date&legend=top-left">
298
+ <picture>
299
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=tsingliuwin/autoclaw&type=date&theme=dark&legend=top-left" />
300
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=tsingliuwin/autoclaw&type=date&legend=top-left" />
301
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=tsingliuwin/autoclaw&type=date&legend=top-left" />
302
+ </picture>
303
+ </a>
package/README.zh-CN.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![NPM Downloads](https://img.shields.io/npm/dm/autoclaw.svg?style=flat-square)](https://www.npmjs.com/package/autoclaw)
5
5
  [![GitHub](https://img.shields.io/badge/GitHub-Repository-blue?logo=github&style=flat-square)](https://github.com/tsingliuwin/autoclaw)
6
6
  [![License](https://img.shields.io/npm/l/autoclaw.svg?style=flat-square)](https://github.com/tsingliuwin/autoclaw/blob/main/LICENSE)
7
+ [![Safety](https://img.shields.io/badge/Safety-Notice-yellow?style=flat-square)](./SAFETY.md)
7
8
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
8
9
 
9
10
  **็จณๅฎšใ€้ซ˜ๅทฅ็จ‹ๅŒ–ใ€ๆ˜“่ง„ๆจกๅŒ–๏ผšไธ“ไธบๆ— ็•Œ้ข็ณป็ปŸ่ฎพ่ฎก็š„้ซ˜ๆ•ˆ่‡ชๅŠจๅŒ– Agent ๆก†ๆžถใ€‚**
@@ -34,7 +35,8 @@ AutoClaw ๆ˜ฏไธ€ๆฌพ้’ˆๅฏน **โ€œๆ— ็•Œ้ข็ณป็ปŸโ€ (Headless Systems)** ็š„้ซ˜็จณ
34
35
  - ๐Ÿ“œ **ๆ— ๅคดๆ‰ง่กŒ (Headless Execution)**: ๆ— ้œ€ๅ›พๅฝข็•Œ้ข๏ผŒ็บฏ็ปˆ็ซฏ่ฟ่กŒใ€‚ๆ ธๅฟƒๆต็จ‹ไป…ไพ่ต– Shell ไธŽๆ–‡ไปถๆ“ไฝœ๏ผ›ๅฏ้€‰็š„็ฝ‘้กตๅทฅๅ…ทๅœจๆ— ๅคด Chromium ไธญ่ฟ่กŒใ€‚
35
36
  - ๐Ÿค– **้žไบคไบ’ๆจกๅผ**: ๆ”ฏๆŒ่‡ชๅŠจๅŒ–ๆ ‡ๅฟ—๏ผˆ`-y`, `--no-interactive`๏ผ‰๏ผŒๅฎŒ็พŽ้€‚้…้›ถๅนฒ้ข„็š„่‡ชๅŠจๅŒ–ๆต็จ‹ใ€‚
36
37
  - ๐Ÿ“‚ **ๅ…จๆ–นไฝๆŽงๅˆถ (Universal Control)**: ไปŽๅŸบ็ก€็š„ๆ–‡ไปถ I/O ๅˆฐๅคๆ‚็š„็ณป็ปŸ็ฎก็†ใ€‚
37
- - ๐Ÿ›ก๏ธ **้˜ฒๅคฑๆŽงไฟๆŠค**: ๅ•ไปปๅŠกๆญฅๆ•ฐไธŠ้™ใ€API ๆŒ‡ๆ•ฐ้€€้ฟ้‡่ฏ•ใ€Shell ๅ‘ฝไปค่ถ…ๆ—ถใ€ๅทฅๅ…ท่พ“ๅ‡บๆˆชๆ–ญ๏ผŒไฟ่ฏๆจกๅž‹ไธŠไธ‹ๆ–‡ไธ่ขซๆ’‘็ˆ†ใ€‚
38
+ - ๐Ÿ–ฅ๏ธ **ๅŽๅฐ่ฟ›็จ‹**: ๅŽๅฐๅฏๅŠจ้•ฟๆœŸๅ‘ฝไปค(ๅผ€ๅ‘ๆœๅŠกๅ™จใ€watcher)ๅนถ่ฝฎ่ฏข่พ“ๅ‡บ,ไธ้˜ปๅกžไปปๅŠก่ฟ่กŒใ€‚
39
+ - ๐Ÿ›ก๏ธ **ๅฎ‰ๅ…จๆŠคๆ **: ็ ดๅๆ€งๅ‘ฝไปค้—ธใ€ๅ‡ญๆฎๆ–‡ไปถ้˜ฒ็บฟใ€ๆญฅๆ•ฐไธŠ้™ใ€ๅข™้’Ÿ่ถ…ๆ—ถไธŽ API ้‡่ฏ•โ€”โ€”ไธบๆฒกๆœ‰ไบบ็œ‹็€็š„ๆœบๅ™จ่€Œ่ฎพ่ฎกใ€‚
38
40
  - ๐Ÿง  **ไธŠไธ‹ๆ–‡ๆ„Ÿ็Ÿฅ (Context Aware)**: ๆไพ›็ฒพ็กฎ็š„ๆ“ไฝœ็ณป็ปŸไธŽๆ—ถ้—ดไธŠไธ‹ๆ–‡๏ผŒๆญฃ็กฎๅค„็†"ไปŠๅคฉ"ใ€"ไธ‹ๅ‘จไธ€"็ญ‰็›ธๅฏนๆ—ถ้—ดใ€‚
39
41
  - ๐ŸŒ **็ฝ‘้กตๆœ็ดข**: ้›†ๆˆ Tavily๏ผŒๆ”ฏๆŒๅฎžๆ—ถไฟกๆฏๆฃ€็ดขใ€‚
40
42
  - ๐ŸŒ **็ฝ‘้กต้˜…่ฏปไธŽๆˆชๅ›พ**: ๆๅ–ๆ–‡็ซ ๆญฃๆ–‡ใ€ๆˆชๅ–้กต้ขๅ›พ็‰‡๏ผˆ้œ€ๅ…ˆๆ‰ง่กŒ `npx playwright install chromium`๏ผ‰ใ€‚
@@ -170,6 +172,14 @@ autoclaw "ๅฐ† src/index.ts ้‡ๆž„ไธบไฝฟ็”จ ES ๆจกๅ—" -y
170
172
 
171
173
  ### ่ฏŠๆ–ญ
172
174
  `autoclaw doctor` ๆ— ๅคดๅฎŒๆˆๅ…จ้ข่‡ชๆฃ€,้€้กนๆ‰“ๅฐ โœ“/โœ—:้…็ฝฎๆ–‡ไปถใ€่งฃๆžๅ‡บ็š„ provider/baseUrl/modelใ€API keyใ€็œŸๅฎž่ฟžๆŽฅๆต‹่ฏ•ใ€่งฃๆžๅ‡บ็š„ shellใ€ๅทฒๆณจๅ†Œๅทฅๅ…ทใ€playwright ๆต่งˆๅ™จ็Šถๆ€ใ€‚้€€ๅ‡บ `0` = ๅฐฑ็ปช,`1` = ๆœ‰ๅ…ณ้”ฎ้กนๅคฑ่ดฅ(ไผšๆ‰“ๅฐๆ˜ฏๅ“ช้กน)ใ€‚้€‚ๅˆ CI ๆˆ–ๆ–ฐๆœบๅ™จใ€‚
175
+
176
+ ### ๆฒ™็ฎฑ
177
+ ๅ‘ฝไปคๆ‰ง่กŒๅฏ้€š่ฟ‡ `config.sandbox` / `AUTOCLOW_SANDBOX` ๅŠ ไปฅ็บฆๆŸ(่ฏๆฑ‡ๅ€Ÿ้‰ด่‡ช DeepSeek Harness):
178
+ - `danger-full-access`(้ป˜่ฎค):ๅ‘ฝไปคไธๅ—็บฆๆŸๅœฐ่ฟ่กŒใ€‚
179
+ - `workspace-write`:ๅ‘ฝไปคๅช่ƒฝๅœจๅฝ“ๅ‰ๅทฅไฝœ็›ฎๅฝ•ๅ’Œ `/tmp` ๅ†…ๅ†™ๅ…ฅใ€‚
180
+ - `read-only`:ๅ‘ฝไปคๆ— ๆณ•ๅœจไปปไฝ•ไฝ็ฝฎๅ†™ๅ…ฅใ€‚
181
+
182
+ ๅŽ็ซฏ:Linux ไฝฟ็”จ bubblewrap(`apt install bubblewrap`),macOS ไฝฟ็”จ `sandbox-exec`ใ€‚**Windows ๆš‚ๆ— ๅŽ็ซฏ**โ€”โ€”้ž้ป˜่ฎคๆจกๅผไผš"ๅคฑ่ดฅๅ…ณ้—ญ"(็›ดๆŽฅๆ‹’็ปๅ‘ฝไปคๅนถ็ป™ๅ‡บๆ˜Ž็กฎ้”™่ฏฏ),่€Œไธๆ˜ฏๅ‡่ฃ…็บฆๆŸ;ๅœจ Windows ไธŠ่ฏทๆš‚็”จ `danger-full-access`ใ€‚ๆญค่ฏๆฑ‡ไธ็บฆๆŸ่ฏปๅ–ไธŽ็ฝ‘็ปœใ€‚
173
183
  - `--json`: ๅœจ stdout ่พ“ๅ‡บ NDJSON ไบ‹ไปถๆต (ไพ›็ผ–ๆŽ’ๅ™จไฝฟ็”จ,้…ๅˆ `-n`)ใ€‚
174
184
 
175
185
  ### Provider ้ข„่ฎพ
@@ -197,6 +207,7 @@ AutoClaw ไฝฟ็”จๅฑ‚็บง้…็ฝฎ็ณป็ปŸใ€‚
197
207
  - `maxSteps`: ๅ•ไปปๅŠกๆœ€ๅคง LLM ่ฝฎๆ•ฐ๏ผŒ่ถ…ๅ‡บๅŽ่‡ชๅŠจๅœๆญข (้ป˜่ฎค: `25`)ใ€‚
198
208
  - `shellTimeout`: Shell ๅ‘ฝไปค่ถ…ๆ—ถๆ—ถ้—ด๏ผˆๆฏซ็ง’๏ผ‰(้ป˜่ฎค: `120000`)ใ€‚
199
209
  - `taskTimeoutMs`: ๅ•ไปปๅŠกๆ•ดไฝ“ๅข™้’Ÿ่ถ…ๆ—ถ๏ผˆๆฏซ็ง’๏ผŒ้ป˜่ฎคๅ…ณ้—ญ๏ผ›ไผšไธญๆ–ญ่ฟ›่กŒไธญ็š„ API ่ฐƒ็”จๅนถไปฅ `timeout` ็Šถๆ€ๅœๆญข๏ผ‰ใ€‚
210
+ - `sandbox`: ็บฆๆŸ shell ๅ‘ฝไปค๏ผˆ`read-only`ใ€`workspace-write`ใ€`danger-full-access`๏ผ›้ป˜่ฎค `danger-full-access`๏ผ‰ใ€‚
200
211
  - `shell`: ๅผบๅˆถ `execute_shell_command` ไฝฟ็”จ็š„ shell (`bash`ใ€`powershell`ใ€`cmd`ใ€`sh`๏ผ›้ป˜่ฎค่‡ชๅŠจๆฃ€ๆต‹โ€”โ€”Windows ไธŠไผ˜ๅ…ˆ Git Bash > PowerShell > cmd)ใ€‚
201
212
  - `tavilyApiKey`: Tavily ็ฝ‘้กตๆœ็ดข็š„ API ๅฏ†้’ฅใ€‚
202
213
  - `smtpHost`, `smtpPort`, `smtpUser`, `smtpPass`, `smtpFrom`: SMTP ้‚ฎไปถ่ฎพ็ฝฎใ€‚
@@ -218,6 +229,7 @@ AutoClaw ไฝฟ็”จๅฑ‚็บง้…็ฝฎ็ณป็ปŸใ€‚
218
229
  - `AUTOCLOW_PROVIDER`: ๆœชไผ  `-P` ๆ—ถไฝฟ็”จ็š„ provider ้ข„่ฎพใ€‚
219
230
  - `AUTOCLOW_MAX_STEPS`, `AUTOCLOW_SHELL_TIMEOUT`: ็จณๅฎšๆ€ง้™ๅˆถ๏ผˆๅ•ไปปๅŠกๆœ€ๅคง่ฝฎๆ•ฐ๏ผ›Shell ่ถ…ๆ—ถๆฏซ็ง’ๆ•ฐ๏ผ‰ใ€‚
220
231
  - `AUTOCLOW_TASK_TIMEOUT_MS`: ๅ•ไปปๅŠกๆ•ดไฝ“ๅข™้’Ÿ่ถ…ๆ—ถ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚
232
+ - `AUTOCLOW_SANDBOX`: ็บฆๆŸ shell ๅ‘ฝไปค๏ผˆ`read-only`ใ€`workspace-write`ใ€`danger-full-access`๏ผ‰ใ€‚
221
233
  - `AUTOCLOW_SHELL`: ๅผบๅˆถ shell ๅ‘ฝไปคไฝฟ็”จ็š„ shell (`bash`ใ€`powershell`ใ€`cmd`ใ€`sh`)ใ€‚
222
234
  - `AUTOCLOW_INCLUDE_USAGE`: ่ฎพไธบ `1`/`true` ๆ—ถๅ‘ API ่ฏทๆฑ‚ token ็”จ้‡๏ผˆๅฏ้€‰ๅผ€ๅฏ๏ผ‰ใ€‚
223
235
  - `TAVILY_API_KEY`, `SMTP_HOST`/`SMTP_PORT`/`SMTP_USER`/`SMTP_PASS`, `FEISHU_WEBHOOK`/`FEISHU_KEYWORD`, `DINGTALK_WEBHOOK`/`DINGTALK_KEYWORD`, `WECOM_WEBHOOK`/`WECOM_KEYWORD`: ๅทฅๅ…ทๅ‡ญๆฎ๏ผŒๅฏไฝœไธบ setup ็š„ๆ›ฟไปฃๆ–นๅผใ€‚
package/dist/agent.js CHANGED
@@ -11,6 +11,21 @@ import { truncateOutput } from './truncate.js';
11
11
  import { buildShellInfo, resolveShellType } from './shell.js';
12
12
  const DEFAULT_MAX_STEPS = 25;
13
13
  const TOOL_RESULT_TRIM_MARKER = 'older tool output trimmed';
14
+ // Canonical JSON: sorted object keys, so equivalent arguments from
15
+ // different model turns map to the same repeat signature.
16
+ function canonicalArgs(args) {
17
+ try {
18
+ return JSON.stringify(args, (_key, value) => {
19
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
20
+ return Object.keys(value).sort().reduce((acc, k) => { acc[k] = value[k]; return acc; }, {});
21
+ }
22
+ return value;
23
+ });
24
+ }
25
+ catch {
26
+ return String(args);
27
+ }
28
+ }
14
29
  export class Agent {
15
30
  client;
16
31
  messages;
@@ -51,6 +66,7 @@ System Information:
51
66
  has('web_search') ? '- Web: web_search โ€” real-time information lookup' : null,
52
67
  has('read_website') ? '- Web: read_website โ€” extract article content from a URL' : null,
53
68
  has('take_screenshot') ? '- Web: take_screenshot โ€” capture page visuals' : null,
69
+ has('start_background_process') ? '- Processes: start_background_process / check_background_process / stop_background_process โ€” run long-lived commands (servers, watchers) in the background and poll their output' : null,
54
70
  has('send_email') ? '- Communication: send_email โ€” SMTP email delivery' : null,
55
71
  has('send_notification') ? '- Communication: send_notification โ€” push to Feishu/DingTalk/WeCom' : null,
56
72
  has('generate_image') ? '- Creation: generate_image โ€” AI image generation (DALL-E compatible)' : null,
@@ -116,6 +132,8 @@ RULES OF ENGAGEMENT:
116
132
  let status = 'completed';
117
133
  let errorMessage;
118
134
  let lastContent = null;
135
+ let lastToolSignature = null;
136
+ let consecutiveRepeats = 0;
119
137
  const totalUsage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
120
138
  let sawUsage = false;
121
139
  this.emitEvent({ event: 'run_start', model: this.model, task: userInput });
@@ -334,6 +352,21 @@ RULES OF ENGAGEMENT:
334
352
  const truncation = truncateOutput(toolResult);
335
353
  const boundedResult = truncation.content;
336
354
  const resultLines = boundedResult.split('\n');
355
+ // Loop hygiene (idea from dsh repeat-tool-reminder): identical
356
+ // consecutive calls get an escalating reminder so the model changes
357
+ // approach early instead of burning turns until the step cap.
358
+ const signature = `${functionName}:${canonicalArgs(functionArgs)}`;
359
+ if (signature === lastToolSignature)
360
+ consecutiveRepeats++;
361
+ else {
362
+ consecutiveRepeats = 1;
363
+ lastToolSignature = signature;
364
+ }
365
+ const repeatSuffix = consecutiveRepeats < 3
366
+ ? ''
367
+ : consecutiveRepeats < 5
368
+ ? `\n[AutoClaw] Note: this is the ${consecutiveRepeats}th identical ${functionName} call in a row. If it keeps returning the same result, change approach instead of repeating it.`
369
+ : `\n[AutoClaw] You have now made the identical ${functionName} call ${consecutiveRepeats} times in a row (arguments: ${JSON.stringify(functionArgs).slice(0, 120)}). This exact call keeps returning the same result. Stop retrying it: change approach, fix the underlying problem, or finish the task with what you already have.`;
337
370
  let outputFile = null;
338
371
  if (resultLines.length > MAX_PREVIEW_LINES || truncation.truncated) {
339
372
  outputFile = await this.saveOutput(functionName, toolResult);
@@ -366,7 +399,7 @@ RULES OF ENGAGEMENT:
366
399
  this.messages.push({
367
400
  role: "tool",
368
401
  tool_call_id: toolCall.id,
369
- content: boundedResult
402
+ content: boundedResult + repeatSuffix
370
403
  });
371
404
  }
372
405
  }
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ dotenv.config({ path: GLOBAL_ENV_FILE });
46
46
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
47
47
  // In dist/index.js, package.json is usually up one level in the root
48
48
  const pkgPath = path.join(__dirname, '..', 'package.json');
49
- let version = '1.3.4';
49
+ let version = '1.3.5';
50
50
  try {
51
51
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
52
52
  version = pkg.version;
@@ -0,0 +1,94 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { shellInvocation } from './shell.js';
4
+ export function resolveSandboxMode(config) {
5
+ const requested = String(config?.sandbox || process.env.AUTOCLOW_SANDBOX || 'danger-full-access').toLowerCase();
6
+ if (requested === 'read-only' || requested === 'workspace-write' || requested === 'danger-full-access') {
7
+ return requested;
8
+ }
9
+ return 'danger-full-access';
10
+ }
11
+ export function sandboxBackend(platform = process.platform) {
12
+ if (platform === 'linux') {
13
+ const available = hasExecutable('bwrap');
14
+ return {
15
+ backend: 'bwrap',
16
+ available,
17
+ detail: available
18
+ ? 'bubblewrap found'
19
+ : 'bubblewrap (bwrap) not found โ€” install it (e.g. apt install bubblewrap) or set sandbox to danger-full-access'
20
+ };
21
+ }
22
+ if (platform === 'darwin') {
23
+ const available = fs.existsSync('/usr/bin/sandbox-exec');
24
+ return {
25
+ backend: 'sandbox-exec',
26
+ available,
27
+ detail: available ? 'sandbox-exec found' : 'sandbox-exec missing (unexpected on macOS)'
28
+ };
29
+ }
30
+ return {
31
+ backend: 'none',
32
+ available: false,
33
+ detail: 'no sandbox backend on this platform yet (Windows restricted-token rung is planned). Set sandbox to danger-full-access to run unrestricted.'
34
+ };
35
+ }
36
+ function hasExecutable(name) {
37
+ for (const dir of (process.env.PATH || '').split(path.delimiter)) {
38
+ try {
39
+ if (fs.existsSync(path.join(dir, name)))
40
+ return true;
41
+ }
42
+ catch {
43
+ // skip unreadable PATH entries
44
+ }
45
+ }
46
+ return false;
47
+ }
48
+ // Pure shape builder: wraps the platform shell invocation so the command can
49
+ // only write where the mode allows. danger-full-access returns the command
50
+ // unchanged; read-only/workspace-write require the platform backend, which
51
+ // the caller checks with sandboxBackend() first (fail-closed otherwise).
52
+ export function sandboxedInvocation(command, mode, workspace, platform = process.platform) {
53
+ const base = shellInvocation(command);
54
+ if (mode === 'danger-full-access')
55
+ return base;
56
+ if (platform === 'linux') {
57
+ if (mode === 'read-only') {
58
+ return {
59
+ file: 'bwrap',
60
+ args: ['--ro-bind', '/', '/', '--dev-bind', '/dev', '/dev', '--proc', '/proc', '--', base.file, ...base.args]
61
+ };
62
+ }
63
+ return {
64
+ file: 'bwrap',
65
+ args: [
66
+ '--ro-bind', '/', '/',
67
+ '--bind', workspace, workspace,
68
+ '--dev-bind', '/dev', '/dev',
69
+ '--proc', '/proc',
70
+ '--tmpfs', '/tmp',
71
+ '--', base.file, ...base.args
72
+ ]
73
+ };
74
+ }
75
+ if (platform === 'darwin') {
76
+ const ws = workspace.replace(/"/g, '\\"');
77
+ if (mode === 'read-only') {
78
+ return {
79
+ file: '/usr/bin/sandbox-exec',
80
+ args: ['-p', '(version 1)(allow default)(deny file-write*)', base.file, ...base.args]
81
+ };
82
+ }
83
+ return {
84
+ file: '/usr/bin/sandbox-exec',
85
+ args: [
86
+ '-p',
87
+ `(version 1)(allow default)(deny file-write*)(allow file-write* (subpath "${ws}") (subpath "/private/tmp") (regex #"^/private/var/folders/"))`,
88
+ base.file,
89
+ ...base.args
90
+ ]
91
+ };
92
+ }
93
+ return { blocked: true, detail: sandboxBackend(platform).detail };
94
+ }
package/dist/shell.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { spawn, execSync } from 'child_process';
2
2
  import * as fs from 'fs';
3
+ import * as path from 'path';
3
4
  const GIT_BASH_CANDIDATES = [
4
5
  'C:\\Program Files\\Git\\bin\\bash.exe',
5
6
  'C:\\Program Files\\Git\\usr\\bin\\bash.exe',
@@ -7,6 +8,7 @@ const GIT_BASH_CANDIDATES = [
7
8
  ];
8
9
  let cachedType = null;
9
10
  let cachedBashPath;
11
+ let cachedBashEnv = null;
10
12
  function findBashPath() {
11
13
  if (process.platform === 'win32') {
12
14
  for (const candidate of GIT_BASH_CANDIDATES) {
@@ -31,6 +33,26 @@ export function getBashPath() {
31
33
  cachedBashPath = findBashPath();
32
34
  return cachedBashPath;
33
35
  }
36
+ // A login shell (-l) would source the profile to put Git's /usr/bin on
37
+ // PATH, but that costs ~450ms per command. Prepending the directories to
38
+ // the child PATH achieves the same tool resolution at spawn speed.
39
+ export function spawnEnv() {
40
+ if (process.platform !== 'win32')
41
+ return process.env;
42
+ if (cachedBashEnv)
43
+ return cachedBashEnv;
44
+ const env = { ...process.env };
45
+ const bp = getBashPath();
46
+ if (bp) {
47
+ const usrBin = path.dirname(bp);
48
+ const gitRoot = path.resolve(usrBin, '..', '..');
49
+ const extra = [usrBin, path.join(gitRoot, 'mingw64', 'bin'), path.join(gitRoot, 'cmd')]
50
+ .filter(p => fs.existsSync(p));
51
+ env.PATH = `${extra.join(path.delimiter)}${path.delimiter}${env.PATH ?? ''}`;
52
+ }
53
+ cachedBashEnv = env;
54
+ return env;
55
+ }
34
56
  export function resolveShellType(config) {
35
57
  const requested = String(config?.shell || process.env.AUTOCLOW_SHELL || 'auto').toLowerCase();
36
58
  if (requested === 'bash' || requested === 'powershell' || requested === 'cmd' || requested === 'sh') {
@@ -57,28 +79,42 @@ export function smartDecode(buf) {
57
79
  }
58
80
  }
59
81
  }
60
- export function execShellCommand(command, opts) {
82
+ export function shellInvocation(command) {
61
83
  const type = resolveShellType();
62
- let file;
63
- let args;
64
84
  if (type === 'bash') {
65
- // -l sources the profile so Git Bash's /usr/bin lands on PATH and Unix
66
- // tools (ls, grep, ...) actually resolve.
67
- file = getBashPath();
68
- args = ['-l', '-c', command];
85
+ // --noprofile/--norc: profile loading costs ~450ms per command; PATH is
86
+ // handled by spawnEnv() instead.
87
+ return { file: getBashPath(), args: ['--noprofile', '--norc', '-c', command] };
69
88
  }
70
- else if (type === 'powershell') {
71
- file = 'powershell.exe';
72
- args = ['-NoProfile', '-Command', command];
89
+ if (type === 'powershell') {
90
+ return { file: 'powershell.exe', args: ['-NoProfile', '-Command', command] };
73
91
  }
74
- else if (type === 'cmd') {
75
- file = process.env.ComSpec || 'cmd.exe';
76
- args = ['/d', '/s', '/c', command];
92
+ if (type === 'cmd') {
93
+ return { file: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', command] };
77
94
  }
78
- else {
79
- file = '/bin/sh';
80
- args = ['-c', command];
95
+ return { file: '/bin/sh', args: ['-c', command] };
96
+ }
97
+ export function killProcessTree(pid) {
98
+ if (process.platform !== 'win32') {
99
+ // The negative pid targets the detached process group.
100
+ try {
101
+ process.kill(-pid, 'SIGKILL');
102
+ }
103
+ catch {
104
+ try {
105
+ process.kill(pid, 'SIGKILL');
106
+ }
107
+ catch {
108
+ // already gone
109
+ }
110
+ }
111
+ return;
81
112
  }
113
+ spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
114
+ }
115
+ export function execShellCommand(command, opts) {
116
+ const type = resolveShellType();
117
+ const { file, args } = opts.sandboxed ?? shellInvocation(command);
82
118
  return new Promise(resolve => {
83
119
  const stdout = [];
84
120
  const stderr = [];
@@ -87,7 +123,7 @@ export function execShellCommand(command, opts) {
87
123
  let truncated = false;
88
124
  let settled = false;
89
125
  const posix = process.platform !== 'win32';
90
- const child = spawn(file, args, { windowsHide: true, detached: posix });
126
+ const child = spawn(file, args, { windowsHide: true, detached: posix, env: spawnEnv() });
91
127
  const finish = () => {
92
128
  if (settled)
93
129
  return;
@@ -101,21 +137,8 @@ export function execShellCommand(command, opts) {
101
137
  });
102
138
  };
103
139
  const killTree = () => {
104
- if (!child.pid)
105
- return;
106
- if (posix) {
107
- try {
108
- process.kill(-child.pid, 'SIGKILL');
109
- }
110
- catch {
111
- child.kill('SIGKILL');
112
- }
113
- }
114
- else {
115
- // Terminating the shell alone would orphan grandchildren, whose
116
- // inherited stdio pipes keep 'close' pending โ€” kill the whole tree.
117
- spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
118
- }
140
+ if (child.pid)
141
+ killProcessTree(child.pid);
119
142
  };
120
143
  const timer = setTimeout(() => {
121
144
  timedOut = true;
@@ -0,0 +1,165 @@
1
+ import { spawn } from 'child_process';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import { killProcessTree, shellInvocation, spawnEnv } from '../shell.js';
6
+ import { resolveSandboxMode, sandboxBackend, sandboxedInvocation } from '../sandbox.js';
7
+ import { matchDangerousPattern } from './core.js';
8
+ const handles = new Map();
9
+ const MAX_TRACKED = 20;
10
+ function newHandleId() {
11
+ return `bg-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e6).toString(36)}`;
12
+ }
13
+ function tailOf(logPath, bytes = 4000) {
14
+ try {
15
+ if (!fs.existsSync(logPath))
16
+ return '(no output yet)';
17
+ const stat = fs.statSync(logPath);
18
+ const start = Math.max(0, stat.size - bytes);
19
+ const fd = fs.openSync(logPath, 'r');
20
+ try {
21
+ const buf = Buffer.alloc(stat.size - start);
22
+ fs.readSync(fd, buf, 0, buf.length, start);
23
+ return (start > 0 ? 'โ€ฆ(earlier output omitted)\n' : '') + buf.toString('utf-8');
24
+ }
25
+ finally {
26
+ fs.closeSync(fd);
27
+ }
28
+ }
29
+ catch (err) {
30
+ return `(could not read log: ${err.message})`;
31
+ }
32
+ }
33
+ export const StartBackgroundProcessTool = {
34
+ name: "Background Process Starter",
35
+ definition: {
36
+ type: "function",
37
+ function: {
38
+ name: "start_background_process",
39
+ description: "Start a long-lived command in the background without blocking: dev servers, watchers, long builds. Output is written to a log file you can read later.",
40
+ parameters: {
41
+ type: "object",
42
+ properties: {
43
+ command: { type: "string", description: "The command to run in the background." },
44
+ rationale: { type: "string", description: "Explain why this command should run in the background." }
45
+ },
46
+ required: ["command", "rationale"]
47
+ }
48
+ }
49
+ },
50
+ handler: async (args, config) => {
51
+ const command = String(args.command ?? '').trim();
52
+ if (!command)
53
+ return 'Error: "command" is required.';
54
+ // Same safety gate as the foreground shell tool.
55
+ const gateLabel = matchDangerousPattern(command);
56
+ if (gateLabel) {
57
+ return `Error: command blocked by AutoClaw safety policy (matched: ${gateLabel}). It was NOT started. If this task genuinely requires it, restart AutoClaw with --allow-dangerous.`;
58
+ }
59
+ // Same sandbox policy as the foreground shell tool.
60
+ const sandboxMode = resolveSandboxMode(config);
61
+ let target = shellInvocation(command);
62
+ if (sandboxMode !== 'danger-full-access') {
63
+ const backend = sandboxBackend();
64
+ if (!backend.available) {
65
+ return `Error: sandbox mode "${sandboxMode}" is configured but no sandbox backend is available: ${backend.detail}`;
66
+ }
67
+ const inv = sandboxedInvocation(command, sandboxMode, process.cwd());
68
+ if ('blocked' in inv) {
69
+ return `Error: sandbox mode "${sandboxMode}" cannot wrap this command: ${inv.detail}`;
70
+ }
71
+ target = inv;
72
+ }
73
+ for (const [id, h] of handles) {
74
+ if (h.exited && Date.now() - h.startedAt > 3600_000)
75
+ handles.delete(id);
76
+ }
77
+ if (handles.size >= MAX_TRACKED) {
78
+ return `Error: too many tracked background processes (${MAX_TRACKED}). Stop finished ones first or wait for them to exit.`;
79
+ }
80
+ const id = newHandleId();
81
+ const logDir = path.join(os.homedir(), '.autoclaw', 'output');
82
+ fs.mkdirSync(logDir, { recursive: true });
83
+ const logPath = path.join(logDir, `bg-${id}.log`);
84
+ const { file: logFile, args: spawnArgs } = target;
85
+ const out = fs.openSync(logPath, 'a');
86
+ let child;
87
+ try {
88
+ child = spawn(logFile, spawnArgs, {
89
+ windowsHide: true,
90
+ detached: process.platform !== 'win32',
91
+ env: spawnEnv(),
92
+ stdio: ['ignore', out, out]
93
+ });
94
+ }
95
+ catch (err) {
96
+ fs.closeSync(out);
97
+ return `Error: failed to start background process: ${err.message}`;
98
+ }
99
+ fs.closeSync(out);
100
+ const entry = { id, pid: child.pid, command, logPath, startedAt: Date.now(), exited: false, exitCode: null };
101
+ handles.set(id, entry);
102
+ child.on('exit', code => { entry.exited = true; entry.exitCode = code ?? null; });
103
+ child.unref();
104
+ return `Background process started.\n- handle id: ${id}\n- pid: ${child.pid}\n- log: ${logPath}\nUse check_background_process with this id to read output, or stop_background_process to terminate it.`;
105
+ }
106
+ };
107
+ export const CheckBackgroundProcessTool = {
108
+ name: "Background Process Checker",
109
+ definition: {
110
+ type: "function",
111
+ function: {
112
+ name: "check_background_process",
113
+ description: "Check a background process started with start_background_process: running/exited status and the tail of its output log.",
114
+ parameters: {
115
+ type: "object",
116
+ properties: {
117
+ id: { type: "string", description: "The handle id returned by start_background_process." }
118
+ },
119
+ required: ["id"]
120
+ }
121
+ }
122
+ },
123
+ handler: async (args) => {
124
+ const id = String(args.id ?? '');
125
+ const entry = handles.get(id);
126
+ if (!entry) {
127
+ return `Error: unknown background process id "${id}". Handles live for the current AutoClaw process only; the log file may still exist under ~/.autoclaw/output/.`;
128
+ }
129
+ const status = entry.exited
130
+ ? `exited (code: ${entry.exitCode ?? 'unknown'})`
131
+ : `running (pid: ${entry.pid}, ${Math.round((Date.now() - entry.startedAt) / 1000)}s elapsed)`;
132
+ return `Background process ${id}: ${status}\nCommand: ${entry.command}\nLog tail:\n${tailOf(entry.logPath)}`;
133
+ }
134
+ };
135
+ export const StopBackgroundProcessTool = {
136
+ name: "Background Process Stopper",
137
+ definition: {
138
+ type: "function",
139
+ function: {
140
+ name: "stop_background_process",
141
+ description: "Terminate a background process started with start_background_process (kills the whole process tree).",
142
+ parameters: {
143
+ type: "object",
144
+ properties: {
145
+ id: { type: "string", description: "The handle id returned by start_background_process." }
146
+ },
147
+ required: ["id"]
148
+ }
149
+ }
150
+ },
151
+ handler: async (args) => {
152
+ const id = String(args.id ?? '');
153
+ const entry = handles.get(id);
154
+ if (!entry) {
155
+ return `Error: unknown background process id "${id}".`;
156
+ }
157
+ if (entry.exited) {
158
+ return `Background process ${id} already exited (code: ${entry.exitCode ?? 'unknown'}). Log: ${entry.logPath}`;
159
+ }
160
+ if (entry.pid)
161
+ killProcessTree(entry.pid);
162
+ entry.exited = true;
163
+ return `Background process ${id} terminated.\nLog: ${entry.logPath}\nLog tail:\n${tailOf(entry.logPath)}`;
164
+ }
165
+ };
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import inquirer from 'inquirer';
4
4
  import chalk from 'chalk';
5
5
  import { execShellCommand } from '../shell.js';
6
+ import { resolveSandboxMode, sandboxBackend, sandboxedInvocation } from '../sandbox.js';
6
7
  import * as os from 'os';
7
8
  const DEFAULT_SHELL_TIMEOUT_MS = 120000;
8
9
  const SHELL_MAX_BUFFER = 10 * 1024 * 1024;
@@ -75,6 +76,21 @@ export const ShellTool = {
75
76
  }
76
77
  }
77
78
  const timeoutMs = Number(config?.shellTimeout || process.env.AUTOCLOW_SHELL_TIMEOUT || DEFAULT_SHELL_TIMEOUT_MS);
79
+ // Sandbox policy: non-default modes wrap the invocation in a platform
80
+ // backend, or refuse (fail-closed) when no backend exists.
81
+ const sandboxMode = resolveSandboxMode(config);
82
+ let sandboxed;
83
+ if (sandboxMode !== 'danger-full-access') {
84
+ const backend = sandboxBackend();
85
+ if (!backend.available) {
86
+ return `Error: sandbox mode "${sandboxMode}" is configured but no sandbox backend is available: ${backend.detail}`;
87
+ }
88
+ const inv = sandboxedInvocation(args.command, sandboxMode, process.cwd());
89
+ if ('blocked' in inv) {
90
+ return `Error: sandbox mode "${sandboxMode}" cannot wrap this command: ${inv.detail}`;
91
+ }
92
+ sandboxed = inv;
93
+ }
78
94
  // Check for auto-confirm flag
79
95
  if (!config?.autoConfirm) {
80
96
  if (!process.stdin.isTTY) {
@@ -97,7 +113,7 @@ export const ShellTool = {
97
113
  console.log(chalk.gray("(Auto-confirming command execution due to --yes flag)"));
98
114
  }
99
115
  try {
100
- const r = await execShellCommand(args.command, { timeoutMs, maxBuffer: SHELL_MAX_BUFFER });
116
+ const r = await execShellCommand(args.command, { timeoutMs, maxBuffer: SHELL_MAX_BUFFER, sandboxed });
101
117
  if (r.timedOut) {
102
118
  return `Command timed out after ${timeoutMs}ms and was terminated.\nStdout: ${r.stdout}\nStderr: ${r.stderr}`;
103
119
  }
@@ -6,6 +6,7 @@ import { BrowserTool } from './browser.js';
6
6
  import { ScreenshotTool } from './screenshot.js';
7
7
  import { ImageTool } from './image.js';
8
8
  import { PromptOptimizerTool } from './prompt-optimizer.js';
9
+ import { CheckBackgroundProcessTool, StartBackgroundProcessTool, StopBackgroundProcessTool } from './background.js';
9
10
  // Central Registry of all available tools
10
11
  export const toolRegistry = [
11
12
  ShellTool,
@@ -18,7 +19,10 @@ export const toolRegistry = [
18
19
  NotifyTool,
19
20
  BrowserTool,
20
21
  ScreenshotTool,
21
- ImageTool
22
+ ImageTool,
23
+ StartBackgroundProcessTool,
24
+ CheckBackgroundProcessTool,
25
+ StopBackgroundProcessTool
22
26
  ];
23
27
  export function getToolDefinitions(config) {
24
28
  return toolRegistry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autoclaw",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {