Files
MyAiDesk/nomifun-tauri/docs/guides/terminal.zh.md
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

168 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 应用内终端
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 的重绘快捷键)。