## 新增:基于 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>
8.9 KiB
8.9 KiB
CLI UI 显示规范(草案)
目标:把 Web 端已有的信息表达方式,压缩成一个低噪声、可扫描、适合终端的 CLI 交互层。 适用范围:CLI 前端展示层,不定义 Agent Runtime 本身。
1. 设计原则
- 事件流优先:CLI 只消费结构化事件,不直接消费原始 API 响应。
- 极简优先:正文少、状态清楚、动作明确。
- 固定符号:统一用少量状态符号表达进度与结果。
- 完整展示变更:文件编辑类内容不折叠,直接完整显示。
- 思考可见但不冗长:只保留滚动中的短窗口,不保留完整历史。
- 交互独立:审批、选择、Slash 命令都走独立输入态。
2. 总体布局
[历史事件区]
[当前思考 / 工具 / 子智能体输出]
[最终结果]
──
[输入栏]
[状态栏]
底部状态栏建议
5 后台智能体 3 后台指令 版本控制: 开 自动审批 沙箱 24k/200k
可扩展字段:
- 模型名
- 运行模式(thinking / fast 先不做切换也可保留显示位)
- Git 开关
- 自动审批状态
- 沙箱/直连状态
- 上下文剩余量
3. 状态符号
| 状态 | 符号 | 颜色 | 说明 |
|---|---|---|---|
| 运行中 | ◦ / • 交替 |
黄/灰 | 闪烁 |
| 完成 | • |
绿 | 静态 |
| 失败 | • |
红 | 静态 |
| 用户输入 | › |
白/青 | 静态 |
| 审批中 | ◦ |
黄 | 慢闪 |
| 已取消 | • |
灰 | 静态 |
4. 思考块显示
思考块只保留一种样式:5 行滚动窗口。
4.1 运行中
◦ 思考中
└ 第一行
第二行
第三行
第四行
第五行
当加入第 6 行时,自动滚动:
◦ 思考中
└ 第二行
第三行
第四行
第五行
第六行
4.2 完成后
• 思考完成 4.2s
不做折叠展开,不保留完整思考历史。
4.3 规则
- 只显示最新 5 行
- 只显示当前思考内容
- 完成后只留完成状态
- 不显示“思考全文”
5. 子智能体前台显示
子智能体前台展示方式与思考块一致,也是 5 行滚动窗口。
◦ 子智能体 review-agent...
└ 运行 ls -la && pwd
搜索 python 新版本
第三行
第四行
第五行
完成后:
• 子智能体完成 8.1s
规则
- 子智能体只显示前台进度,不显示全部历史
- 只保留最新 5 行
- 完成态只显示耗时和结果摘要
6. 工具块通用格式
统一使用:
• 动作摘要
└ 结果摘要
6.1 运行中
◦ 运行 npm run build
└ 正在执行...
6.2 完成
• 运行 npm run build
└ 成功,用时 8.2s
6.3 失败
• 运行 npm run build
└ 失败,exit code 2
7. 工具显示规范
下面定义 └ 后面的内容包含哪些字段、如何显示。
7.1 运行命令类
适用工具
run_commandterminal_inputterminal_sessionterminal_snapshot
显示字段
command/inputtimeoutexit_codestdout/outputstderr(若可分离)
显示规则
• 运行 printf '' > test.txt && ls -l test.txt
└ -rw-r--r-- 1 jojo staff 0 May 14 14:48 test.txt
长输出:
• 运行 ls -la && pwd
└ 第 1 行内容
第 2 行内容
… +6 lines
第 9 行内容
第 10 行内容
约束
- 第一行优先显示结果中最有信息量的一行
- 长文本可局部截断,但不要丢掉关键错误
- 如果有 exit code,必须显示
7.2 文件编辑类
适用工具
write_fileedit_file
原则
必须完整显示,不折叠。
因为:
- 这是文件内容本身,不是工具摘要
- 单次替换通常不会太大
- 终端里保留完整 + / - 最有价值
7.2.1 Write
显示字段:
pathmode(append / overwrite)statuscontent
显示模板:
• Write test.txt (+3 -0)
1 +这是第一行测试内容。
2 +这是第二行测试内容。
3 +这是第三行测试内容。
7.2.2 Edit
显示字段:
pathreplace_allfoundreplacements- 每个 replacement 的
old/new
显示模板:
• 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_fileread_skill
显示字段
pathstatussizeread_typecontentsearch/extract子模式信息
显示模板
普通读取:
• 阅读 test.txt
└ 读取 3 行
提取模式:
• 阅读 test.txt
└ 提取第 12-28 行
搜索模式(如果未来恢复):
• 搜索 handle_task
└ 找到 12 处,位于 5 个文件
规则
- 正文尽量压缩成“读取了多少 / 提取了什么 / 找到了什么”
- 需要看全文时再走展开命令
7.4 搜索类
当前 CLI v1 不做搜索工具。
如果未来恢复,显示方式参考阅读类:
• 搜索 "xxx"
└ 找到 N 处,位于 M 个文件
7.5 文件创建 / 删除 / 重命名
当前 CLI v1 不做这些工具块,可直接忽略。
7.6 子智能体 / 后台任务类
显示字段
namestatuslatest_progress_lineselapsed
显示模板
◦ 子智能体 review-agent...
└ 运行 ls -la && pwd
搜索 python 新版本
第三行
第四行
第五行
完成后:
• 子智能体完成 8.1s
规则
- 只显示最新 5 行
- 运行中用
◦ - 完成后用
•
7.7 审批 / 交互类
适用场景
- 权限审批
- 需要用户确认的危险操作
- 自定义选择
- Slash 命令补全
显示方式
使用输入栏扩展 + 选项列表,不是普通消息流。
示例:
┌────────────────────────────────────┐
│ 需要批准:运行 rm -rf dist │
│ 原因:删除构建目录 │
│ │
│ › 1. Allow once │
│ 2. Always allow │
│ 3. Deny │
└────────────────────────────────────┘
规则
- 上下键切换
- 当前行高亮
- Enter 确认
- Esc 或拒绝关闭
8. Slash 命令规范
所有操作都通过 / 输入。
8.1 触发方式
输入 / 或 /a 后,打开命令建议列表。
8.2 显示方式
› /approve 简要说明
/allow 简要说明
/assist 简要说明
8.3 规则
- 支持模糊过滤
- 上下键选择
- 当前项变色
- Enter 执行
9. 最终输出区
任务完成后,使用固定分隔结构:
──
• 已创建 test.txt 文件。
1. 修改文件列表:test.txt
2. 核心变更点:在 /Users/jojo/Desktop/test 下创建了一个空文本文件
3. 验证结果:已执行 ls -l test.txt,文件存在。
──
最终结果字段
建议统一输出:
- 修改文件列表
- 核心变更点
- 验证结果
如果有失败:
- 失败原因
- 已做的尝试
- 下一步建议
10. 信息优先级
从高到低:
- 当前交互态(审批 / 命令选择)
- 当前思考 / 当前子智能体进度
- 当前工具输出
- 最终结论
- 低优先级元信息
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 在做什么。