aacode-rs 1.8.15

Rust port of the AACode ReAct coding agent, built on fastshell for mobile AI agents
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# aacode-rs 使用指南

> Rust 版 AACode ReAct 编码 Agent,基于 `fastshell` 沙箱运行时
> 位置: `fastshell_local/aacode-rs/`

---

## 1. 快速开始

### 1.1 前置依赖

- **macOS / Linux**:默认使用本地 OS shell(`sh`),无需额外安装。
- **Windows**:默认使用本地 OS shell(`cmd`),无需额外安装。
- **Android / iOS**:使用 fastshell 沙箱引擎(VFS + 180+ 命令 + 内嵌 CPython),由 App 负责初始化。
- **LLM API key**:必需,支持 DeepSeek / OpenAI / Kimi / Anthropic 等(见 §2)。

### 1.2 安装与构建

**开源 / 同仓库开发**(`fastshell` 在 `../fastshell`,同 repo):

```bash
# 从 aacode-rs 目录直接构建(aacode-rs 是独立 workspace)
cd aacode-rs
cargo build --release        # 桌面 CLI + 库
cargo build --lib --release  # 仅库(需先装 cmake,见下方说明)

# 编译产物:
#   桌面 CLI:   target/release/aacode
#   静态库:     target/release/libaacode_rs.a
#   动态库:     target/release/libaacode_rs.dylib / .so
```

> **关于 cmake**:`fastshell` 可选依赖 `aws-lc-sys`(rustls 的加密原语)在单独构建 `aacode-rs` 时需要 cmake。如无 cmake,可从 workspace 根构建(`cd .. && cargo build -p aacode-rs`),此时 fastshell 依赖链已缓存。

**开源单独发布**(仅 `aacode-rs` 目录,无 `fastshell` 源码):

```bash
# 将 fastshell 改为 git 依赖(编辑 aacode-rs/Cargo.toml)
#   fastshell = { git = "https://github.com/xxx/fastshell", rev = "v0.3.4" }
cd aacode-rs
cargo build --release
```

**产物:**
- 桌面 CLI:`target/release/aacode`
- 静态库(Android):`target/release/libaacode_rs.a`(`cargo build --target aarch64-linux-android --release --lib`)

### 1.3 桌面 CLI 用法

```bash
# 单任务
aacode -p /path/to/project "用 Flask 写一个 hello world API"

# 指定 session 续接
aacode --session session_20260714_123456_abc123 "继续完善这个 API"

# Plan-first 模式(先规划,再执行)
aacode -p /path/to/project --plan-first "重构用户认证模块"

# 交互式会话模式
aacode -p /path/to/project --interactive

# 查看帮助
aacode --help
```

---### 1.4 与 Python 版 aacode 的共存与迁移

**设计意图**:Rust 版(aacode-rs)是 Python 版 aacode 的**替代品**,不是并存。以下说明两者冲突点与迁移方式。

#### 会冲突的点

**CLI 命令名**

两者安装后都提供一个叫 `aacode` 的可执行文件:

| 版本 | 安装方式 | 典型安装路径 |
|---|---|---|
| Python | `pip install aacode` | `~/.local/bin/aacode` 或 venv |
| Rust | `cargo install --path .` | `~/.cargo/bin/aacode` |

同时安装两个时,`$PATH` 中靠前的生效。

**解决方法**:

```bash
# 切到 Rust 版:卸载 Python 版
pip uninstall aacode

# 或者不改 PATH,用绝对路径区分
~/.cargo/bin/aacode "任务"        # Rust 版
~/.local/bin/aacode "任务"        # Python 版
```

**`.aacode/` 项目数据目录**

两者都在项目目录下创建 `.aacode/`,写入:
- `.aacode/sessions/<id>.json` — 会话历史
- `.aacode/sessions/sessions_index.json` — 会话索引
- `.aacode/context/` — 大输出归档
- `.aacode/extracts/` — 工具输出归档

**格式基本兼容** —— Rust 版的 `SessionMessage` 序列化字段与 Python 版一致(role/content/tool_calls/tool_call_id/reasoning_content),跨版本读旧会话文件通常无问题。

**两个已知微小差异**:

| 差异 | Python 版 | Rust 版 | 影响 |
|---|---|---|---|
| 时间戳格式 | ISO 8601 字符串 | epoch 秒数字符串 | 读取不受影响;`list_sessions` 排序时可能有微小偏差 |
| token 计数 | tiktoken(精确) | 字符估算(4:1) | 会话统计数字不一致;**不影响对话功能** |

> 如两个版混用同一个项目目录,会话文件不会被损坏,只是统计可能有轻微偏差。

#### 不会冲突的点

