Appearance
功能规格说明:IDE 上下文提及
创建日期:2026-10-22
平台边界:仅插件端(VS Code 扩展 / JetBrains 插件)。CLI、桌面端与远控页不提供这两个提及——它们没有插件端可读的数据源,
@下拉里不出现对应条目。
背景与取舍
agent 自己的 Bash 工具与用户的 IDE 终端是两个互不相通的进程:用户在终端里看到的启动失败、端口占用、依赖安装报错,agent 一条都看不到;编译器/检查器报错同理。要让 agent 收敛,只能由用户把文本复制过来。
本规格采用用户 @ 手动附加的形态:
| 形态 | 取舍 |
|---|---|
@ 手动附加(本规格) | 由用户决定附加哪一类上下文;无调用成本、无时序等待。Continue 的 @terminal / @problems 是同一形态的完整先例 |
| 模型主动调用工具 | Claude Code 的 getDiagnostics 属此类,但 Claude Code 两个插件都不读取用户终端,终端侧无先例 |
| 每轮自动注入 | Roo Code 每轮把终端输出与增量诊断塞进 environment_details,token 成本无条件发生,且要等终端冷却 |
内容怎么进消息、怎么呈现
内容在发送那一刻取一次,作为文本拼进用户消息正文;界面渲染为用户气泡里的标签,点标签在弹窗里看全文。 由此带来三条硬性质:
- 消息自包含:历史重放不再读 IDE 状态,切会话/重开面板/换机器看到的都是当初发出去的那份文本。
- 所见即所发:用户点开标签就能看到 agent 到底拿到了什么,不是隐藏注入。
- 组装只有一份:宿主只负责"读数据"(两端的 IDE API 本就不同,这层重复无法避免),标题、上限、排序、清理转义这些格式逻辑放在共享 webview 里,不在 TS 与 Kotlin 各写一遍。
与参考实现的差异
- 内容进消息正文、点标签可见:Continue 把
@terminal/@problems留作文本标记,在请求时刻解析成上下文项,用户在对话里看不到具体内容。本规格改成发送时取内容并入消息,并在用户消息里以标签留存、点开弹窗看全文。 - 统一的终端范围:Continue 自己两端不一致——VS Code 端取最后 1 条命令(
getTerminalContents(1)),JetBrains 端读整个 buffer(history + screen)。本规格两端统一取「最后一条命令及其输出」。 - 补了上限与转义清理:Continue 的两个 provider 都不做截断与 ANSI 清理(
compressTerminalOutput在 Roo Code 里,不在 Continue)。终端输出长度无上限,token 成本必须封顶。 @problems用结构化定位而非内联源码:Continue 每条给的是「±2 行源码 + message」,getProblems的映射里丢掉了 severity、正文也不写行号(JetBrains 端反而过滤 ERROR/WARNING,两端同样不一致)。本规格给「行:列 + 严重级 + 消息」:wave 的 agent 有 Read 工具,拿定位能自己取源码,不必把源码搬进上下文。- 不做调试上下文:Continue 的
@debugger在 JetBrains 侧三个方法均throw NotImplementedError(该宿主从没实现过),只覆盖 VS Code 的已暂停会话,两端不对称;本轮两端都不做,见「非目标」。
用户场景与测试 (必填)
用户故事:附加终端输出(优先级:P1)
作为插件端用户,我希望把终端里最后一条命令的输出附加到消息里,以便我不用复制粘贴就能让 agent 看到那个只在我 shell 里出现的问题。
为什么是这个优先级:agent 无法通过运行命令重建用户终端的历史,这是唯一能把该信息交给它的通道。
独立测试:在 IDE 终端执行一条有输出的命令,输入 @ 选中 Terminal 后发送,点开用户消息里的终端标签,验证弹窗中含该命令的命令行与输出。
验收场景:
- 假设存在活动终端且已执行过命令,当用户选中
@terminal并发送时,则消息正文必须含该终端最后一条命令的命令行与其输出。 - 假设用户附加了
@terminal之后又在终端里跑了一条新命令,当用户随后发送时,则取的是发送时刻的最后一条命令,而非附加时刻的。 - 假设终端输出包含 ANSI 颜色/控制转义或进度条重绘,当内容进入消息时,则必须清理控制字符,只留可读文本。
- 假设输出长度超过上限,当内容进入消息时,则只保留尾部,并在该块首行注明已截断。
- 假设没有任何终端,或活动终端没有任何输出,当用户发送时,则该块替换为一行明确的不可用说明(含原因),消息照常发送。
- 假设同一消息里用户附加了两次
@terminal,当发送时,则只取一份内容。
用户故事:附加当前文件的问题(优先级:P1)
作为插件端用户,我希望把当前文件的编译/检查报错附加到消息里,以便 agent 直接看到要修什么,而不是我逐条复制。
为什么是这个优先级:改完代码后确认"是否真的没错了"是最高频的动作,没有它每次都要人工搬运报错。
独立测试:在一个有错误的文件上打开编辑器,@ 选中 Problems 后发送,点开用户消息里的问题标签,验证弹窗中列出该文件的错误条目(行:列 + 严重级 + 消息)。
验收场景:
- 假设活动编辑器所在文件有 ERROR/WARNING,当用户选中
@problems并发送时,则消息正文必须含该文件的诊断条目,按行号顺序排列。 - 假设该文件没有任何诊断,当发送时,则块内写入明确的无问题说明,不是空块。
- 假设当前没有活动编辑器,当发送时,则块内写入不可用说明。
- 假设条目数超过上限,当发送时,则截断并在块首行报告总数与已列条数。
- 假设宿主读不到诊断(JetBrains 内部 API 在本版本不可用),当发送时,则块内写入明确失败原因,不抛异常、不中断发送。
用户故事:用户消息里的上下文标签(优先级:P1)
作为用户,我希望附加的 IDE 上下文在我自己那条消息里以标签形式留存,点开能在弹窗里看到完整内容,以便我知道这次到底把什么交给了 agent,同时不把聊天记录刷成一大段日志。
为什么是这个优先级:内容进了消息正文就必须能收起来,否则每次附加终端输出都会把对话顶出屏幕;同时"所见即所发"是这套能力的信任基础。
独立测试:发送一条带 @terminal 与 @problems 的消息,验证用户消息气泡里出现两个标签,分别点开后弹窗显示终端输出与诊断列表;重开面板后标签仍在,且不再访问 IDE。
归属与形态:
- 标签在用户消息气泡内,排在用户输入的文字之后,与文件/选区/图片标签同一处、同一套观感(复用现有上下文标签组件)。
- 用户气泡里只有标签、不放可展开的块——可展开块是助手侧的范式(思考/工具),用户气泡遵循"附件清单 + 点击预览",与图片标签一致。
- 点标签开弹窗预览完整文本(复用现有图片预览那类全屏遮罩层),弹窗内容可选中复制,点遮罩或按 ESC 关闭。
验收场景:
- 假设消息含终端标签,当这条用户消息渲染时,则标签出现在该用户消息气泡内、用户文字之后,标签文案是英文名(见「条目文案」),与文件/选区标签同一套外观。
- 假设用户点击终端标签,当弹窗打开时,则弹窗标题是英文名、正文首行是块标题(两者不同名,避免同一个词出现两次),正文显示进入消息的完整文本,且该文本与 agent 实际收到的一致;文本可选中复制。
- 假设弹窗已打开,当用户点击遮罩或按 ESC 时,则弹窗关闭,消息本身不变。
- 假设同一条消息含两个标签,当用户点开其中一个时,则一次只显示一个弹窗。
- 假设该块内容被截断,当用户查看标签与弹窗时,则都能看到"已截断"的标注(标签上以角标/文案提示,弹窗内首行说明)。
- 假设宿主当时读不到数据,当渲染时,则标签照常出现(形态统一,不因失败换一种呈现),打开后弹窗内是那一行说明。
- 假设会话被重新加载,当消息历史重放时,则弹窗内文本来自消息本身,不产生任何 IDE 读取。
- 假设用户在发送前删掉了输入框里的标签,当发送时,则消息里不出现对应标签。
- 假设用户只用键盘,当焦点落在标签上时,则标签在 Tab 序内,Enter/Space 可打开弹窗,ESC 关闭后焦点回到该标签。
用户故事:@ 下拉里混排固定条目与文件搜索(优先级:P1)
作为插件端用户,我希望 @ 下拉同时能选文件、也能选这两类 IDE 上下文,且在没有 IDE 数据源的端不出现无效条目。
为什么是这个优先级:两个条目若各占一个独立入口(菜单、按钮),交互面会膨胀;并入现有 @ 下拉是唯一不新增交互概念的做法。
条目文案:@ 后面那个词(下拉条目名、输入框与消息气泡里的标签)用英文 Terminal / Problems——与 Cursor 的 @Terminals、TRAE 的 #Problems 同一形制,提及标记在所有产品里都保持英文名词。中文留在两处:条目下方的描述("当前终端最后一条命令的输出"),以及消息正文里的块标题与说明行。
独立测试:输入 @ 验证两个固定条目排在文件搜索结果之前;继续输入 @ter 验证只剩 Terminal(文件搜索同时照常过滤);在桌面端与远控页输入 @ 验证两个条目都不出现。
验收场景:
- 假设用户在插件端面板输入
@,当下拉展开时,则两个固定条目必须排在文件搜索结果之前。 - 假设用户继续输入过滤文本,当过滤文本匹配某固定条目的名称、描述或 id 时,则该条目保留;不匹配的固定条目收起,文件搜索结果不受影响。
- 假设用户选中某固定条目,当插入输入框时,则以原子标签形式呈现(不可在标签内编辑,退格整体删除),与文件/选区标签同一套行为。
- 假设输入框内容被持久化后重新加载(切会话/重开面板),当内容恢复时,则标签仍在,发送时照常取内容。
- 假设宿主是桌面端或远控页,当用户输入
@时,则下拉里只有文件搜索结果,不出现这两个固定条目。
边界情况
- VS Code 读终端靠剪贴板中转:实现是「保存剪贴板 → 选中上一条命令 → 复制 → 读剪贴板 → 还原剪贴板 → 清除选区」。因此发送带
@terminal的消息时会短暂占用系统剪贴板,若用户此刻正好在复制,可能互相干扰。 - 只影响活动终端:VS Code 路径操作的是活动终端;用户切到别的终端时读到的是切换后的那个。
- 读数据有延迟:VS Code 侧要执行内置命令、JetBrains 侧要读终端缓冲,两者都比普通发消息慢。读期间的发送必须等待,不能让用户点了发送却发出一个空块;超时按"不可用"处理并照常发送。
- JetBrains 终端依赖 Shell 终端:通过 Shell 终端 widget 的文本缓冲读取;非 Shell 终端、或拿不到 widget 时按"不可用"处理(终端场景 5)。
- JetBrains 诊断读的是内部 API:
HighlightInfo.fromRangeHighlighter属impl包,跨 IDE 大版本可能失效;失效必须降级为问题场景 5 的说明行,且不得影响另一个条目。 - 诊断可能未完成:刚改完代码时检查器可能还在跑,读到的是上一轮结果;规格不等待分析完成(不做无界等待),但不得因此报错。
- 隐私:终端输出可能含密钥/令牌,展开的是用户自己终端里已经打印出来的内容,不额外扩大读取范围;内容会留在会话历史里,与用户手动粘贴同性质。
- 多根工作区:
@problems取的是活动文件,因此天然落在某个根内;消息里的路径按仓库相对路径呈现,便于 agent 直接读取。
非目标
- 不做调试上下文(
@debugger):VS Code 侧可做(活动会话 → 当前帧局部变量),但参考实现在 JetBrains 侧从未实现,两端行为会不对称;本轮两端都不做。 - 不做工具形态:不注册让模型主动调用的诊断/终端工具(与 Claude Code 的形态差异,留待后续评估)。
- 不做每轮自动注入:不在环境信息里无条件携带终端输出或诊断。
- 不做全项目 problems:
@problems只取当前活动文件,不做跨文件全量扫描。 - 不做终端选区/历史命令列表:
@terminal固定取最后一条命令及其输出,不支持选择第 N 条或指定终端。 - 不做发送后刷新:块内容固定在发送时刻,之后终端又输出了什么、诊断变没变,都不回写已有消息。
- 不做桌面端:桌面端终端(PTY)不接入该能力。
- 不做 Visual Studio:VS 的进程外扩展没有公开终端 API。
假设
- 宿主只提供原始数据:新增一对请求/响应(webview 请求两个 provider 的内容,宿主返回终端文本与诊断条目数组),格式化与拼消息在共享 webview 里做。该命令需在四个路由实例登记(桌面端返回不可用),否则 webview 命令审计 CI 失败。
- 不回写 SDK 侧机制:消息正文里就是最终的纯文本,SDK 不需要新工具,也不需要新的 RPC 类别。
- 条目可见性沿用已有宿主判据:两个固定条目只在插件端出现,判据取 webview 现有的宿主标识(
isDesktopHost/isRemotePageHost),不新增"宿主能力声明"请求。 - VS Code 侧无 shell integration 依赖:终端读取走内置命令 + 剪贴板,因此不要求终端启用 shell integration,也不要求 VS Code 1.93+ 的执行事件 API。
- JetBrains 侧不要求 2025.3+:终端走经典 Shell 终端 widget 的文本缓冲,不依赖 2025.3 起的新 Terminal API。
- 上限取设计默认值:终端尾部行数、problems 条数各有硬上限,超出按"尾部/前 N 条优先"截断并在块首行注明。