Files
CST-agent/README.md
T
WHC 60cbce3c5c v0.9.2: files the agent delivers come back as cards, and the saved key is used
A turn could write a report and say "here it is" -- and there was nothing to
open. The engine had no way to hand a finished file to the person, and the
front end had no way to show one. This release adds the whole path: a tool to
deliver, a store that keeps the copy, a card in the transcript, and open /
share through the platform. It also fixes two bugs the phone exposed while it
was being tried there.

deliver_file (engine)
- cst_attachment_t.for_user marks a copy that belongs to the person, not to
  the model; cst_attach_import_artifact() stores any bytes up to
  CST_ARTIFACT_MAX_BYTES (64 MB) with the mime taken from the name's
  extension. Attachments are unchanged (content decides the type, 16 MB).
- It is a read-policy engine tool: path resolves inside the workspace, a
  directory is refused, title (optional) is the display name.
- The wire is what keeps it out of the conversation: parts_add_attachments()
  skips for_user entries without even a "not sent" note, and a turn that only
  delivered a file no longer invents an empty user message. tests/test_artifacts.c
  pins that down end to end, including a session-file round trip.

The card (Qt front end)
- filesText() replaced imagesText(): one line per file -- kind, stored name,
  name, origin, mime, bytes, pixels -- decoded by ImageStrip.qml. A for_user
  entry is a file card (extension badge, name, type and size, Open, Share);
  a picture stays a thumbnail and opens on tap. Both shapes are read, so
  sessions written by an older version still draw.
- AgentCore::openArtifact() / shareArtifact() accept a name only when one of
  the session's own messages carries it, and hand the display name to the
  platform layer for the status line.
- Android opens through the app's FileProvider (content:// + ACTION_VIEW,
  resolveActivity() checked, read permission granted) and shares through the
  share sheet; a desktop uses QDesktopServices / the clipboard.
  CST_APP_NO_OPEN=1 makes --uitest run without opening anything.

Two bugs the device found
- The session writer had its own copy of the attachment JSON and forgot
  `for_user`: the file survived the file, but came back as an attachment --
  no card, and put back in front of the model. It now uses the one writer
  (cst_attachments_to_json()), with the round trip covered by a test.
- engineToolImages() looked the card up with findTool(), which only finds a
  card that is still running -- and the engine reports the result *before*
  the files, so the card was already complete and the files were dropped.
  A live turn therefore showed no card while a replay of the same session
  did. It now matches on the call id.