- **依赖生态**:`pip`(Python)和 `cargo`(Rust)完全独立,互不干扰
- **进程**:都是短命 CLI 程序,不驻留后台,不同时跑就不冲突
- **端口**:都没有长期监听端口

---

## 2. 环境配置

### 2.1 LLM 配置

**环境变量**(推荐,优先级最高):

```bash
# DeepSeek(OpenAI 兼容网关)
export LLM_API_KEY="sk-your-key"
export LLM_API_URL="https://api.deepseek.com/v1"
export LLM_MODEL_NAME="deepseek-chat"

# DeepSeek(Anthropic 兼容网关 — 同一 key,不同端点)
export LLM_API_KEY="sk-your-key"
export LLM_API_URL="https://api.deepseek.com/anthropic"
export LLM_MODEL_NAME="deepseek-v4-flash"
export LLM_GATEWAY="anthropic"

# OpenAI
export LLM_API_KEY="sk-your-key"
export LLM_API_URL="https://api.openai.com/v1"
export LLM_MODEL_NAME="gpt-4o"

# Kimi
export LLM_API_KEY="your-kimi-key"
export LLM_API_URL="https://api.moonshot.cn/v1"
export LLM_MODEL_NAME="kimi-k2"
```

**JSON 配置文件**(沙箱内 `aacode_config.json`,优先级低于 env):

```json
{
  "model": {
    "name": "deepseek-chat",
    "api_key": "sk-...",
    "base_url": "https://api.deepseek.com/v1",
    "gateway": "openai"
  },
  "search": {
    "searxng_url": "https://your-searxng-instance"
  }
}
```

**taskJson 内联**(移动端/FFI 路径,通过 `aacode_task_start(taskJson, ...)` 传入):

```json
{
  "task": "创建 hello.py 并运行",
  "project_path": "/sandbox/projects/demo",
  "session_id": "session_20260714_123456_abc123",
  "model": {
    "name": "deepseek-chat",
    "api_key": "sk-...",
    "base_url": "https://api.deepseek.com/v1",
    "gateway": "openai"
  },
  "max_iterations": 30,
  "plan_first": false,
  "shell_backend": "auto",
  "search": {
    "searxng_url": "https://..."
  },
  "multimodal": {
    "name": "kimi-k2.5",
    "api_key": "...",
    "base_url": "https://api.moonshot.cn/v1"
  }
}
```

### 2.2 配置优先级

```
taskJson 内联 > 环境变量 > aacode_config.json > 默认值
```

---

## 3. Shell 执行后端

**这是 aacode-rs 区别于 Python 版的关键设计**。

### 3.1 两种后端

| 后端 | 实现 | 适用场景 |
|---|---|---|
| **NativeShell** | 真实 OS shell (`sh -c` / `cmd /C`) | 桌面开发(Linux / macOS / Windows) |
| **FastshellBackend** | fastshell 沙箱引擎(VFS + 180+ 内置命令 + CPython) | 移动端(Android / iOS) |

### 3.2 自动选择

```rust
// 默认行为: 根据目标 OS 自动选
shell_backend: "auto"
// → 桌面 = native OS shell
// → 移动端 = fastshell 沙箱
```

### 3.3 手动切换

**三选一**,在任意平台上都可强制指定:

| 值 | 行为 |
|---|---|
| `"auto"` | 按目标 OS 自动选(桌面=native, 移动=fastshell) |
| `"native"` | **强制**走真实 OS shell(需系统有 sh/cmd) |
| `"fastshell"` | **强制**走 fastshell 沙箱引擎 |

**设置方式**:

```bash
# 环境变量
export AACODE_SHELL_BACKEND=native   # 或 fastshell / auto

# 或通过 taskJson 的 "shell_backend" 字段
```

### 3.4 桌面端用 NativeShell 的优势

| 能力 | NativeShell(桌面默认) | Fastshell(沙箱) |
|---|---|---|
| heredoc(`cat <<'EOF'`) | ✅ 原生支持 | ❌ 不支持 |
| 管道(`\|`) | ✅ 原生支持 | 部分支持(fastshell pipeline) |
| 真实路径(`/tmp/foo/bar`) | ✅ 自然路径 | VFS 沙箱内嵌套 |
| `grep` / `rg` | ✅ 系统原生 | fastshell 内置 |
| `python3` / `pip` / `pytest` | ✅ 系统 Python | fastshell 内嵌 CPython |
| `git` / `curl` / `docker` | ✅ 系统原生 | fastshell 内置 |
| 子进程 | ✅ 系统原生 | 受沙箱策略限制 |

### 3.5 在 observation 中识别后端

