Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
+167
View File
@@ -0,0 +1,167 @@
# 应用内终端
Nomi 在应用内附带了一个真正的终端。每个终端都是一个由后端管理的
PTY 会话,你可以从浏览器/桌面窗口中以交互方式驱动它 —— 当你把它
绑定到一个 tag 上时,AutoWork 也可以代你来驱动它。
> 需要自动化指南?参见 [AutoWork & Requirements](./autowork-requirements.md)。
> 需要按计划运行 agent?参见 [定时任务](./scheduled-tasks.zh.md)。
![Nomi 应用内终端](../images/terminal-01-session.png)
## 应用内终端是什么
当你创建一个终端时,后端 (`nomifun-terminal`) 会通过
[`portable-pty`] 派生一个连接到真实伪终端的子进程。该会话由三部分组成:
- **持久化元数据** —— id、名称、工作目录、命令 + 参数、env、
preset/backend、权限模式、当前尺寸 (列 × 行)、pinned 标记、
退出状态。存储在 SQLite 中,所以会话条目在重启后仍然存在。
- **一个活跃的 PTY** (仅在子进程运行时存在) —— OS 伪终端、
其字节流输出,以及后端为后加入者保留的回滚缓冲区。
- **WebSocket 总线上的实时事件** —— PTY 输出的每一块都会被
base64 编码并以 `terminal.output` 广播。生命周期事件
(`terminal.created``terminal.updated``terminal.exit``terminal.removed`)
也走同一条总线。渲染进程中的 xterm.js 视图订阅并渲染这条流。
PTY 子进程不能被暂停或在进程间迁移:当子进程退出时,
列表行保留,但活跃的 PTY 没了。重新启动是原地进行的 —— 同一个会话 id
会附上一个全新的进程,所以你不会每次重启 CLI 都得到一个新的侧边栏
条目。
[`portable-pty`]: https://crates.io/crates/portable-pty
## 创建终端
打开终端创建页面 (终端侧边栏区段中的 **+** 按钮,或导航到
`/terminal-new`)。你需要选择五样东西:
1. **Workspace** —— 子进程将在其中派生的工作目录。
最近使用过的 workspace 会被记住。
2. **Preset** —— `Shell``Claude Code``Codex``Gemini`。shell
preset 会在启动时解析为你平台的 login shell (Windows
PowerShell/`cmd`macOS/Linux`$SHELL`)agent preset 会启动
对应的 CLI 二进制,该二进制必须已安装并在 `PATH` 上。
3. **权限模式** (仅 agent preset) —— `Default` (交互式审批)
`Full Auto` (会附加该 CLI 自身的非交互式 flag):
| Preset | Full-auto flag |
| ------------ | ------------------------------------------- |
| `claude` | `--dangerously-skip-permissions` |
| `codex` | `--dangerously-bypass-approvals-and-sandbox`|
| `gemini` | `--yolo` |
这些 flag 会绕过 CLI 的交互式审批提示 —— 这是 AutoWork 在没有
人按回车的情况下端到端驱动一轮所必需的,但同样的 flag 也赋予了
CLI 在你机器上的广泛能力。请把 full-auto 终端当作已登录的 shell 来对待。
4. **启动命令** —— 对话框会把解析后的 `command + args` 渲染到
一个可编辑字段中。在按下 **Launch** 之前可以自由调整 (额外
flag、替代入口点等)。
5. **知识库** (可选) —— 多选一个或多个知识库绑定到本会话。绑定的库
会在子进程派生前挂载到 `{workspace}/.nomi/knowledge/`,并生成一份
`README.md` (检索协议 + 各库梗概 + TOC + 回写规则);`claude`
preset 还会额外附加一条指向该 README 的 `--append-system-prompt`
指针。改绑在下次重新启动时生效。(网关工具 `nomi_create_terminal`
通过 `knowledge_base_ids` 支持同样的绑定。)
![终端创建页面](../images/terminal-02-create-page.png)
后端会持久化该行并派生子进程。页面会跳转到
`/terminal/<id>`,然后你开始接收实时输出。
## 驱动终端
会话页面是与实时流相连的 xterm.js:
- **键入** 把击键发送给 PTY。发送框也接受带 bracketed-paste 标记
的粘贴,所以多行文本会变成一次粘贴而不是一连串的回车。
- **调整大小** 调整面板大小,后端会相应调整 PTY 尺寸 (会向子进程
发送 `SIGWINCH`)。新的尺寸会被持久化。
- **重新启动** 在子进程退出后:单个按钮会杀掉同一 id 的任何残留
PTY,使用存储的命令 + cwd + env 派生一个新的进程,清空视图,
同样的 `terminal.<id>` 订阅会接管新的输出。你保留同一个侧边栏条目。
- **重命名 / 置顶** 从会话头进行 (重命名会作为
`terminal.updated` 广播;置顶的终端会浮到侧边栏顶部)。
- **Kill** 停止子进程但保留行 (它会转换为 `exited` 并可重新启动)。
**Delete** 杀掉子进程并完全移除该行。
![驱动一个终端会话](../images/terminal-03-driving-session.png)
## 流模型
输出走单个 WebSocket。当你正在查看一个会话时,你的客户端
会接收到该 id 的 `terminal.output` 事件并渲染它们。在 PTY 活跃期间,
后端在内存中保留一个回滚缓冲区:当你打开一个已经在运行的
终端时,GET 响应会包含一个 base64 编码的 `scrollback_b64` 快照,
所以 xterm 会先回放历史记录,然后实时事件再流入。
客户端到服务器的输入走另一个方向,通过一个小的 REST 端点
(base64 编码的字节)。后端会把这些字节直接写到 PTY 的 stdin。
## 终端作为自动化目标
驱动 UI 的同一个内存中的 PTY 映射通过 `TerminalDriver` trait
`nomifun-requirement` 中的 **AutoWork orchestrator** 共享。该
trait 让 AutoWork
- 订阅终端实时输出的副本 (它会监视完成标记并检测静默 ——
契约见 AutoWork 指南)。
- 向 PTY 写入输入字节 (它把 requirement prompt 包装在
bracketed-paste 中注入,使得多行指令会作为单次粘贴落地)。
- 检查存活性,读取该行的元数据 (user、backend、mode),并读取或
写入每个终端的 `autowork` 配置 blob。
换句话说:**你在这里创建的终端可被 AutoWork 自动化**。
在会话头的 AutoWork 工具栏上绑定一个 tagorchestrator
就会开始认领 requirement 并把它们喂给运行在该终端中的 CLI。
只有 agent-CLI 终端 (`claude``codex``gemini`) 才符合条件 ——
普通的 shell 可以手动驱动但不是 AutoWork 目标。orchestrator 也
推荐使用 Full Auto 模式,因为一轮如果撞上交互式审批提示
会一直阻塞直到超时。
如果工作区挂载了知识库 (存在 `{cwd}/.nomi/knowledge/`)AutoWork 与
cron 驱动注入的 prompt 会自动前置一行提示,让 CLI 先阅读挂载目录里的
`README.md` 再开工。
如果在 AutoWork 仍绑定时 PTY 退出,循环不会停止 —— 它会
空转并等待你重新启动该终端,然后从中断处继续认领。
如果你删除该行,循环会彻底停止。
## IDMM (决策停滞监督)
长时间运行的 CLI 会话有时会停滞:provider 掉线,模型在某个工具
调用上空转,CLI 打印了一个无人回答的确认提示。IDMM
(Intelligent Decision-Making Mode) supervisor 会监视会话并介入 ——
先用基于规则的轻推 (无 LLM),然后调用一个 sidecar 备用模型 ——
这样这一轮会到达一个终态,而不是挂起到 AutoWork 超时触发。
你可以在同一个会话头 (AutoWork 旁边的 **IDMM** 控件) 中按终端
启用 IDMM。无论 AutoWork 是否同时绑定它都能工作;当两者都开启
时,AutoWork 会确保 IDMM 在每一轮的全程都在监督。
## 路由 & API
| 内容 | 位置 |
| ------------------------ | --------------------------------------------- |
| 创建页面 | `/terminal-new` |
| 会话页面 | `/terminal/:id` |
| 列出 / 创建 | `GET /api/terminals``POST /api/terminals` |
| 获取 / 更新 / 删除 | `GET|PATCH|DELETE /api/terminals/:id` |
| 发送输入 | `POST /api/terminals/:id/input` |
| 调整大小 | `POST /api/terminals/:id/resize` |
| 杀掉子进程 | `POST /api/terminals/:id/kill` |
| 原地重新启动 | `POST /api/terminals/:id/relaunch` |
| 实时输出 / 生命周期 | WebSocket 事件 `terminal.*` |
## 故障排查
- **找不到 CLI。** Agent preset 直接调用 `claude``codex`
`gemini` —— 它们必须在运行后端的账户的 `PATH` 上。要么全局安装
CLI,要么在启动前编辑启动命令使用绝对路径。
- **AutoWork 绑定是灰色的。** 当前只有 `claude`/`codex` 终端才是
AutoWork 目标。普通 shell preset 不能被绑定;Gemini 终端 AutoWork
还没有接入后端的完成契约。
- **重新启动一直复用同一个 env / cwd。** 这是有意为之 —— 会话
行存储着它们。要修改它们,请用想要的设置创建一个新的终端。
- **调整大小后输出乱了。** 一些 TUI 在 `SIGWINCH` 时需要重绘。
`Ctrl-L` (或你 CLI 的重绘快捷键)。