- A tap that could not work said nothing: ImageStrip.qml's errorText was
  never drawn. The card now carries the reason ("no app on the phone opens
  report.md").

Also
- The saved provider key reaches the engine on every start: AgentCore::open()
  was handed an empty key and the engine never resolves credentials itself
  (only the CLI did), so every start asked for it again. It resolves the same
  order main.c uses (saved key, provider environment, CST_API_KEY) and wipes
  the copy it made; --selftest checks it against a scratch credentials file.
- Android: notification_send reads the Java exception text into its result,
  and keeps failing on the NX789J -- the build() returns null and Qt clears
  the exception before C++ can read it, so the call has to be wrapped in Java
  next (docs/android.md says so).
- --uitest grew the delivered-file case (card, Open, Share, a forged name
  refused), the live order of result-then-files, and the startup row count.

Version 0.9.2.
2026-10-11 16:34:47 +08:00

32 KiB
Raw Blame History

CST-Agent

C11 OpenCode-like carrier that extends paper CST (confirmation frontier / irreversible emission) from Markdown→HTML to the agent tool loop.

See docs/theory_tool_cst.md. 评测/反哺论文素材:docs/eval_tool_cst.md。

能不能用来工作?

可以:有监督的本地改动 / 探索仓库(TUI + 13 个内置工具 + 带 diff 的权限确认 + /undo)。
不宜:无人值守长任务或当 CI 主力(网关偶发空答;headless 模式默认自动批准)。

5 分钟试用

# Linux / WSL / macOS 一键:克隆到 ~/CST-agent、编译、装到 ~/.local/bin
curl -fsSL https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/bootstrap_linux.sh | bash

# 已有仓库目录内(装到别处:CST_AGENT_PREFIX=/usr/local)
./scripts/bootstrap_linux.sh .

# 以后升级:拉取最新代码、重新编译并安装(本地有未提交改动时跳过 pull)
cst-agent-update
cst-agent --version

# 在项目目录里直接启动:首次会让你选 provider 并输入 API key(遮罩输入,按 provider 保存)
cd ~/my-project && cst-agent

不想编译:Release 里有静态链接的 Linux 单文件 (cst-agent-VERSION-linux-{x86_64,x86,aarch64}.tar.gz,musl,任何发行版都能直接跑),解压后把 cst-agent 放进 PATH 即可;每个文件旁边有 .sha256。

Windows 10 1903+(PowerShell,不需要管理员):

irm https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/install_windows.ps1 | iex
# 新开终端后:cd 到项目目录运行 cst-agent;升级用 cst-agent-update

也可以下载 Release 里的安装包 cst-agent-VERSION-windows-x86_64-setup.exe(按用户安装、自动加 PATH、 开始菜单有 WebUI 入口),或便携版 zip。详见 docs/README-windows.md。

一键脚本可以重复运行:已有仓库会 git pull --ff-only,代码有更新时干净重编,然后重新安装; 同时装一个 cst-agent-update 命令(等同重跑脚本)。NixOS 上会自动进 nix-shell 编译。

之后再启动会沿用上次的 provider、模型和推理强度(low / medium / high / max),不需要任何参数或环境变量。 界面里用 /provider 切换(没保存过 key 的会当场询问),/model、/effort 切换模型和推理强度,/key 更换 key。

完整环境变量说明:docs/env_openai.md。

平台 一键脚本
Linux scripts/bootstrap_linux.sh
Termux scripts/bootstrap_termux.sh
Windows 安装 / 升级 scripts/install_windows.ps1(从 Release 安装),之后 cst-agent-update
Linux 静态发行包(musl 交叉) scripts/package_linux.sh x86_64|x86|aarch64 → dist/*.tar.gz
Windows 交叉构建(在 Linux 上) scripts/bootstrap_windows.sh → dist/windows-x86_64/、zip 和安装包
Windows 原生构建(MSYS2 UCRT64) scripts/bootstrap_msys2.sh → dist/windows-x86_64/

迷你验收工程(论文案例)

故意带 bug 的小计算器:fixtures/mini_calc/(任务书 TASK.md)。

./scripts/e2e_mini_calc.sh --baseline-only   # 无 API:证明初始测试 RED
./scripts/e2e_mini_calc.sh --provider deepseek --protocol ctl   # 有 key

更多夹具(每个都有 TASK.md + check.sh):makefile_tab(写含 TAB 的 Makefile)、 big_file(写 5000 字节文件)、test_fix_test(先跑测试→修→再跑测试)、 multi_file_refactor(跨文件改名 + 修 bug,适合 edit_file / grep)、 ask_or_todo(必须用 todo_write 规划并全部完成;无人回答 ask_user)。 后两个需要 --tools default(脚本默认 minimal,即论文用的 4 个工具)。

scripts/e2e_fixture.sh makefile_tab --provider deepseek --protocol native-inc
scripts/e2e_fixture.sh multi_file_refactor --provider deepseek --tools default
# 批量实验:协议 x 模型 x 夹具 x N 次 → results.csv + summary.md
scripts/run_matrix.sh -p "native-eos native-inc ctl ctl-heredoc xml" \
  -m "deepseek:deepseek-flash deepseek:deepseek-v4-pro" -n 3 -r

口径:pass / fail / agent_error / timeout — 见 docs/eval_tool_cst.md。

Build

Make(Linux 默认)

cd CST-agent
make            # 依赖 pkg-config 找 libcurl / ncursesw(找不到时退回 ncurses)
make check      # 单元测试 + 截断实验(含真实录制流)
make asan       # AddressSanitizer + UBSan 构建并跑全部测试
make CC=tcc AR=ar check   # TinyCC:整套构建 + 测试十几秒(CI 的校验就是这条)
make install PREFIX=$HOME/.local   # 或 sudo make install(默认 /usr/local)

NixOS:nix-shell(仓库自带 shell.nix)后再 make。 macOS:brew install curl ncurses pkg-config 后直接 make(Makefile 会找 Homebrew 的 keg-only 库)。 各平台细节与验收清单见 docs/compat.md。

CMake(推荐跨编译器 / 交叉)

# GCC
cmake -B build-gcc -DCMAKE_C_COMPILER=gcc -DCST_BUILD_TUI=ON
cmake --build build-gcc && ctest --test-dir build-gcc

# Clang
cmake -B build-clang -DCMAKE_C_COMPILER=clang -DCST_BUILD_TUI=ON
cmake --build build-clang

# Headless only(无 ncurses)
cmake -B build-hd -DCST_BUILD_TUI=OFF && cmake --build build-hd

# Windows:用一键脚本(见下方“Windows 发行包”),会先编译静态依赖
./scripts/bootstrap_windows.sh

依赖:gcc、clang 或 tcc(0.9.27 mob 以上)、libcurl、TUI 需 ncursesw(Windows 版用打过补丁的 ncurses 6.6,脚本自动编译)。 Sashidown(vendor/sashidown)与 cJSON(vendor/cjson,MIT)已随仓库提供。
API Key:TUI 里输入并按 provider 保存到配置目录的 credentials(权限 0600);/key 更换,--ask-key 在命令行输入。 --api-key 只用于本次进程、不落盘;DEEPSEEK_API_KEY 等环境变量和 CST_API_KEY 仅作为没有保存 key 时的兜底(方便 CI)。

平台支持矩阵

OS Arch 编译器 Headless TUI Shell
Linux(glibc / musl) x86_64 / x86 / aarch64 GCC, Clang, TinyCC yes ncursesw /bin/sh 或旁路 busybox
WSL2 x86_64 同 Linux yes ncursesw /bin/sh(工作区建议放在 Linux 文件系统内)
macOS arm64 / x86_64 Apple Clang yes ncurses(Homebrew) /bin/sh
Android Termux aarch64 / … Clang (Termux) yes ncurses(Termux 包) $PREFIX/bin/sh;可旁路 busybox
Windows 10 1903+ x86_64 MinGW-w64 GCC(UCRT) yes ncurses 6.6(Windows 控制台) 捆绑 busybox-w32

WebUI(--web)

同一个二进制内置一个 HTTP + SSE 前端:cst-agent --web(默认 http://127.0.0.1:8787/)。 它和 TUI 是同一引擎的两个前端,浏览器里能流式看回答、点审批、切会话、贴图。 协议是一份稳定的 JSON + SSE 接口,供以后的专用客户端复用。详见 docs/webui.md。

cst-agent --web                    # 本机浏览器打开 http://127.0.0.1:8787/
cst-agent --web --web-port 0       # 端口交给系统,启动日志里给 URL
cst-agent --web --web-dev ./web    # 开发前端:直接读目录,改完刷新
cst-agent --web --web-host 0.0.0.0 --web-token 口令   # 手机/局域网访问
sh scripts/e2e_webui.sh            # 用录制流跑完整轮次,不需要 API key

不带 TUI 的构建也可以开 WebUI(cmake -DCST_BUILD_WEBUI=ON -DCST_BUILD_TUI=OFF); -DCST_BUILD_WEBUI=OFF 时 --web 只打印一条提示。

Termux

# 一键:装依赖、克隆到 ~/CST-agent、编译、安装到 $PREFIX/bin
curl -fsSL https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/bootstrap_termux.sh | bash

# 以后升级(重跑上面那条也一样)
cst-agent-update

# 或手动
pkg install git clang make pkg-config binutils libcurl ncurses
git clone https://git.whcself.xyz/WHC/CST-agent.git && cd CST-agent
make && make install      # 装到 Termux 的 $PREFIX/bin,之后任意目录运行 cst-agent
# 可选:旁路 Unix busybox
# ./scripts/build_third_party_mingw.sh busybox-unix

环境:PREFIX 自动用于找 sh;CST_EXE_DIR / argv[0] 用于定位旁路 busybox(/proc/self/exe 受限时)。 临时目录:依次试 $TMPDIR、$PREFIX/tmp、/tmp、/var/tmp、~/.cache/cst-agent/tmp,用第一个真能建文件的, 并写回 TMPDIR 给 shell 子进程和沙箱用(Termux 的 /tmp 常常不存在或被 SELinux 拒绝)。

Linux 静态发行包

scripts/package_linux.sh x86_64     # 或 x86 / aarch64;CST_LINUX_CHECK=1 顺便跑 make check

用 Bootlin 的 musl 交叉 gcc(固定版本、校验 sha256)静态编译 mbedTLS、curl 8.22(只开 HTTP/HTTPS)和宽字符 ncurses, 再以 STATIC=1 和 -Werror 编出 cst-agent,产物是 dist/cst-agent-VERSION-linux-ARCH.tar.gz(+ .sha256)。 宿主需要 cmake ninja make perl 以及 tic/infocmp 和终端条目(Debian:ncurses-bin ncurses-term)。 ncurses 会去常见目录找 terminfo,找不到时用编进去的 xterm-256color、xterm、screen-256color、tmux-256color、linux、vt100。 CA 证书:CST_CA_BUNDLE → SSL_CERT_FILE → 各发行版的常见位置(Debian/Ubuntu、Fedora/RHEL、openSUSE、Alpine)。

Windows 发行包

见 docs/README-windows.md(安装、编码、沙箱、构建、已知限制)。

./scripts/bootstrap_windows.sh   # Linux 交叉编译;Windows 上在 MSYS2 UCRT64 里用 scripts/bootstrap_msys2.sh

产物:dist/windows-x86_64/(静态链接的 cst-agent.exe + busybox.exe,无其他 DLL)、 dist/cst-agent-VERSION-windows-x86_64.zip,以及装了 makensis 时的安装包 dist/cst-agent-VERSION-windows-x86_64-setup.exe。

CI 与 Release

CI 用 TinyCC 校验(构建、make check、WebUI 端到端),通过后用 gcc(-Werror)出发行包: Linux x86_64 / x86 / aarch64 静态包(x86 两种跑 make check,aarch64 用 qemu 冒烟), Windows x86_64 的 zip 和安装包(Debian 的 mingw-w64 UCRT gcc + NSIS)。 推送到 main 或 v* tag 时每个任务把自己的产物上传到 Gitea Release vVERSION(同名资源会被替换); 仓库可设 RELEASE_TOKEN(write:repository),否则用任务自带 token。 本地:GITEA_TOKEN=... scripts/release.sh dist/cst-agent-*.tar.gz dist/cst-agent-*.zip dist/cst-agent-*-setup.exe。

环境变量:CST_SHELL、CST_EXE_DIR、CST_AUTO_APPROVE 等。

Defaults (API)

--provider(或 CST_PROVIDER)选择预设;显式的 --api-base / --model / 环境变量优先。

provider API Base 默认模型 Key 变量
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
teamorouter https://api.teamorouter.com/v1 登录后自动取 /models 第一个(/model 可换) TEAMOROUTER_API_KEY
用途 来源(优先级从高到低)
Provider --provider → CST_PROVIDER → 上次使用 → minimax
Key --api-key → 已保存的 key(credentials)→ provider 的 Key 变量 → CST_API_KEY → TUI 询问
Base(含 /v1) --api-base → CST_API_BASE → OPENAI_BASE_URL → OPENAI_API_BASE → 预设
Model --model → CST_MODEL → OPENAI_MODEL → 该 provider 上次使用的模型 → 预设
推理强度 --effort → CST_EFFORT → 上次使用 → medium

“上次使用”记在配置目录的 settings.json(TUI 里切换时更新;headless 运行不改它)。 推理强度以 reasoning_effort 发送给 DeepSeek 和 OpenAI 推理模型(o 系列、gpt-5,max 映射为 high),其他模型不发送。

详见 docs/env_openai.md。

变量 作用(默认)
CST_PROTOCOL / --protocol 工具调用协议(native-eos)
CST_TOOLS / --tools 工具集:default(全部)/ minimal(论文 4 件套)
CST_MODE / --mode 工作模式:coding(默认,代码口径)/ general(通用助手口径)
CST_EFFORT / --effort 推理强度 low / medium / high / max
CST_CONFIRM / --confirm 哪些类别要确认:shell,write,net(默认)或 none
CST_MAX_STEPS / --max-steps 每个用户回合最多调用模型几次(30)
CST_TOOL_OUT_CAP 单个工具输出发给模型的字节上限(16384,保留头 60% + 尾 40%)
CST_CTX_WINDOW 发给模型的最近非 system 消息条数(80)
CST_CTX_TOKENS / CST_AUTO_COMPACT=0 覆盖模型上下文窗口大小 / 关闭自动压缩(用到 75% 时自动 /compact)
CST_NO_PROJECT_NOTES=1 不把工作区的 AGENTS.md 注入 system prompt
NO_COLOR / CST_ASCII=1 关闭颜色 / 强制 ASCII 图形(非 UTF-8 终端自动退化)
CST_SHELL_TIMEOUT run_shell 超时秒数(默认不限),设了才在超时后整组杀进程,exit=124
CST_METRICS=0 / CST_METRICS_PATH 关闭 / 指定计时埋点 JSONL(默认 .agent/metrics/<session>.jsonl)
CST_RECORD_SSE=DIR / CST_REPLAY_SSE=DIR 录制 / 回放原始 SSE 流(NNNN.sse + NNNN.req.json)
CST_APPROVAL=ask|auto|yolo / CST_SANDBOX=off|landlock|seccomp 审批模式 / 关闭沙箱(回落到只读白名单规则)或只用指定沙箱
CST_CA_BUNDLE HTTPS 用的 CA 证书文件(默认:SSL_CERT_FILE、libcurl 自带路径、常见发行版位置;Windows 用系统证书库)
CST_MODELS、CST_AUTO_APPROVE=1、CST_SHELL、CST_EXE_DIR 模型列表、自动批准(= yolo)、shell、旁路 busybox

Run

# TUI(工作目录 = 当前目录;Assistant Markdown 经 Sashidown CST 渲染)
cst-agent

# Local CTL demo (no API)
cst-agent --demo-ctl

# 在命令行输入并保存当前 provider 的 key(不回显)
cst-agent --ask-key --provider deepseek

# Headless(默认 auto-approve shell/写文件)
cst-agent --headless --prompt 'List files'

会话相关命令行参数:

参数 作用
-c / --continue 打开上次的会话(默认行为)
-N / --new 开新会话
-R ID / --resume ID 打开指定会话(完整 id 或唯一前缀)
-L / --list-sessions 列出会话(时间、条数、标题)后退出
-A MODE / --approval MODE 审批模式 ask / auto(默认)/ yolo,见下文
-y / --yolo 等同 --approval yolo,只对本次运行有效
-C LIST / --confirm LIST 需要确认的类别:shell,write,net 的任意组合或 none
-T PROFILE / --tools PROFILE default 或 minimal
-M MODE / --mode MODE 工作模式 coding(默认)/ general,也读 CST_MODE,并记进 settings.json
-P NAME / -m NAME / -e LEVEL 本次使用的 provider / 模型 / 推理强度(不传则用上次的)
-a / --ask-key 在命令行输入并保存当前 provider 的 key

工作目录默认就是当前目录(-w DIR 可指定别的目录)。

会话落盘:<workspace>/.agent/sessions/<id>.jsonl + CURRENT;.agent/ 自带 .gitignore,不会被误提交。

内置工具

工具 类别 说明
read_file 读 支持 offset / limit 分段读和行号
list_dir 读 目录树(depth),遵守 .gitignore,跳过 .git / node_modules 等
glob 读 按通配符找文件(支持 **)
grep 读 正则搜索内容,可限定路径 / glob、忽略大小写、带上下文
write_file 写 整文件写入(自动建父目录)
edit_file 写 精确片段替换(必须唯一,或 replace_all)
apply_patch 写 应用 unified diff,多文件原子生效
delete_file / move_file 写 删除 / 移动(重命名)文件
run_shell shell 非交互命令,超时整组进程被杀,Esc 可中断
web_fetch 网络 抓取 URL,HTML 转纯文本
clipboard_read 读 读系统剪贴板里的文本(max_bytes 默认 64 KiB,超出截断并说明)
clipboard_write 写 把文本放进系统剪贴板(按写类权限确认;本机走 pbcopy / wl-copy / xclip / Win32 API)
todo_write 元 维护任务清单(TUI 右侧面板,Ctrl+T 开关)
ask_user 询问 向用户提问(可给选项);headless 下返回错误让模型自行决定
skill 元 加载一个 skill 的说明(SKILL.md)或其中的文件;有 skill 时才出现
task 子 agent 把独立任务交给子 agent(general / explore / plan / 自定义角色),只返回最终报告
deliver_file 交付 把工作区里的成品文件复制进会话库并交给用户(任意字节,≤64 MB):前端画成文件卡,可打开 / 分享;带 for_user 标记,永不进模型上下文

插件工具和 MCP 工具(mcp__<server>__<tool>)在运行时注册,和内置工具走同一套审批与 /tools 开关。 同一步里的多个只读调用(读文件、搜索、只读子 agent)会并行执行(最多 4 个)。

--tools minimal 只保留 read_file / run_shell / write_file / apply_patch(论文实验口径)。 TUI 中 /tools off NAME、/tools on NAME 可临时开关单个工具。

工作模式(coding / general)

--mode general(或 TUI 里 /mode general、WebUI 运行设置里的「工作模式」、CST_MODE=general)把默认口径 从"代码助手"换成"通用助手":工作流从改代码/跑测试换成"先弄清要什么、再动手、给出处和不确定的地方", 工具说明从"仓库"改成"文件与数据",并说明 web_fetch 可以拿外部资料;/status 第一行显示当前模式, 切换后 history 里会留一条系统提示。默认仍是 coding,选择记在 settings.json; --tools minimal(论文口径)的提示词按字节冻结,不受模式影响。

剪贴板与文档附件

  • 剪贴板:clipboard_read / clipboard_write 两个工具(写剪贴板是不可逆发射,要确认); TUI 里 /attach paste 在剪贴板里没有图片时,把剪贴板的文本放进输入框(丢掉 CR 与控制字节,尊重输入长度上限)。
  • 附件(/attach <path>、拖入终端、剪贴板图片都算):图片按原样存进会话目录,发给模型时是 image part; PDF 在摄取时就抽出文本层(零依赖解析器:Flate / ASCIIHex / ASCII85、对象流、CID 字体 ToUnicode、 内联图像跳过),存的是文本、mime 仍是 application/pdf;其余文件必须是 UTF-8 文本。 文档以 text part 内联发出,所以不受 CST_NO_IMAGES 或非视觉模型影响;图片照旧会被换成一行"未发送"说明。 上限:单个附件 ≤16 MB(文档文本 ≤4 MiB),每条消息最多 8 个。 加密 PDF、扫描件(无文本层)会被明确拒绝并说明原因; view_image 仍只收图片,文本/PDF 走 /attach。

权限、撤销与项目说明

  • 审批模式(Shift+Tab 循环切换,/approval,--approval,CST_APPROVAL):

    模式 写文件 run_shell web_fetch
    ask 询问 询问 询问
    auto(默认) 直接执行 有沙箱 → 在沙箱里直接执行;没有沙箱 → 只读白名单命令直接执行,其余询问 询问
    yolo 直接执行 直接执行(不进沙箱) 直接执行

    已保存的规则(a / p)优先:匹配的命令不进沙箱直接运行。 ask / auto 的选择会记在 settings.json;yolo 只在本次运行有效。headless 行为不变(全部自动)。

  • 沙箱(C 实现,src/sandbox_exec.c):

    • Linux:Landlock(内核 5.13+,直接 syscall,无需 libseccomp)。子进程只能写工作区、临时目录、/dev/null、/dev/tty; 读、执行和联网不受限。
    • Linux 没有 Landlock 时(Android / Termux、WSL、老发行版;内核 5.5+,x86_64 / aarch64):seccomp user-notify (界面显示 auto · seccomp)。子进程的写类系统调用(带写标志的 open、创建 / 删除 / 改名 / 链接 / chmod 等)先交给 agent 里的监督线程,按真实路径判断是否在工作区、临时目录或 /dev/null 等之内,不在就返回 EACCES。联网不受限。 读、执行和已打开文件的写入不检查。它防的是误操作而不是恶意程序:检查之后内核会再读一次路径,多线程程序可以趁机换掉。 探测还会确认能读直接子进程的内存;ptrace_scope=2 且无 CAP_SYS_PTRACE 时 seccomp 不会启用。 比 shell 活得久的后台进程:桌面发行版开着 Yama(ptrace_scope ≥ 1)时它们的受检写入会被拒;命令结束后一律失败。
    • macOS:sandbox_init profile,同样只放行工作区和临时目录写入,不限制联网。
    • Windows 10 1903+:Low 完整性级别的受限令牌 + Job Object(界面显示 auto · lowil)。第一次在工作区运行时 给它打上 Low 标签,之后子进程只能写工作区和沙箱临时目录,读不到凭据文件;不限制联网。 /sandbox 查看、/sandbox reset-labels 撤销标签,详见 docs/README-windows.md。
    • 更老的内核:没有沙箱,自动回落到规则判断(界面显示 auto · rules)。
    • CST_SANDBOX=off 强制关闭沙箱;CST_SANDBOX=landlock / seccomp / lowil 只用指定的那种。
    • 沙箱里的命令如果像是被拦了(Permission denied / 权限不够 / Read-only file system 等),会弹窗问 “Rerun outside the sandbox?”:y 在沙箱外重跑一次 · p 保存命令前缀规则并重跑 · n 保留沙箱结果(模型会收到说明)。
  • 只读白名单(无沙箱时 auto 用):ls cat head tail wc grep rg find pwd echo which file stat du tree 和 git status/diff/log/show/branch;可以用 | && || ; 串联,但每段都必须在白名单里。 find -exec/-delete、rg --pre、git branch NAME、> 重定向(2>&1、>/dev/null 除外)、$(…)、反引号、 <(…)、sudo、rm 一律不算安全。

  • 确认弹窗会显示命令或写入的 diff: y 允许一次 · a 本工具总是允许 · p 总是允许这个命令前缀(如 make、git status)· n / Esc 拒绝。 a / p 写入 <workspace>/.agent/permissions.json,/permissions 查看,/permissions reset 清空。 sudo、rm、解释器(python3 -c …)等危险前缀不会被建议成规则。

  • 撤销:每轮写文件前自动快照到 .agent/undo/,/undo 恢复上一轮改动的全部文件(新建的文件会被删除)。

  • AGENTS.md:工作区根目录的 AGENTS.md 会注入 system prompt;/init 让 agent 自己生成一份。

  • 自动压缩:上一轮 prompt 用到模型上下文 75% 时,下一轮开始前自动总结历史(CST_AUTO_COMPACT=0 关闭)。

扩展:Skills、插件、MCP、子 agent

--tools minimal 下这些都不加载(论文口径保持不变)。配置写法与示例见 docs/plugins.md。

  • Skills:<dir>/SKILL.md(frontmatter name + description),放在工作区 .agent/skills/、.claude/skills/ 或配置目录 skills/。system prompt 只放名字和一句描述,模型需要时调用 skill(name) 读全文; /skills 浏览,/skill NAME 任务 直接带着某个 skill 开始一轮。
  • 插件:plugins/<name>/plugin.json 声明外部命令工具(参数以 JSON 写到 stdin,stdout 即结果,perm 决定审批, 默认 60 s 超时),也可以附带 skills 目录和 MCP 服务器。
  • MCP:mcp.json({"mcpServers": {...}},与 Claude/Cursor 格式相同)里的 stdio 服务器在后台启动, 工具注册为 mcp__<server>__<tool>,默认按网络工具审批(autoApprove 可放行)。/mcp 看状态,侧栏显示连接数。
  • 工作区信任:工作区里的 .agent/plugins/ 和 .agent/mcp.json 会执行命令,默认不加载; /plugins trust 后才生效(记在 settings.json)。配置目录(~/.config/cst-agent/)下的始终加载。
  • 子 agent:task(description, prompt, agent?) 新开一个独立上下文跑完一轮,把最终报告交回主 agent。 内置 general(除 task 外全部工具)、explore(只读,可并行)、plan(只读,输出计划); .agent/agents/*.md 或配置目录 agents/*.md 可自定义角色(frontmatter name / description / model / tools / max_steps / readonly,正文是角色说明)。子 agent 不能再开子 agent;Esc 会一并取消;它的审批弹窗标 [角色名]; 文件改动并入当前轮,/undo 一起撤销。转录在 .agent/sessions/<会话>/sub-N.jsonl,/agents 查看。

TUI 快捷键

默认 leader 是 Ctrl+X:先按 Ctrl+X,再按后面的字母。/keys(或 F1、Ctrl+X h)列出当前全部绑定。

按键 说明
Enter 发送;busy 时入队
Ctrl+N / Alt+Enter 换行(粘贴的多行文本保持原样,不会被当成发送)
Shift+Enter / Ctrl+Enter 换行;需要支持 kitty 键盘协议的终端(kitty、WezTerm、foot、Ghostty、WHC Shell 等),启动时自动探测
← → Home End Ctrl+A/E Ctrl+B/F Alt+B/F Ctrl/Alt+← → 移动光标(按字 / 按词,支持中文宽字符)
Ctrl+W / Alt+Backspace / Ctrl+Backspace / Ctrl+Delete 向前删词 / 向后删词(Ctrl+Backspace 需要 kitty 键盘协议)
Ctrl+U / Ctrl+K / Delete 删到行首 / 删到行尾 / 删后一个字符
↑ / ↓ 提示历史(多行输入时先在行间移动)
Tab 补全 /命令 和 @文件;没有可补全时切换 Build / Plan(Plan 只开放只读工具)
Shift+Tab 循环切换审批模式 ask → auto → yolo
Ctrl+P 命令面板(输入过滤,↑↓ / 鼠标选择)
Ctrl+O / Ctrl+R 展开/折叠最近的 tool 输出 / 最近的 Thought
Ctrl+T / Ctrl+L 开关任务面板 / 重绘
PgUp / PgDn、Alt+↑ / Alt+↓、Ctrl+Home / Ctrl+End 半页滚动、单行滚动、跳到开头 / 最新
Ctrl+X n / l 新会话 / 会话列表
Ctrl+X m / p / e 模型 / provider / 推理强度
Ctrl+X u / r / c 撤销上一轮改动 / 重问 / 压缩上下文
Ctrl+X y / x 复制上一条回复 / 导出记录
Ctrl+X t / d / s 全部 Thought 默认展开或折叠 / 全部 tool 输出 / 侧栏
Ctrl+X a / k / g 子 agent 面板 / skills / 插件
Ctrl+X h、F1 / Ctrl+X q 快捷键列表 / 退出
鼠标点击 展开/折叠 tool 卡片和 Thought、在输入框里定位光标、点模式行切换 Build/Plan、在选择框里选中
鼠标拖动 / 双击 / 三击 在对话区选中文字(按字符 / 词 / 行),松开即通过 OSC 52 复制到剪贴板;拖到上下边缘会自动滚动。Shift+拖动或 /mouse 关闭后用终端原生选择
Esc 取消当前 turn(不会退出)
Ctrl+C 两次 / 空输入 Ctrl+D 退出

Thought(推理过程)默认折叠成一行“Thought · 3.2s”,生成中显示实时计时;/thinking on 改为默认展开。

自定义按键:~/.config/cst-agent/keybinds.json,键是动作名(/keys 里能看到),值是一个或多个按键, "none" 解除绑定:

{
  "leader": "ctrl+x",
  "new_session": ["<leader> n", "f2"],
  "toggle_thought": "ctrl+g",
  "todo": "none"
}

按键写法:ctrl+x、alt+b、shift+tab、f1…f12、pgup、home、<leader> n。 写错的条目会被跳过并在 /keys 里提示,其余照常生效。

斜杠命令

输入 / 弹出补全;唯一前缀即可(如 /st → /status)。标 * 的命令在模型运行中也能用。

命令 说明
/help * 命令与按键
/new、/session [id]、/rename <标题> *、/delete [id] 新建 / 选择切换 / 命名 / 删除会话
/provider [name]、/model [name]、/effort [level] 切换 provider(缺 key 时当场输入并用 /models 校验)、模型(列表优先显示 API 实际返回的模型,标 (api))、推理强度;都会记住
/key 输入或更换当前 provider 的 API key(遮罩输入并保存)
/protocol [name] 切换工具调用协议
/mode [coding|general] 工作模式(通用助手 / 代码助手口径)
/tools [on|off NAME|minimal|default] 查看或开关工具
/approval [ask|auto|yolo] *、/yolo * 审批模式;本次开关 yolo
/permissions [reset] *、/max-steps [N] * 权限规则、每轮步数
/sidebar *、/mouse * 开关右侧边栏(≥120 列时显示)、开关鼠标
/status *、/todo * 会话 / 模型 / 上下文占比 / 设置;任务清单
/undo、/retry 撤销上一轮文件改动;丢弃上一条回复重问
/diff [path] * git status + git diff
/compact 手动压缩上下文
/export [file] *、/copy * 导出 Markdown 记录;复制上一条回复(OSC 52,需终端支持)
/attach [path|paste|list|clear] 给下一条消息带附件:图片、PDF(自动取文本层)、文本文件;paste 优先取剪贴板图片,没有就把剪贴板文本放进输入框
/web * 后台起一个 WebUI 进程(同 workspace、新会话,日志写到 .agent/webui.log)
/init 让 agent 写 AGENTS.md
/details *、/thinking [on|off]、/keys * 展开全部 tool 输出;Thought 默认展开/折叠;快捷键列表
/skills [reload|list]、/skill <name> [任务] 浏览 skills;带着某个 skill 开始一轮
/plugins [trust|untrust|reload]、/mcp [reload] 插件与工作区信任;MCP 服务器状态
/agents 子 agent 角色与本会话的子 agent 记录(可查看转录)
/quit * 退出

协议

请求统一走 OpenAI 兼容 POST …/chat/completions(SSE)。工具调用的承载方式由 --protocol 决定:

协议 承载 何时执行
native-eos(默认) 原生 tools[] / SSE tool_calls 整个流结束后(基线)
native-inc 同上 某个 index 的参数完整(下一个 index 开始或 finish)即执行
ctl 正文中的 CTL 帧,长度前缀参数 end 行一被确认即执行,流仍在继续
ctl-heredoc 正文中的 CTL 帧,<<TAG 定界参数 同上
xml MiniMax <minimax:tool_call> 文本 流结束后

文本协议(ctl / ctl-heredoc / xml)不发送 tools[],工具结果以 result <id> exit=<n> <nbytes> … end 帧放在下一条 user 消息里。 同一会话可以随时切换协议:历史中的调用会按当前协议重新渲染。

CTL tool language

call <id> <name>
arg <key> <nbytes>
<raw bytes exactly nbytes>
arg <key> <<TAG
<lines, verbatim>
TAG
arg <key> short inline value
end

Execution happens only on confirmed end (CTL_EV_CALL_READY); no prefix of a call is ever executable. See docs/theory_tool_cst.md.

Starvation experiment

make check
./scripts/replay_starvation.sh
./tests/test_replay_real      # 真实 DeepSeek 录制流上的逐字节截断(实验 C)

CTL vs JSON-buffer 截断数字写入 docs/eval_tool_cst.md 实验 A / C;与 mini_calc e2e 一起作为 Tool-loop CST 附录素材(不替代 SD 主论文 Markdown→HTML 实验)。