## 新增:基于 React/Ink 的 CLI 终端(cli/)
使用 Ink 框架构建完整的终端 UI,取代原有的纯 print 交互方式:
- App.tsx:主应用组件,管理对话状态、任务轮询、键盘输入、slash 命令
- components.tsx:Timeline、Composer、StatusLine、Picker、SlashMenu 等全部 UI 组件
- eventMapper.ts:将后端 SSE 事件流映射为 Timeline 条目,支持 thinking/tool/assistant/sub_agent 等类型
- api.ts:封装所有后端 REST 接口(任务、对话、模型、权限、工作区等)
- commands.ts:slash 命令定义(/new /resume /model /permission /execution /workspace /compact /tools 等)
- workspaces.ts:本地工作区目录的持久化管理
- types.ts:所有共享类型定义
- format.ts:context 格式化、路径缩写等工具函数
## 重大修复:去除固定高度全屏模式,恢复滚轮滚动
**问题根因**:根 Box 设置了 `height={stdout.rows}` 使 Ink 进入原地重绘模式
(类似 vim/htop),所有内容在同一块屏幕区域反复擦写,永远不会真正
"滚出"屏幕顶部,导致 terminal 的 scrollback buffer 始终为空,鼠标
滚轮无任何响应。
**修复方案**:
- 移除根 Box 的 `height` 约束和 `overflow="hidden"`
- 移除内层 Box 的 `flexGrow`/`justifyContent="flex-end"` 全屏布局
- Timeline 不再做行数截断,直接渲染全部条目
- 内容自然流入 terminal scrollback buffer,鼠标滚轮恢复正常
**同步清理**:删除 `getTimelineMaxRows`、`estimateTimelineRows`、
`scrollOffset` 状态及方向键滚动逻辑(这些都是全屏模式下的补丁,
去除固定高度后不再需要)
## 修复:Composer 光标位置偏移一行
`setCursorPosition` 中 y 轴使用 `lines.length` 导致单行时偏移 +1。
改为 `lines.length - 1`,与实际最后一行渲染位置对齐。
## 其他
- AGENTS.md / README.md:补充 CLI 启动方式与架构说明
- server/chat.py:补充任务轮询接口所需字段
- package.json:新增 cli 相关脚本入口
- docs/cli_slash_commands_spec.md、docs/cli_ui_display_spec.md:CLI 设计文档
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
524 lines
8.9 KiB
Markdown
524 lines
8.9 KiB
Markdown
# CLI UI 显示规范(草案)
|
||
|
||
> 目标:把 Web 端已有的信息表达方式,压缩成一个低噪声、可扫描、适合终端的 CLI 交互层。
|
||
> 适用范围:CLI 前端展示层,不定义 Agent Runtime 本身。
|
||
|
||
## 1. 设计原则
|
||
|
||
1. **事件流优先**:CLI 只消费结构化事件,不直接消费原始 API 响应。
|
||
2. **极简优先**:正文少、状态清楚、动作明确。
|
||
3. **固定符号**:统一用少量状态符号表达进度与结果。
|
||
4. **完整展示变更**:文件编辑类内容不折叠,直接完整显示。
|
||
5. **思考可见但不冗长**:只保留滚动中的短窗口,不保留完整历史。
|
||
6. **交互独立**:审批、选择、Slash 命令都走独立输入态。
|
||
|
||
---
|
||
|
||
## 2. 总体布局
|
||
|
||
```text
|
||
[历史事件区]
|
||
[当前思考 / 工具 / 子智能体输出]
|
||
[最终结果]
|
||
──
|
||
[输入栏]
|
||
[状态栏]
|
||
```
|
||
|
||
### 底部状态栏建议
|
||
|
||
```text
|
||
5 后台智能体 3 后台指令 版本控制: 开 自动审批 沙箱 24k/200k
|
||
```
|
||
|
||
可扩展字段:
|
||
|
||
- 模型名
|
||
- 运行模式(thinking / fast 先不做切换也可保留显示位)
|
||
- Git 开关
|
||
- 自动审批状态
|
||
- 沙箱/直连状态
|
||
- 上下文剩余量
|
||
|
||
---
|
||
|
||
## 3. 状态符号
|
||
|
||
| 状态 | 符号 | 颜色 | 说明 |
|
||
|---|---|---|---|
|
||
| 运行中 | `◦` / `•` 交替 | 黄/灰 | 闪烁 |
|
||
| 完成 | `•` | 绿 | 静态 |
|
||
| 失败 | `•` | 红 | 静态 |
|
||
| 用户输入 | `›` | 白/青 | 静态 |
|
||
| 审批中 | `◦` | 黄 | 慢闪 |
|
||
| 已取消 | `•` | 灰 | 静态 |
|
||
|
||
---
|
||
|
||
## 4. 思考块显示
|
||
|
||
思考块只保留一种样式:**5 行滚动窗口**。
|
||
|
||
### 4.1 运行中
|
||
|
||
```text
|
||
◦ 思考中
|
||
└ 第一行
|
||
第二行
|
||
第三行
|
||
第四行
|
||
第五行
|
||
```
|
||
|
||
当加入第 6 行时,自动滚动:
|
||
|
||
```text
|
||
◦ 思考中
|
||
└ 第二行
|
||
第三行
|
||
第四行
|
||
第五行
|
||
第六行
|
||
```
|
||
|
||
### 4.2 完成后
|
||
|
||
```text
|
||
• 思考完成 4.2s
|
||
```
|
||
|
||
不做折叠展开,不保留完整思考历史。
|
||
|
||
### 4.3 规则
|
||
|
||
- 只显示最新 5 行
|
||
- 只显示当前思考内容
|
||
- 完成后只留完成状态
|
||
- 不显示“思考全文”
|
||
|
||
---
|
||
|
||
## 5. 子智能体前台显示
|
||
|
||
子智能体前台展示方式与思考块一致,也是 5 行滚动窗口。
|
||
|
||
```text
|
||
◦ 子智能体 review-agent...
|
||
└ 运行 ls -la && pwd
|
||
搜索 python 新版本
|
||
第三行
|
||
第四行
|
||
第五行
|
||
```
|
||
|
||
完成后:
|
||
|
||
```text
|
||
• 子智能体完成 8.1s
|
||
```
|
||
|
||
### 规则
|
||
|
||
- 子智能体只显示前台进度,不显示全部历史
|
||
- 只保留最新 5 行
|
||
- 完成态只显示耗时和结果摘要
|
||
|
||
---
|
||
|
||
## 6. 工具块通用格式
|
||
|
||
统一使用:
|
||
|
||
```text
|
||
• 动作摘要
|
||
└ 结果摘要
|
||
```
|
||
|
||
### 6.1 运行中
|
||
|
||
```text
|
||
◦ 运行 npm run build
|
||
└ 正在执行...
|
||
```
|
||
|
||
### 6.2 完成
|
||
|
||
```text
|
||
• 运行 npm run build
|
||
└ 成功,用时 8.2s
|
||
```
|
||
|
||
### 6.3 失败
|
||
|
||
```text
|
||
• 运行 npm run build
|
||
└ 失败,exit code 2
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 工具显示规范
|
||
|
||
下面定义 `└` 后面的内容包含哪些字段、如何显示。
|
||
|
||
### 7.1 运行命令类
|
||
|
||
#### 适用工具
|
||
|
||
- `run_command`
|
||
- `terminal_input`
|
||
- `terminal_session`
|
||
- `terminal_snapshot`
|
||
|
||
#### 显示字段
|
||
|
||
- `command` / `input`
|
||
- `timeout`
|
||
- `exit_code`
|
||
- `stdout` / `output`
|
||
- `stderr`(若可分离)
|
||
|
||
#### 显示规则
|
||
|
||
```text
|
||
• 运行 printf '' > test.txt && ls -l test.txt
|
||
└ -rw-r--r-- 1 jojo staff 0 May 14 14:48 test.txt
|
||
```
|
||
|
||
长输出:
|
||
|
||
```text
|
||
• 运行 ls -la && pwd
|
||
└ 第 1 行内容
|
||
第 2 行内容
|
||
… +6 lines
|
||
第 9 行内容
|
||
第 10 行内容
|
||
```
|
||
|
||
#### 约束
|
||
|
||
- 第一行优先显示结果中最有信息量的一行
|
||
- 长文本可局部截断,但不要丢掉关键错误
|
||
- 如果有 exit code,必须显示
|
||
|
||
---
|
||
|
||
### 7.2 文件编辑类
|
||
|
||
#### 适用工具
|
||
|
||
- `write_file`
|
||
- `edit_file`
|
||
|
||
#### 原则
|
||
|
||
**必须完整显示,不折叠。**
|
||
|
||
因为:
|
||
|
||
- 这是文件内容本身,不是工具摘要
|
||
- 单次替换通常不会太大
|
||
- 终端里保留完整 + / - 最有价值
|
||
|
||
#### 7.2.1 Write
|
||
|
||
显示字段:
|
||
|
||
- `path`
|
||
- `mode`(append / overwrite)
|
||
- `status`
|
||
- `content`
|
||
|
||
显示模板:
|
||
|
||
```text
|
||
• Write test.txt (+3 -0)
|
||
1 +这是第一行测试内容。
|
||
2 +这是第二行测试内容。
|
||
3 +这是第三行测试内容。
|
||
```
|
||
|
||
#### 7.2.2 Edit
|
||
|
||
显示字段:
|
||
|
||
- `path`
|
||
- `replace_all`
|
||
- `found`
|
||
- `replacements`
|
||
- 每个 replacement 的 `old` / `new`
|
||
|
||
显示模板:
|
||
|
||
```text
|
||
• Edited main.py (+2 -2)
|
||
14 -old line 1
|
||
15 -old line 2
|
||
14 +new line 1
|
||
15 +new line 2
|
||
```
|
||
|
||
#### 7.2.3 规则
|
||
|
||
- 不折叠
|
||
- 不显示上下文行
|
||
- 只显示参数里已有的变更信息
|
||
- 若存在多段替换,按替换顺序逐段输出
|
||
|
||
---
|
||
|
||
### 7.3 阅读类
|
||
|
||
#### 适用工具
|
||
|
||
- `read_file`
|
||
- `read_skill`
|
||
|
||
#### 显示字段
|
||
|
||
- `path`
|
||
- `status`
|
||
- `size`
|
||
- `read_type`
|
||
- `content`
|
||
- `search` / `extract` 子模式信息
|
||
|
||
#### 显示模板
|
||
|
||
普通读取:
|
||
|
||
```text
|
||
• 阅读 test.txt
|
||
└ 读取 3 行
|
||
```
|
||
|
||
提取模式:
|
||
|
||
```text
|
||
• 阅读 test.txt
|
||
└ 提取第 12-28 行
|
||
```
|
||
|
||
搜索模式(如果未来恢复):
|
||
|
||
```text
|
||
• 搜索 handle_task
|
||
└ 找到 12 处,位于 5 个文件
|
||
```
|
||
|
||
#### 规则
|
||
|
||
- 正文尽量压缩成“读取了多少 / 提取了什么 / 找到了什么”
|
||
- 需要看全文时再走展开命令
|
||
|
||
---
|
||
|
||
### 7.4 搜索类
|
||
|
||
当前 CLI v1 **不做搜索工具**。
|
||
|
||
如果未来恢复,显示方式参考阅读类:
|
||
|
||
```text
|
||
• 搜索 "xxx"
|
||
└ 找到 N 处,位于 M 个文件
|
||
```
|
||
|
||
---
|
||
|
||
### 7.5 文件创建 / 删除 / 重命名
|
||
|
||
当前 CLI v1 **不做这些工具块**,可直接忽略。
|
||
|
||
---
|
||
|
||
### 7.6 子智能体 / 后台任务类
|
||
|
||
#### 显示字段
|
||
|
||
- `name`
|
||
- `status`
|
||
- `latest_progress_lines`
|
||
- `elapsed`
|
||
|
||
#### 显示模板
|
||
|
||
```text
|
||
◦ 子智能体 review-agent...
|
||
└ 运行 ls -la && pwd
|
||
搜索 python 新版本
|
||
第三行
|
||
第四行
|
||
第五行
|
||
```
|
||
|
||
完成后:
|
||
|
||
```text
|
||
• 子智能体完成 8.1s
|
||
```
|
||
|
||
#### 规则
|
||
|
||
- 只显示最新 5 行
|
||
- 运行中用 `◦`
|
||
- 完成后用 `•`
|
||
|
||
---
|
||
|
||
### 7.7 审批 / 交互类
|
||
|
||
#### 适用场景
|
||
|
||
- 权限审批
|
||
- 需要用户确认的危险操作
|
||
- 自定义选择
|
||
- Slash 命令补全
|
||
|
||
#### 显示方式
|
||
|
||
使用**输入栏扩展 + 选项列表**,不是普通消息流。
|
||
|
||
示例:
|
||
|
||
```text
|
||
┌────────────────────────────────────┐
|
||
│ 需要批准:运行 rm -rf dist │
|
||
│ 原因:删除构建目录 │
|
||
│ │
|
||
│ › 1. Allow once │
|
||
│ 2. Always allow │
|
||
│ 3. Deny │
|
||
└────────────────────────────────────┘
|
||
```
|
||
|
||
#### 规则
|
||
|
||
- 上下键切换
|
||
- 当前行高亮
|
||
- Enter 确认
|
||
- Esc 或拒绝关闭
|
||
|
||
---
|
||
|
||
## 8. Slash 命令规范
|
||
|
||
所有操作都通过 `/` 输入。
|
||
|
||
### 8.1 触发方式
|
||
|
||
输入 `/` 或 `/a` 后,打开命令建议列表。
|
||
|
||
### 8.2 显示方式
|
||
|
||
```text
|
||
› /approve 简要说明
|
||
/allow 简要说明
|
||
/assist 简要说明
|
||
```
|
||
|
||
### 8.3 规则
|
||
|
||
- 支持模糊过滤
|
||
- 上下键选择
|
||
- 当前项变色
|
||
- Enter 执行
|
||
|
||
---
|
||
|
||
## 9. 最终输出区
|
||
|
||
任务完成后,使用固定分隔结构:
|
||
|
||
```text
|
||
──
|
||
|
||
• 已创建 test.txt 文件。
|
||
|
||
1. 修改文件列表:test.txt
|
||
2. 核心变更点:在 /Users/jojo/Desktop/test 下创建了一个空文本文件
|
||
3. 验证结果:已执行 ls -l test.txt,文件存在。
|
||
|
||
──
|
||
```
|
||
|
||
### 最终结果字段
|
||
|
||
建议统一输出:
|
||
|
||
1. **修改文件列表**
|
||
2. **核心变更点**
|
||
3. **验证结果**
|
||
|
||
如果有失败:
|
||
|
||
- 失败原因
|
||
- 已做的尝试
|
||
- 下一步建议
|
||
|
||
---
|
||
|
||
## 10. 信息优先级
|
||
|
||
从高到低:
|
||
|
||
1. 当前交互态(审批 / 命令选择)
|
||
2. 当前思考 / 当前子智能体进度
|
||
3. 当前工具输出
|
||
4. 最终结论
|
||
5. 低优先级元信息
|
||
|
||
---
|
||
|
||
## 11. 参考的 Web 端表现
|
||
|
||
Web 端已经存在的对应参考:
|
||
|
||
- `static/src/components/chat/actions/ThinkingAction.vue`
|
||
- 思考块
|
||
- 运行中 / 完成两态
|
||
- 可折叠
|
||
- `static/src/components/chat/actions/ToolAction.vue`
|
||
- 工具块
|
||
- `renderEnhancedToolResult()`
|
||
- 文件编辑 / 读取 / 命令 / 图片等不同分支
|
||
- `static/src/components/chat/StackedBlocks.vue`
|
||
- 思考 + 工具的堆叠展示
|
||
- `static/src/components/chat/MinimalBlocks.vue`
|
||
- 摘要组展示
|
||
- 最终只保留当前步骤预览
|
||
- `static/src/components/panels/ToolApprovalPanel.vue`
|
||
- 审批面板
|
||
- 编辑 diff、写入内容、命令预览
|
||
|
||
CLI 的目标不是复制这些视觉细节,而是抽取其中最有信息价值的字段。
|
||
|
||
---
|
||
|
||
## 12. 推荐的 CLI 默认策略
|
||
|
||
### 默认开启
|
||
|
||
- 5 行思考滚动
|
||
- 5 行子智能体滚动
|
||
- 文件编辑完整展示
|
||
- 命令输出摘要展示
|
||
- Slash 命令补全
|
||
- 审批选择弹层
|
||
|
||
### 默认关闭
|
||
|
||
- 完整思考历史
|
||
- 文件创建 / 删除 / 重命名显示
|
||
- 搜索工具
|
||
- 折叠展开复杂交互
|
||
|
||
---
|
||
|
||
## 13. 一句话总结
|
||
|
||
CLI 的目标是:
|
||
|
||
> 用最少的符号,表达最多的有效信息;用固定的状态和结构,让用户能一眼看懂 Agent 在做什么。
|