Usability pass: tool registry, TUI polish, saved keys and settings
ci / linux (clang) (push) Canceled after 0s
ci / linux (gcc) (push) Canceled after 0s
ci / sanitizers (push) Canceled after 0s
ci / musl (push) Canceled after 0s
ci / macos (push) Canceled after 0s

Tools
- Table-driven tool registry with permission classes and default/minimal
  profiles; new tools: edit_file, list_dir, glob, grep, delete_file,
  move_file, todo_write, web_fetch, ask_user (plus read_file paging).
- Built-in Myers diff; write tools return unified diffs.
- Persistent permission rules (.agent/permissions.json), per-turn undo
  snapshots (.agent/undo), AGENTS.md project notes, auto-compact at 80%
  of the model context, cancellable run_shell with process-group kill.

TUI
- Table-driven slash commands: /provider /key /effort /status /tools
  /permissions /yolo /undo /retry /diff /export /copy /rename /delete
  /init /max-steps /todo, with / and @ completion.
- Permission modal with diff and prefix rules, ask_user modal, todo
  panel, header with model/effort/context, bracketed paste, line editing,
  wide-char cursor, NO_COLOR and ASCII fallback; Esc never quits.

Startup and configuration
- API keys are entered in the TUI (hidden) and saved per provider in
  <config>/credentials (0600); env variables are only a fallback.
- Last provider, per-provider model and reasoning effort are remembered
  in <config>/settings.json; --effort/-e and /effort send
  reasoning_effort (low|medium|high|max) to models that accept it.
- DeepSeek preset uses deepseek-flash / deepseek-v4-pro.
- Session flags --new/--continue/--resume/--list-sessions, --yolo,
  --confirm.

Compatibility and tests
- ncurses detection, macOS/Homebrew support, make install, XDG config
  dir, POSIX cleanups; Gitea CI (gcc/clang/asan/musl/macOS).
- Unit tests for diff, fs tools, registry, undo/perms and config;
  e2e fixtures multi_file_refactor and ask_or_todo; vendored cJSON.