每条 `run_shell` 的返回值含 `"backend"` 字段:

```json
{"backend": "native", "returncode": 0, "stdout": "...", ...}      // NativeShell
{"backend": "fastshell", "returncode": 0, "stdout": "...", ...}   // FastshellBackend
```

模型可以看到这个字段,从而在沙箱模式时**自动适配命令风格**——例如 fastshell 里用 `python3` 而非 `python`、避开 heredoc、用 fastshell 内置命令替代系统工具。

---

## 4. C ABI / 移动端集成

### 4.1 公开的 C 函数(句柄式)

```c
typedef void (*aacode_event_fn)(const char* line, void* userdata);

void* aacode_task_start(const char* task_json, aacode_event_fn cb, void* userdata); // 非阻塞
char* aacode_task_wait(void* handle);   // 阻塞到结束,返回终态 JSON
void  aacode_task_cancel(void* handle); // 非阻塞,按句柄取消
void  aacode_task_free(void* handle);
char* aacode_validate_api_key(const char* config_json);
char* aacode_list_sessions(const char* project_path);
char* aacode_get_session_messages(const char* project_path, const char* session_id);
void  aacode_free_string(char* ptr);
```

### 4.2 调用流程(Android / iOS 一致)

```
宿主 UI (Kotlin / Swift)
  → aacode_task_start(taskJson, cb, userdata)   // 非阻塞,返回句柄
    → Rust: TOKIO_RT.spawn(AgentRuntime::run_task)
      → 每个 JSONL 事件 → cb(line, userdata)     // 从 tokio worker 线程发出
        → 宿主 parser → UI
  → aacode_task_wait(handle)                    // 阻塞取终态
  → aacode_task_free(handle)

取消:aacode_task_cancel(handle) → ReAct 循环在检查点响应
```

### 4.3 静态库构建

```bash
cargo build -p aacode-rs --release --target aarch64-linux-android --lib
# → target/aarch64-linux-android/release/libaacode_rs.a
CMakeLists.txt: 链接 libaacode_rs.a

# iOS
cargo build -p aacode-rs --release --target aarch64-apple-ios --lib
cargo build -p aacode-rs --release --target aarch64-apple-ios-sim --lib
```

---

## 5. 任务输入 / 输出 JSONL 协议

**严格对齐 Kotlin `AgentOutputParser`**(详见 `design.md` §7)。每条事件独占一行:

```json
{"type":"start","task":"写个 hello world"}
{"type":"session_created","session_id":"session_..."}
{"type":"seg_content","seg":"thinking","content":"..."}
{"type":"seg_content","seg":"thought","content":"..."}
{"type":"tool_progress","state":"building","name":"run_shell","chars":42}
{"type":"seg_content","seg":"observation","content":"..."}
{"type":"done","session_id":"session_..."}
{"type":"error","message":"..."}
```

token 行内换行在管道模式下用 `\x00` 转义。

### 5.1 向后兼容的增量事件(2026-08)

旧事件(`start`/`session_*`/`seg_content`/`tool_progress`/`delta`/`done`/`error`)
**始终照发且 wire 形状不变**,现有 Android / iOS 解析器零改动。新增:

- `{"type":"session","task":"...","session_id":"session_...","created":true|false}`
  合并启动事件,紧跟 `start` + `session_created`/`session_switched` 之后发出。
  无 `content` 字段,被解析器当作未知类型忽略或供新客户端消费。
- `seg_content`(observation)额外携带 `truncated`(bool)与 `total_chars`(int)字段,
  旧解析器忽略未知字段,新客户端可据此渲染折叠 UI。
- `seg_append {"type":"seg_append","seg":"...","content":"..."}` 与
  `seg_reset {"type":"seg_reset","seg":"...","content":"..."}` 增量 patch 事件,
  仅当 `EventSink::supports_incremental()` 返回 `true` 时发出(旧宿主默认 `false`,
  永远收不到,行为完全不变);新宿主 opt-in 后可原地 patch 存活段(稳定 id、
  不触发全量重建)。

---

## 6. 测试

### 6.1 测试架构

```
层级          | 运行方式        | 依赖真实 API | 数量   | 覆盖范围
-------------|----------------|-------------|--------|---------
单元测试      | cargo test     | 否           | 136    | 所有模块
集成测试      | cargo test     | 否 (mock)   | 5      | ReAct 闭环 + Python + 会话
LLM 兼容测试  | cargo test     | 否 (录制)   | 23     | 多厂商 SSE + 格式容错
LLM 活体测试  | L KEY=... -- --ignored | 是    | 4      | DeepSeek 文本+工具双网关
shell 后端测试 | cargo test     | 否 (本地 sh)| 10+   | 见 §6.2
```

