refactor: archive GUI clients; keep minimal TUI with hooks/API
ci / tcc-check (push) Successful in 1m8s
ci / linux (aarch64, 0) (push) Failing after 1h0m3s
ci / linux (x86, 1) (push) Failing after 1h0m3s
ci / linux (x86_64, 1) (push) Failing after 1h0m2s
ci / windows (push) Failing after 50s

Move Qt/Tauri/full Web SPA under archive/, ship hermit as CLI/TUI plus
libcstcore and cst-webui/1 HTTP+SSE (turn lifecycle hooks, capabilities).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-30 22:49:23 +08:00
co-authored by Cursor
parent ea38247161
commit 5538ed3412
184 changed files with 14098 additions and 1858 deletions
+6 -3
View File
@@ -9,9 +9,12 @@
| [env_openai.md](env_openai.md) | Provider、模型、API Key、环境变量(`HERMIT_*` / `CST_*`) |
| [parliament.md](parliament.md) | 多设备议会 |
| [workers.md](workers.md) | 后台任务、worker、job 完成报告 |
| [webui.md](webui.md) | 内置 HTTP + SSE WebUI |
| [plugins.md](plugins.md) | Skills、插件、MCP |
| [webui.md](webui.md) | 引擎 HTTP + SSE API(`cst-webui/1`) |
| [webui-schema.md](webui-schema.md) | SSE / state 字段摘要 |
| [plugins.md](plugins.md) | Skills、插件、MCP(可替换扩展;非 core 工具) |
| [desktop.md](desktop.md) | 归档桌面 GUI 指针 |
| [compat.md](compat.md) | 平台兼容 |
| [README-windows.md](README-windows.md) | Windows |
| [android.md](android.md) | Android / APK |
| [android.md](android.md) | Android / APK(归档 Qt 路径) |
| [archive/](archive/) | 历史设计笔记(非产品文档) |
| [../archive/](../archive/) | 冻结客户端(qt-app / desktop-tauri / web-spa) |
+13 -12
View File
@@ -1,10 +1,11 @@
# CST-agent on Android
The engine runs on the phone as a plain bionic binary -- no Termux, no proot,
no second process for the UI to talk to. It is the same C core the CLI, the
TUI and the WebUI use, cross-compiled with the NDK; on the phone the Qt front
end (app/) will link `libcstcore.a` in-process and register its own platform
tools under the owner `app:android`.
no second process for the UI to talk to. It is the same C core the CLI/TUI and
`--web` API use, cross-compiled with the NDK. The archived Qt front end
(`archive/qt-app/`) can link `libcstcore.a` in-process and register platform
tools under the owner `app:android`. Prefer a remote client on `cst-webui/1`
for new work.
Two scripts cover the engine side:
@@ -163,7 +164,7 @@ have, so the script rewrites the shebang of the build-tools wrappers.
provider's environment variable, `CST_API_KEY`), so the phone answers without
being asked for the key again. Rotation keeps the session; the sidebar
appears in landscape.
* Platform tools behind `app:android` (`app/src/platform/android/AndroidTools.cpp`):
* Platform tools behind `app:android` (`archive/qt-app/src/platform/android/AndroidTools.cpp`):
`notification_send`, `clipboard_get` / `clipboard_set` under the engine's
own tool names and `share` (`ACTION_SEND` + chooser) all work on the device.
The file
@@ -176,20 +177,20 @@ have, so the script rewrites the shebang of the build-tools wrappers.
the tool could only say "failed: no exception detail", because Qt clears a
Java exception before C++ gets to look at it. The one step that has to fail
visibly (a builder, a `PendingIntent`, `build()`, `notify()`) now lives in
`app/android/src/xyz/whcself/cstagent/Notifier.java`, which catches the
`archive/qt-app/android/src/xyz/whcself/cstagent/Notifier.java`, which catches the
throwable and hands the text back for the tool result; the device then
reported `notification sent` and the shade had it. androiddeployqt copies
`app/android/` with the rest of the package source and Qt's Gradle template
`archive/qt-app/android/` with the rest of the package source and Qt's Gradle template
puts its `src/` on `java.srcDirs`, so **a class dropped in that directory
needs no build-script change** -- the front service of the next step goes
there too.
* A session's transcript replays a delivered file (`deliver_file`) as a card
with Open and Share -- the copy lives next to the session, marked `for_user`
so no request ever contains it (see `app/README.md`, "Files the agent
so no request ever contains it (see `archive/qt-archive/qt-app/README.md`, "Files the agent
delivers").
* **A turn survives the app leaving the screen**: `AgentCore::send()` calls
`app::holdForeground()`, which starts `CstService`
(`app/android/src/xyz/whcself/cstagent/CstService.java`) as a foreground
(`archive/qt-app/android/src/xyz/whcself/cstagent/CstService.java`) as a foreground
service and takes a partial wake lock; `finishTurn()` stops it again. On the
NX789J: with the app sent home and the screen off, the service showed
`isForeground=true` with its `CST-agent is working` notification (id 2, the
@@ -205,17 +206,17 @@ have, so the script rewrites the shebang of the build-tools wrappers.
the foreground the same two hooks wait two minutes (a permission) or five
(a question) and then answer "no" with a reason instead of waiting for good.
The GUI hop a platform tool makes (`onGuiThread()` in
`app/src/platform/android/AndroidTools.cpp`) pings first and gives up after
`archive/qt-app/src/platform/android/AndroidTools.cpp`) pings first and gives up after
three seconds, and the header shows what the turn is doing
(`shell: make -j8`) so a long turn is not mistaken for a stuck one.
`--uitest` covers the window side of all this.
* **Documents**: `md_to_pdf` (the window's own tool,
`app/src/core/PdfTools.cpp`, owner `app:qt`) lays markdown or a workspace
`archive/qt-app/src/core/PdfTools.cpp`, owner `app:qt`) lays markdown or a workspace
`.md`/`.html` out as an A4 PDF with 15 mm margins and hands it over as a file
card, exactly the way `deliver_file` does; `QPrinter`/PrintSupport is not
used, so the phone gets it too. Qt's PDF engine embeds a font *subset*, which
is why a Chinese page is a few KB rather than the few MB a whole CJK font
would cost (see `app/README.md`).
would cost (see `archive/qt-archive/qt-app/README.md`).
* Still to come: the back gesture, and the icon glyphs -- `ThemeIcon` finds no
icon files on the phone and draws empty boxes where the desktop shows Breeze
icons.
+13 -10
View File
@@ -1,20 +1,23 @@
# Hermit Desktop (Tauri)
# Hermit Desktop(归档)
Canonical GUI: [`apps/desktop`](../apps/desktop/). Qt under [`app/`](../app/) is frozen ([DEPRECATED](../app/DEPRECATED.md)).
桌面 GUI **不在本仓默认交付物中**。参考实现已冻结于:
## Shape
| 树 | 说明 |
|----|------|
| [`archive/desktop-tauri/`](../archive/desktop-tauri/) | Tauri 2 + React(原 `apps/desktop`) |
| [`archive/qt-app/`](../archive/qt-app/) | Qt6/QML(原 `app/`,已 DEPRECATED) |
- **Shell**: Tauri 2 (Linux / Windows / macOS)
- **UI**: React 19 — session rail, chat, tool cards, composer (DeepSeek Harness–like IA)
- **Engine**: in-process `libcstcore` via Rust FFI (feature `engine`); without it the host uses a mock / echo path so the UI is developable
正式远程客户端请实现 [webui.md](webui.md) / [webui-schema.md](webui-schema.md)
(`hermit --web`)。进程内嵌入请用 `libcstcore` + `cst_agent_hooks_t`;归档 FFI
见 `archive/desktop-tauri/src-tauri/src/ffi_bridge.*`。
## Dev
## 归档 Tauri 开发(不保证可编)
```bash
nix-shell -p rustc cargo nodejs_22 pkg-config webkitgtk_4_1 gtk3 librsvg openssl curl
cd apps/desktop && npm install && npm run tauri dev
cd archive/desktop-tauri && npm install && npm run tauri dev
```
## Product env
## 产品环境变量
`HERMIT_*` preferred, `CST_*` accepted (see `hermit_getenv`). Config dir `~/.config/hermit`.
`HERMIT_*` 优先,`CST_*` 仍接受(见 `hermit_getenv`)。配置目录 `~/.config/hermit`。
+7 -2
View File
@@ -1,7 +1,12 @@
# 扩展:Skills、插件、MCP 与自定义子 agent
所有扩展只在默认工具档(`--tools default`)下加载;`--tools minimal` 完全不受影响。
下文的“配置目录”指 `~/.config/cst-agent/`(`$XDG_CONFIG_HOME/cst-agent/`;Windows 为 `%APPDATA%\cst-agent\`)。
Hermit 的**可替换面**是本页这些扩展;**不是**内置 core 工具
(`read_file` / `write_file` / `apply_patch` / `run_shell` / …)或权限/沙箱——那些是
agent 契约与 `--tools minimal` 基线,不能当用户插件卸掉。
所有扩展只在默认工具档(`--tools default`)下加载;`--tools minimal` 时
`cst_extensions_load` 直接跳过。下文的“配置目录”优先 `~/.config/hermit/`,
若无则回退 `~/.config/cst-agent/`(`$XDG_CONFIG_HOME/…`;Windows 为 `%APPDATA%\…`)。
| 放在哪 | 什么 | 何时加载 |
|---|---|---|
+66
View File
@@ -0,0 +1,66 @@
# cst-webui/1 — event & state schema
Machine-oriented summary of the HTTP/SSE engine API. Narrative + security:
[webui.md](webui.md). Peer/device sockets are separate: [parliament.md](parliament.md).
Protocol string: `GET /api/state` → `"protocol": "cst-webui/1"`.
## Capabilities
`GET /api/state` includes `"capabilities": [ ... ]` listing SSE `type` values this
build emits. Clients should probe this array rather than hard-coding.
Current set:
| type | Mirrors hook | Notes |
|------|--------------|--------|
| `assistant_delta` | `on_assistant_delta` | streaming text |
| `think_delta` | `on_think_delta` | reasoning stream |
| `assistant_html` | (web md) | sanitized HTML chunks |
| `status` | `on_status` | `{ "message": "..." }` |
| `tool` | `on_tool_event` | phase + call |
| `tool_result` | `on_tool_result` | output + exit |
| `todo` | `on_todo` | task list text |
| `permission` | `on_permission` | wait for `POST /api/permission` |
| `ask_user` | `on_ask_user` | wait for `POST /api/ask` |
| `state` | — | full snapshot (same shape as `/api/state`) |
| `turn_begin` | `on_turn_begin` | `{ "text": "<user prompt>" }` |
| `turn_end` | `on_turn_end` | `{ "rc": 0\|N, "error": "" }` |
| `error` | `on_error` | `{ "kind": "turn\|cancel\|…", "message": "…" }` |
## SSE framing
```
GET /api/events?since=<seq>
```
- `id: <seq>` + `data: {"type":…,"seq":…, …}`
- Comment heartbeat: `: ping`
## Primary control endpoints
| Method | Path | Role |
|--------|------|------|
| GET | `/api/state` | snapshot + `capabilities` |
| GET | `/api/events` | SSE stream |
| POST | `/api/turn` | `{ "text": "..." }` start turn |
| POST | `/api/cancel` | cancel in-flight turn |
| POST | `/api/permission` | answer permission prompt |
| POST | `/api/ask` | answer ask_user |
| POST | `/api/quit` | shut down server |
Full route list: [webui.md](webui.md).
## Embedded hooks (same semantics)
```c
cst_agent_hooks_t {
on_status, on_assistant_delta, on_think_delta,
on_tool_event, on_tool_result, on_tool_images,
on_permission, on_ask_user, on_todo,
on_turn_begin, on_turn_end, on_error,
userdata
};
```
Archived FFI mirror: `archive/desktop-tauri/src-tauri/src/ffi_bridge.h` (`hermit_hooks_t`).
+23 -17
View File
@@ -1,32 +1,35 @@
# WebUI(内置 HTTP + SSE 前端)
# 引擎 HTTP + SSE API(`cst-webui/1`)
`cst-agent --web` 让同一个进程同时提供命令行以外的前端:浏览器(或任何 HTTP 客户端)
连上后能看到流式回答、工具卡片、审批弹窗和任务清单。它是 TUI 之外的第二个前端,
**引擎没有为它改一行**——两边用的都是 `cst_agent_hooks_t` 这组回调。
`hermit --web`(兼容 `cst-agent --web`)在同一进程上暴露 **远程客户端契约**:
任意 HTTP 客户端可订阅 SSE、发起 turn、回答审批。事件与嵌入式
`cst_agent_hooks_t` 一一对应——TUI 与 `--web` 共用引擎,不另开私有通道。
这份文档同时是**引擎 API v1 的规范**:以后专门客户端(Tauri / Qt / 移动端)直接照它实现,
不需要读 C 代码。
字段级摘要见 [webui-schema.md](webui-schema.md)。设备间 peer 套接字见
[parliament.md](parliament.md)(与本 API 无关)。
默认只嵌入**健康页**;完整浏览器 SPA 参考实现在 `archive/web-spa`。
## 快速开始
```bash
# 默认只监听 127.0.0.1:8787
cst-agent --web
# 默认只监听 127.0.0.1:8787(健康页 + /api/*)
hermit --web
# 指定端口;0 = 让系统分配(启动日志里会打印实际 URL)
cst-agent --web --web-port 8787
hermit --web --web-port 8787
# 让手机/另一台机器访问(必须给 token,否则拒绝启动在非 loopback 上)
cst-agent --web --web-host 0.0.0.0 --web-token 我自己定的口令
hermit --web --web-host 0.0.0.0 --web-token 我自己定的口令
# 开发前端:直接从目录读 web/,改完刷新页面即可,不用重新编译
cst-agent --web --web-dev ./web
# 开发:读磁盘上的静态目录(健康页或归档 SPA)
hermit --web --web-dev ./web
hermit --web --web-dev archive/web-spa
# 启动后自动打开浏览器
cst-agent --web --web-open
hermit --web --web-open
# 启动即跑一轮(不点页面也能用)
cst-agent --web -p "看看这个仓库有什么问题"
hermit --web -p "看看这个仓库有什么问题"
```
启动日志:
@@ -62,7 +65,8 @@ webui: token 3f9a… (append #token=3f9a… to the URL)
## 引擎 API(协议 `cst-webui/1`)
所有响应都是 JSON(`Content-Type: application/json`)。`GET /api/state` 的
`protocol` 字段给出版本号,客户端应当先看它再决定支持哪些端点。
`protocol` 字段给出版本号;`capabilities` 数组列出本进程会发出的 SSE `type`。
客户端应当先看它们再决定支持哪些端点/事件。
### 事件流
@@ -95,9 +99,11 @@ GET /api/events?since=<seq> → text/event-stream (chunked)
| `permission_done` | `id`,`answer` | 问题已结束(超时/取消也会发) |
| `ask_user` | `id`,`question`,`options[]` | 需要回答 |
| `session` | `id` | 切换/新建了会话,客户端应重新拉 `/api/session` |
| `turn_end` | `rc`,`error` | 本轮结束(`rc=0` 正常,非 0 见 `error`) |
| `turn_begin` | `text` | 一轮开始(用户提示原文);对应 `on_turn_begin` |
| `turn_end` | `rc`,`error` | 本轮结束(`rc=0` 正常,非 0 见 `error`);对应 `on_turn_end` |
| `error` | `kind`,`message` | 结构化错误(`turn` / `cancel` / …);对应 `on_error`,常在 `turn_end` 之前 |
`state` 字段:`workspace` `session` `provider` `model` `effort` `approval`
`state` 字段:`protocol` `capabilities[]` `workspace` `session` `provider` `model` `effort` `approval`
(`ask|auto|yolo`) `plan_mode` `protocol_tools` `status_busy` `todo`
`context_tokens` `undo_available` `pending_images[]` `usage{requests,prompt_tokens,
completion_tokens,cached_tokens,cache_miss_tokens,cost,priced,currency}`