This commit is contained in:
WHC
2026-10-05 01:47:01 +08:00
parent 9877ff48b6
commit 2fde898da0
105 changed files with 19348 additions and 3786 deletions
+94
View File
@@ -0,0 +1,94 @@
# Platform support: Linux, WSL, macOS
CST-Agent targets POSIX systems: Linux (glibc and musl), WSL 2 and macOS.
Native Windows builds are covered separately in `README-windows.md`.
## Build
| Platform | Dependencies | Build |
|---|---|---|
| Debian / Ubuntu / WSL | `build-essential pkg-config libcurl4-openssl-dev libncurses-dev` | `make` |
| Fedora | `gcc make pkgconf libcurl-devel ncurses-devel` | `make` |
| Arch | `base-devel curl ncurses` | `make` |
| Alpine (musl) | `build-base pkgconf curl-dev ncurses-dev` | `make` |
| NixOS | `nix-shell` (uses `shell.nix`) | `make` |
| macOS | `xcode-select --install`, `brew install ncurses curl pkg-config` | `make` |
`make install PREFIX=~/.local` installs the binary; CMake works too
(`cmake -B build && cmake --build build && ctest --test-dir build`).
The Makefile picks `ncursesw`, then `ncurses`, through pkg-config. On macOS it
adds Homebrew's keg-only `ncurses`/`curl` to `PKG_CONFIG_PATH`; without
Homebrew it falls back to the system `-lncurses` (wide-character capable).
Verified so far: NixOS x86_64 with gcc and clang (`-Werror`, `make check`,
`make asan`, ctest), and every source compiled `-fsyntax-only` with
`x86_64-unknown-linux-musl-gcc` without warnings. Full musl, macOS and WSL
builds run in CI (`.gitea/workflows/ci.yml`; the macOS job needs a runner and
the `HAVE_MACOS_RUNNER` variable) or by hand with the checklist below.
## Files
| What | Where |
|---|---|
| API keys (one per provider, entered in the TUI or with `--ask-key`) | `$XDG_CONFIG_HOME/cst-agent/credentials` (default `~/.config/cst-agent/`), mode 0600 |
| Last provider, models and effort | `$XDG_CONFIG_HOME/cst-agent/settings.json` |
| Sessions, undo snapshots, exports | `<workspace>/.agent/` (git-ignored automatically) |
| Saved permission rules | `<workspace>/.agent/permissions.json` |
| Project notes injected into the prompt | `<workspace>/AGENTS.md` (or `CST.md`) |
## Terminal notes
- UTF-8 locale gives the full UI. Under a non-UTF-8 locale, or with
`CST_ASCII=1`, box and icon glyphs fall back to ASCII.
- `NO_COLOR=1` turns colours off.
- `/copy` uses OSC 52. iTerm2 needs "Applications in terminal may access
clipboard"; tmux needs `set -g set-clipboard on`; Windows Terminal (WSL)
supports it out of the box.
- Bracketed paste needs ncurses (all platforms above).
- macOS Terminal.app sends Option+Enter as Esc+Enter only with "Use Option as
Meta key"; Ctrl+N always inserts a newline.
## Acceptance checklist
Run on each platform before a release. Use a scratch git repository as the
workspace and a real API key (entered when the TUI asks for it).
Build and tests:
- [ ] `make` has no warnings; `make check` passes
- [ ] `make asan` passes (Linux; macOS: `make CFLAGS="-fsanitize=address,undefined -g" check`)
- [ ] `cmake -B build && cmake --build build && ctest --test-dir build` passes
- [ ] `make install PREFIX=/tmp/cst && /tmp/cst/bin/cst-agent --help`
CLI:
- [ ] `--list-sessions`, `--new`, `--resume <prefix>`, `--continue` behave as documented
- [ ] `--headless --prompt "list the files"` prints a transcript and exits 0
- [ ] First start asks for a provider and key; the credentials file is mode 0600
- [ ] Second start reuses provider, model and effort without asking
TUI:
- [ ] Typing Chinese/emoji keeps the cursor in the right column; ←→ Home End work
- [ ] Pasting a multi-line block does not send it; Enter sends
- [ ] `/` shows the command popup, Tab completes, `@` completes file paths
- [ ] Esc cancels a running turn; Esc on an empty prompt does nothing; Ctrl+C twice quits
- [ ] A long `run_shell` (e.g. `sleep 30`) stops promptly on Esc
- [ ] Write permission shows the diff; `a` saves a rule; `p` saves a shell prefix rule
- [ ] `ask_user` modal accepts a number, an arrow selection and typed text
- [ ] Todo panel appears while tasks are open; Ctrl+T hides it
- [ ] `/undo` restores files changed by the last turn; `/retry` re-asks
- [ ] `/diff`, `/export`, `/status`, `/tools off grep`, `/yolo` work
- [ ] Resize the terminal during streaming: layout recovers
- [ ] `NO_COLOR=1` and `CST_ASCII=1` render readable screens
WSL specifics:
- [ ] Workspace under `/mnt/c/...` works (slower IO is expected); `~/project` is recommended
- [ ] Windows Terminal: mouse wheel scroll and OSC 52 `/copy`
macOS specifics:
- [ ] Builds with Homebrew ncurses and with the system ncurses (`brew unlink ncurses`)
- [ ] Terminal.app and iTerm2: arrows, Home/End, PgUp/PgDn, bracketed paste
+50 -28
View File
@@ -1,47 +1,56 @@
# OpenAI 兼容 API 环境变量
CST-Agent 使用 **OpenAI Chat Completions** 协议(`POST {base}/chat/completions`),
可对接官方 OpenAI、MiniMax、以及其它兼容网关。
CST-Agent 使用 **OpenAI Chat Completions** 协议(`POST {base}/chat/completions`,SSE 流式),
可对接官方 OpenAI、DeepSeek、MiniMax,以及其它兼容网关。
## 必填 / 常用
## Provider 预设
| 变量 | 别名 | 说明 | 示例 |
`--provider NAME`(或 `CST_PROVIDER`,或 TUI 里的 `/provider`)一次性给出 base 和默认模型。
不传时沿用上次使用的 provider(记在配置目录的 `settings.json`)。
| provider | API Base | 默认模型 | Key 变量 |
|---|---|---|---|
| `CST_API_KEY` | `OPENAI_API_KEY`、`MINIMAX_API_KEY` | API Key(按此优先级读取) | `sk-...` |
| `CST_API_BASE` | `OPENAI_BASE_URL`、`OPENAI_API_BASE` | API 根路径,须含 `/v1`(不要带 `/chat/completions`) | `https://api.openai.com/v1` |
| `CST_MODEL` | `OPENAI_MODEL` | 模型名 | `gpt-4.1-mini` / `MiniMax-M2.7` |
| `minimax`(默认) | `https://api.minimax.cn/v1` | `MiniMax-M2.7` | `MINIMAX_API_KEY` |
| `deepseek` | `https://api.deepseek.com/v1` | `deepseek-flash`(另有 `deepseek-v4-pro`) | `DEEPSEEK_API_KEY` |
| `openai` | `https://api.openai.com/v1` | `gpt-4.1-mini` | `OPENAI_API_KEY` |
未设置 key 时:读配置文件 →(Linux)zenity / 控制台输入。
显式给出的 `--api-base` / `--model` / 下表中的环境变量优先于预设。
配置文件:
- Linux/Termux:`~/.config/cst-agent/credentials`
## 解析顺序
| 项 | 优先级(高 → 低) |
|---|---|
| Key | `--api-key`(**只用于本次进程,不写盘**)→ 该 provider 已保存的 key → provider 的 Key 变量 → `CST_API_KEY` → TUI 弹出遮罩输入框(输入后保存) |
| Base(含 `/v1`,不带 `/chat/completions`) | `--api-base` → `CST_API_BASE` → `OPENAI_BASE_URL` → `OPENAI_API_BASE` → 预设 |
| Model | `--model` → `CST_MODEL` → `OPENAI_MODEL` → 该 provider 上次使用的模型 → 预设 |
| 推理强度 | `--effort` → `CST_EFFORT` → 上次使用 → `medium`(low / medium / high / max) |
key 按 provider 保存在 credentials 文件(权限 0600),每行 `<provider>.api_key=...`;
TUI 里 `/key` 更换,命令行 `--ask-key` 输入(不回显):
- Linux/macOS/Termux:`$XDG_CONFIG_HOME/cst-agent/credentials`(默认 `~/.config/cst-agent/`)
- Windows:`%APPDATA%\cst-agent\credentials`
旧版只有一行 `api_key=` 的文件仍可读:按其中的 `api_base` 归到对应 provider(没有 `api_base` 时视为 MiniMax)。
## 示例
### DeepSeek
```bash
cst-agent --provider deepseek # 首次会询问 key;默认 deepseek-flash
cst-agent --provider deepseek --model deepseek-v4-pro -e high # 推理内容显示为 Thought
```
### OpenAI 官方
```bash
export OPENAI_API_KEY='sk-...'
export OPENAI_BASE_URL='https://api.openai.com/v1'
export OPENAI_MODEL='gpt-4.1-mini'
./cst-agent --workspace .
```
等价 CST 前缀:
```bash
export CST_API_KEY='sk-...'
export CST_API_BASE='https://api.openai.com/v1'
export CST_MODEL='gpt-4.1-mini'
cst-agent --provider openai
```
### MiniMax(默认)
```bash
export CST_API_KEY='...' # 或 MINIMAX_API_KEY
# 默认 base=https://api.minimax.cn/v1 model=MiniMax-M2.7
./cst-agent --workspace .
cst-agent --provider minimax
```
### 任意兼容网关
@@ -53,21 +62,34 @@ export OPENAI_MODEL='your-model'
./cst-agent --headless --prompt 'ping' --workspace .
```
命令行也可覆盖:`--api-base` / `--api-key` / `--model`。
网关不支持 `stream_options.include_usage` 时设 `CST_NO_STREAM_USAGE=1`。
## 其它环境变量
| 变量 | 说明 |
|---|---|
| `CST_PROTOCOL` | 工具调用协议:`native-eos`(默认)/ `native-inc` / `ctl` / `ctl-heredoc` / `xml` |
| `CST_MAX_STEPS` | 每个用户回合最多调用模型几次(默认 30) |
| `CST_TOOL_OUT_CAP` | 单个工具输出发给模型的字节上限(默认 16384) |
| `CST_CTX_WINDOW` | 发给模型的最近非 system 消息条数(默认 80) |
| `CST_SHELL_TIMEOUT` | `run_shell` 超时秒数(默认 120) |
| `CST_METRICS=0` / `CST_METRICS_PATH` | 关闭 / 指定计时埋点 JSONL |
| `CST_RECORD_SSE` / `CST_REPLAY_SSE` | 录制 / 回放原始 SSE 流目录;`CST_REPLAY_CHUNK` 控制回放分块字节数 |
| `CST_MODELS` | `/model` 可选列表,`:` 或 `,` 分隔 |
| `CST_AUTO_APPROVE=1` | 自动允许 shell/写文件(headless 常用) |
| `CST_AUTO_APPROVE=1` | 自动允许 shell/写文件(headless 默认开启) |
| `CST_SHELL` | shell 解释器路径(覆盖 busybox/`sh`) |
| `CST_EXE_DIR` | 可执行文件目录(Termux 旁路 busybox 时有用) |
网络:连接超时 30 秒;300 秒内无数据视为超时;429 / 5xx / 连接错误在尚未收到任何内容时自动重试 3 次(1s、2s、4s 退避)。
## CLI 对照
```text
--provider NAME ← CST_PROVIDER
--api-base URL ← CST_API_BASE / OPENAI_BASE_URL
--api-key KEY ← CST_API_KEY / OPENAI_API_KEY
--api-key KEY ← 已保存的 key / <provider key 变量> / CST_API_KEY
--model NAME ← CST_MODEL / OPENAI_MODEL
--effort LEVEL ← CST_EFFORT
--protocol P ← CST_PROTOCOL
--max-steps N ← CST_MAX_STEPS
```
+101 -22
View File
@@ -10,18 +10,15 @@
| 层 | 主张 | 主变量 | 非主张 |
|---|---|---|---|
| 论文 CST | \(O_{\mathrm{conf}}\) 有界确认 → HTML | \(\mathcal{L}_0\) vs 引用式链接等 | — |
| Tool-loop CST | 同构到工具帧 | **CTL vs JSON-buffer** 截断 | 网关空答率、模型智商 |
| 工程载体 | OpenAI `tool_calls` + XML fallback | 可演示端到端 | 不算进 CST 正确性定理 |
| Tool-loop CST | 同构到工具帧 | **CTL vs JSON-buffer** 截断;确认即执行 | 网关空答率、模型智商 |
| 工程载体 | 五种协议同一 agent 循环 | 端到端通过率、步数、token、时延 | 不算进 CST 正确性定理 |
## 实验 A — 饥饿 / 截断(已自动化)
## 实验 A — 饥饿 / 截断(合成样例,已自动化)
```bash
cd agent && make check
# 关注 ./tests/test_replay_starvation
make check # 关注 tests/test_replay_starvation
```
填写模板(跑完后更新数字):
| 指标 | CTL | JSON-buffer |
|---|---|---|
| 完整输入可执行 | yes | yes |
@@ -29,45 +26,127 @@ cd agent && make check
| 安全截断样本(本机 `make check`) | 3 | 3 次 trunc failure |
对应理论:裸 JSON ∉ \(\mathcal{L}_0^{\mathrm{tool}}\);CTL 长度前缀使参数体字节可确认,pending 有界。
定界写法另有 `tests/test_ctl_heredoc`:对一个含 TAB、空行、`end`/`call`/围栏行和缩进 TAG 的体,
在**每一个**字节前缀上都不产生 `CALL_READY`,完整流上体逐字节还原。
## 实验 B — mini_calc 端到端案例(不可逆发射)
协议见 [fixtures/mini_calc/](../fixtures/mini_calc/) 与 [scripts/e2e_mini_calc.sh](../scripts/e2e_mini_calc.sh)。
协议见 [fixtures/mini_calc/](../fixtures/mini_calc/) 与 [scripts/e2e_fixture.sh](../scripts/e2e_fixture.sh)。
```bash
cd agent
./scripts/e2e_mini_calc.sh --baseline-only # 无 key:证明 fixture 初始 RED
./scripts/e2e_mini_calc.sh # 有 key:agent 修 bug → make test
./scripts/e2e_mini_calc.sh --baseline-only # 无 key:证明 fixture 初始 RED
./scripts/e2e_mini_calc.sh --provider deepseek # 有 key:agent 修 bug → make test
```
### 验收口径
每个夹具有 `TASK.md`(任务书)和 `check.sh`(判定脚本)。基线在一份临时副本上先跑 `check.sh`,必须失败。
| outcome | 含义 |
|---|---|
| `pass` | `make test` 绿 |
| `partial` | 改了 `calc.c` 但测试仍红 |
| `fail` | 未改文件或 agent/API 失败 |
| `baseline_ok_agent_skipped` / `baseline_ok_no_api_key` | 仅验证夹具 |
| `pass` | agent 结束后 `check.sh` 通过 |
| `fail` | agent 正常结束但 `check.sh` 失败 |
| `agent_error` | agent 非零退出(如连续契约错误、API 错误) |
| `timeout` | 超过 `E2E_TIMEOUT`(默认 900 秒) |
| `unexpected_green_fixture` | 夹具一开始就通过(夹具坏了) |
### 论文可引用的叙事
1. Session JSONL 中顺序:assistant `tool_calls` → `role:tool` 结果 →(可选)写文件/shell → 最终文本。
2. 工具仅在确认/原生完整 arguments 后执行(工程桥);**CTL 饥饿实验**仍是形式主证据。
1. Session JSONL 中顺序:assistant 调用 → 工具结果 → 最终文本;只追加。
2. 工具仅在确认(CTL `end`、原生参数完整)后执行;**CTL 饥饿实验**仍是形式主证据。
3. 案例展示「确认后不可撤回的磁盘副作用」与 transcript 只追加。
### 跑分记录(模板)
### 稳定性(M1,DeepSeek `deepseek-chat`,ASAN + UBSan 构建)
| 日期 (UTC) | model | outcome | notes |
|---|---|---|---|
| 2026-09-07 | MiniMax-M2.7 | **pass** | `e2e_mini_calc.sh`;baseline RED → agent 修 `add`/`div_ints` → `make test` 绿;见 `out/result-20260907T131300Z.txt` |
| 日期 (UTC) | 场景 | 结果 |
|---|---|---|
| 2026-09-07 | mini_calc,MiniMax-M2.7 | **pass**(旧版本,`out/result-20260907T131300Z.txt`) |
| 2026-10-04 | mini_calc 连续 10 次,native-eos | **10/10 pass**,ASAN/LSan 无报告 |
| 2026-10-04 | makefile_tab(写含 TAB 的 Makefile;旧版本此处 HTTP 400) | pass |
| 2026-10-04 | big_file(写 5000 字节文件;旧版本长内容被截断) | pass |
| 2026-10-04 | test_fix_test(先跑测试→修→再跑;旧版本第二次 `make test` 被错误去重) | pass,`make test` 实际执行 2 次 |
| 2026-10-04 | mini_calc,`ctl` / `ctl-heredoc` / `native-inc` / `xml` 各 1 次(ASAN) | 4/4 pass |
## 实验 C — 真实模型流上的逐字节截断
```bash
make check # 关注 tests/test_replay_real
```
数据:`tests/fixtures/sse/deepseek-*/NNNN.sse`,是 `CST_RECORD_SSE` 录下的 DeepSeek 原始 SSE 流(同一任务分别用三种协议)。
测试把每条助手消息还原成运行时看到的逻辑流,在**每一个字节**处截断:CTL 流喂给 CTL 解析器;原生流把各调用拼成
`[{"name","arguments"},…]`,分别按 json-whole(整体可解析才执行,即 native-eos)和 json-object(每个对象可解析即执行,即 native-inc)计数。
全部前缀上 S1/S2/S3 成立(未完整调用不可执行;执行过的调用与完整流一致;可执行数单调)。可执行调用数随前缀比例变化:
| 消息 | 协议 | 字节 | 调用 | 25% | 50% | 75% | 90% | 100% | 首个调用可执行于 |
|---|---|---|---|---|---|---|---|---|---|
| heredoc #0 | ctl-heredoc | 193 | 3 | 0 | 1 | 2 | 2 | 3 | 45% |
| heredoc #1 | ctl-heredoc | 223 | 2 | 0 | 0 | 0 | 1 | 2 | 85% |
| ctl #0 | ctl | 74 | 2 | 0 | 0 | 1 | 1 | 2 | 51% |
| native #0 | json-whole | 100 | 2 | 0 | 0 | 0 | 0 | 2 | 100% |
| native #0 | json-object | 100 | 2 | 0 | 0 | 1 | 1 | 2 | — |
| native #1 | json-whole | 108 | 2 | 0 | 0 | 0 | 0 | 2 | 100% |
| native #1 | json-object | 108 | 2 | 0 | 1 | 1 | 1 | 2 | — |
读法:json-whole 在流结束前一个调用都不能执行;CTL 与 json-object 都是前缀单调的,确认点落在每个调用结束处。
CTL 的区别在于 pending 有界(\(\le L_{\mathrm{line}}\)),json-object 的 pending 是整个参数对象。
另外 `ctl` 目录里 #1–#4 是四次长度前缀写错的消息(三次 patch 字节数写大、一次 content 写小,见实验 D),解析器在这些消息的任何前缀上**一次也没有执行**。
`tests/test_sse_decode` 还验证:录制流按 1 字节和 4096 字节分块回放,解码出的文本、调用和参数完全相同(网络分块不影响确认结果)。
## 实验 D — 协议对照矩阵(M3)
```bash
scripts/run_matrix.sh -p "native-eos native-inc ctl ctl-heredoc xml" \
-m "deepseek:deepseek-flash" -f "mini_calc makefile_tab big_file test_fix_test" -n 3 -r
# → out/matrix-*/results.csv(每次运行一行)与 summary.md
```
2026-10-04,DeepSeek `deepseek-chat`,5 协议 × 4 夹具 × 3 次 = 60 次(`out/matrix-m3/`):
| 协议 | n | 通过率 | 步数 | 墙钟 s | 模型时间 s | 工具时间 s | wait_ms | 契约错误/次 | 输入 token | 输出 token |
|---|---|---|---|---|---|---|---|---|---|---|
| native-eos | 12 | 100% | 4.2 | 5.38 | 5.11 | 0.24 | 30 | 0 | 9900 | 1058 |
| native-inc | 12 | 100% | 4.7 | 5.90 | 5.82 | 0.26 | 0 | 0 | 10615 | 1084 |
| ctl | 12 | 58% | 5.8 | 7.26 | 7.09 | 0.16 | 0 | 2.33 | 10574 | 1336 |
| **ctl-heredoc** | 12 | **100%** | **3.7** | **4.48** | 4.27 | 0.24 | 0 | **0** | **5728** | **756** |
| xml | 12 | 92% | 5.7 | 7.09 | 6.84 | 0.19 | 53 | 1.75 | 10468 | 1398 |
`wait_ms` = 调用确认到开始执行的平均间隔。eager 协议恒为 0;流结束后执行的协议要等到整条消息结束。
契约错误分解(全部 12 次运行合计):
| 协议 | 参数写法使用次数 | 契约错误 |
|---|---|---|
| ctl | 长度前缀 33、行内 73 | `unexpected_line`(数少了)20、`body_eof`(数多了)8 |
| ctl-heredoc | 定界 30、行内 29、长度前缀 0 | 0 |
| xml | — | 标签写坏(如 `<minimax:tool_call">`)21 |
结论(可写入论文):
1. **长度前缀对真实模型不可靠**。模型几乎从不数对多行参数的字节数(33 次长度前缀参数里 28 次契约错误),
ctl 只有 58% 通过,失败的运行都是连续契约错误后放弃。CST 的安全性仍然成立——错误帧从未被执行——但可用性差。
2. **定界写法消除了计数负担**,保留同样的确认前沿与有界 pending:ctl-heredoc 100% 通过、0 契约错误,
且步数、墙钟、token 都是五种协议里最少的(输入 token 比 native-eos 少 42%:不发 `tools[]` schema,参数也不需要 JSON 转义)。
3. **eager 执行在这些任务上的时延收益很小**:确认到执行的等待从几十毫秒降到 0;墙钟差异主要来自步数和 token,而非执行时机。
原因是模型通常把耗时工具(make、测试)放在一条消息的最后一个调用,确认之后流几乎立即结束。
4. **反馈让文本协议可恢复**:xml 在加入“标签写坏→契约错误→要求重发”之前为 58%(`out/matrix-m3-old-xml/`),之后为 92%(重复一组为 83%,`out/matrix-m3-xml-replicate/`)。
注意事项:
- 执行在 SSE 回调内同步进行,执行期间客户端暂停读流,所以客户端测得的“流结束”包含了工具时间。
`summary.md` 里的 `overlap_s` 因此是上界(native-inc 下恒等于工具时间),不能当作节省的时间引用。
- 每格只有 3 次,模型输出有随机性;引用通过率时请同时给出 n。
## 与主论文的衔接建议
- **附录 / 扩展节标题示例**:「Tool-loop CST:确认前沿到工具调用」
- 粘贴:theory 表(theory_tool_cst.md)+ 实验 A 表 + 实验 B 一行案例。
- 粘贴:theory 表(theory_tool_cst.md)+ 实验 A/C 表 + 实验 D 主表与结论 1–2。
- 明确声明:主 fan-out / \(\mathcal{A}=0\) 数字仍来自 Sashidown;本笔记只提供 **同构第二实例**。
## 明确不写进定理的内容
- 商业 API 空回复、权限 UX、TUI 观感
- 「已达生产 coding agent」类宣称
- 实验 D 的时延数字(受网络与服务端负载影响)
+67
View File
@@ -43,6 +43,29 @@ end\n
取 \(K = L_{\mathrm{line}}\)(行头)与实现常量 `CTL_MAX_ARG`(单参上限,契约拒绝更大值)。
### 参数的三种写法
| 写法 | 语法 | 何时确认 | 代价 |
|---|---|---|---|
| 长度前缀 | `arg <key> <nbytes>` + 恰好 nbytes 字节 | 每个体字节到达即确认;`end\n` 确认整次调用 | 模型必须**精确数字节**(含换行、UTF-8 多字节) |
| 行内 | `arg <key> <value…>`(值为行尾剩余部分) | 头行 `\n` 即确认 | 只能单行;值不能全是数字(否则视为长度前缀) |
| 定界(heredoc) | `arg <key> <<TAG` … 单独一行 `TAG` | 每个完整体行(`\n` 到达且 ≠ TAG)即确认 | 体不能含独立成行的 TAG;值总以换行结尾;单行 ≤ \(L_{\mathrm{line}}\) |
定界写法的确认规则:
1. 头行 `arg <key> <<TAG\n` 确认 `(key, TAG)`;TAG 须匹配 `[A-Za-z0-9_]{1,32}`,可加单/双引号。
2. 体内每读到一个 `\n`:若该行**逐字节等于** TAG,则参数结束;否则该行(连同换行)确认为体的一部分。行首空白、`end`、`call`、代码围栏在体内都只是普通文本。
3. 未闭合行的 pending \(\le L_{\mathrm{line}}\):它可能成为 TAG,也可能是体行,在 `\n` 前不能确认——与行头同界,所以 \(K = L_{\mathrm{line}}\) 不变。
4. 流在 TAG 前结束 → `heredoc_eof` 契约错误,不执行。最后一行恰为 TAG 且无换行时,EOF 视为该行结束(EOF 充分性)。
定界写法与长度前缀的确认前沿完全相同(都是“参数完整后 `end\n`”),差别只在**谁承担边界信息**:长度前缀要求模型事先给出精确计数(真实模型常数错,见 eval 实验 D),定界写法把边界放到流里,由运行时逐行检测。
### 契约错误的分类
解析器在离开 \(\mathcal{L}_0^{\mathrm{tool}}\) 时给出 `ctl_error_kind_t`,供实验统计:
`bad_call`、`bad_arg`、`unexpected_line`(长度前缀写小了,体后面还有内容)、`body_eof`(写大了,流先结束)、`heredoc_eof`、`line_too_long`、`limit`。
错误信息会带上声明值与实际到达字节数(例如 `declared 495 bytes but only 438 arrived`),运行时把它作为下一条 user 消息反馈给模型重发;已确认并执行的调用不受影响。
## 不可逆发射
实现是确认忠实的,当且仅当:
@@ -55,6 +78,50 @@ end\n
**JSON-buffer**:将模型输出拼接到可 `cJSON_Parse` 的 `{"name","arguments"}`(或 OpenAI `tool_calls` 形)才执行;流中途截断则失败。与 CTL 使用同一截断点集合,只换帧语法。
细分两档(实验 C 都测):
- **json-whole**:整条消息的 `tool_calls` 数组可解析才执行(= 原生接口流结束后执行,`native-eos`)。
- **json-object**:每个调用对象一旦可解析就执行(= `native-inc`)。它也是前缀单调的,但 pending 是“当前调用的全部参数”,不受 \(L_{\mathrm{line}}\) 约束:一个 5KB 的 `content` 字段在最后一个 `"}` 到达前一个字节都不能确认。
## 运行时:五种协议模式
同一个 agent 循环支持五种承载方式(`--protocol`),只换“调用从哪里来、何时执行”,工具、会话、权限完全相同,便于做对照实验。
| 模式 | 请求 | 调用来源 | 执行时刻(不可逆点) |
|---|---|---|---|
| `native-eos` | 发 `tools[]` | SSE `tool_calls` | 流结束后统一执行 |
| `native-inc` | 发 `tools[]` | SSE `tool_calls` | 某 index 的参数完整(出现下一个 index / `finish_reason` / `[DONE]`),且 JSON 可解析 |
| `ctl` | 不发 `tools[]` | 正文 CTL 帧(长度前缀) | `CALL_READY`(`end\n` 确认)即执行,流继续 |
| `ctl-heredoc` | 同上 | 正文 CTL 帧(定界) | 同上 |
| `xml` | 同上 | MiniMax `<minimax:tool_call>` | 流结束后执行 |
文本协议的结果帧:
```text
result <id> exit=<code> <nbytes>\n
<nbytes 字节输出>\n
end\n
```
作为下一条 user 消息发送(模型不得自己写 result 帧)。超过 `CST_TOOL_OUT_CAP` 的输出先按“头 60% + 尾 40%”截断再计算 nbytes,保证帧内字节数精确。
### eager 执行的不可逆语义
- 调用在确认时刻立刻执行(同步,在 SSE 回调内)。执行期间客户端暂停读流,但服务端仍在继续生成,字节堆在 socket 缓冲里,工具返回后再被消费——模型生成与工具执行因此重叠。多个调用严格按确认顺序执行。
- CTL 模式下,**已确认调用之后**若模型继续写普通文本(例如“臆想”工具结果),运行时立即停止读流(客户端 stop,而非错误),只把截至最后一个确认调用的原始文本写入会话。这正是“确认前沿之外的字节不产生副作用”。
- 若同一条消息里,已执行的调用之后出现契约错误:已执行的副作用保留并记录,错误帧之后的内容丢弃,并请模型重发。
- 用户取消(Esc)在执行中的调用上表现为“进程组被终止(exit=130)”,仍然记录,不撤回。
### 实测检验的三条性质(实验 C)
对真实录制流的每一个字节前缀:
- **S1**:未完整的调用永远不可执行;
- **S2**:前缀上已执行的调用与完整流中的同一调用逐字节相同(执行永不被撤回或改写);
- **S3**:可执行调用数随前缀长度单调不减。
## 补丁确认
`apply_patch` 的 patch 文本作为 CTL 的一个 `arg`;应用侧再要求 unified diff 的每个 hunk 完整(`@@` 到下一块或 EOF)才写盘——整 hunk 确认,禁止半截应用。
实现上更进一步:一个 patch 可含多个文件(`---`/`+++` 头,`/dev/null` 表示新建/删除)。所有文件的所有 hunk 先在内存中定位并计算结果,**全部成功才写盘**;任一 hunk 定位失败或有歧义(同一上下文匹配多处且无行号),整个 patch 不产生任何磁盘副作用。hunk 优先在 `@@ -a,b` 给出的行号附近查找,行号不准时退化为搜索,并容忍行尾空白差异。