### 6.2 Shell 后端专项测试

#### 自动选择 (Auto)

| 测试 | 覆盖 |
|---|---|
| `runtime::desktop_defaults_to_native_backend` | 桌面 `Auto` → `NativeShell` |
| `runtime::force_fastshell_backend` | 强制 `Fastshell` → `backend_kind()=="fastshell"` |
| `runtime::native_backend_uses_real_cwd` | `Native` 后端文件落在真实 CWD |

#### 原生 shell 功能 (`tools::backend::NativeShell`)

| 测试 | 覆盖 |
|---|---|
| `native_echo` | 基本 stdout + exit code |
| `native_heredoc_works` | heredoc `cat <<'EOF'` + 文件落地 |
| `native_real_cwd_and_pipes` | `grep \| wc -l` 真实管道 |
| `native_stdin_input` | stdin 支路 |
| `native_nonzero_exit` | `exit 3` → exit code 3 |
| `native_timeout_kills` | `sleep 10` + 1s 超时 → exit 124 |
| `native_python_via_backend` | `python3 -c` 真实输出 |

#### 工具层集成 (`tools::shell::ShellTool`)

| 测试 | 覆盖 |
|---|---|
| `native_echo_roundtrip` | JSON 返回含 `"backend":"native"` |
| `native_writes_to_real_cwd` | `echo > note.txt` 在真实 CWD |
| `native_heredoc_via_tool` | heredoc 通过工具层 |
| `fastshell_backend_still_works` | 沙箱后端 `"backend":"fastshell"` |
| `max_output_truncation` | `max_output` 参数截断 |
| `dangerous_reject_mode` | `rm -rf /` 被拦截 |

#### 跨语言能力

| 测试 | 覆盖 |
|---|---|
| `backend::tests::platform_default_is_native_on_desktop` | cfg 宏验证 |
| `runtime::desktop_defaults_to_native_backend` | 运行时自检 |

### 6.3 环境变量切换测试

```bash
# 测试 Auto (桌面 → native)
cargo test -p aacode-rs runtime::desktop_defaults_to_native_backend

# 测试强制 fastshell
cargo test -p aacode-rs runtime::force_fastshell_backend

# 测试强制 native
cargo test -p aacode-rs runtime::native_backend_uses_real_cwd
```

### 6.4 运行全部测试

```bash
# 全部离线测试
cargo test -p aacode-rs

# 离线 + 真实 API(需要 key)
LLM_API_KEY=sk-xxx LLM_API_URL=... LLM_MODEL_NAME=deepseek-chat \
cargo test -p aacode-rs -- --ignored --nocapture

# Shell 后端相关全部测试
cargo test -p aacode-rs -- backend shell native fastshell runtime
```

### 6.5 端到端验证清单

| 场景 | 验证方式 |
|---|---|
| 写文件 + heredoc + python3 | `aacode -p /tmp/demo "用 heredoc 创建 greet.py 并 python3 运行"` |
| 搜索 + fetch_url + git | 给模型一个实际 repo 路径 |
| 多轮会话 | `aacode --session <id>` 续接 |
| Plan-first | `aacode --plan-first` |
| 中断 + 恢复 | Ctrl+C → `aacode --session <id>` |
| 多语言任务 | 同时要求 Python + Shell 操作 |
| Anthropic 网关 | `LLM_GATEWAY=anthropic LLM_API_URL=https://api.deepseek.com/anthropic` |
| 强制 fastshell 桌面 | `AACODE_SHELL_BACKEND=fastshell` |

---

## 7. 故障排查

**"API key not configured"**
```bash
export LLM_API_KEY="sk-your-key"
```
或通过 `aacode_config.json` 或 taskJson 的 `model.api_key` 设置。

**401 / authentication failed**
- 检查 key 是否有效
- 检查 endpoint(`LLM_API_URL`)是否正确

**http timeout / network error**
- 检查网络连接
- 增加 `model.max_tokens` 或减小上下文

**"Python is not available" (fastshell 后端)**
- 桌面端: 默认 native 后端不受影响
- 移动端: 需要 fastshell 内嵌 CPython

**"Command rejected by safety guard"**
- 危险命令(`rm -rf /`)被拦截
- 关掉: `config.safety.dangerous_command_action = "log"` 或 "ask"

---

## 8. 相关文档

| 文档 | 说明 |
|---|---|
| `design.md` | 完整设计文档 |
| `LLM_CLIENT.md` | LLM 客户端设计与兼容性 |
| `DEPS.md` | Python → Rust 依赖逐条替代说明 |
| `../MOBILE_AGENT_GUIDE.md` | 移动端集成指南 |