---
url: /wave-agent/specs/editor/next-edit-suggestion.md
description: >-
  编辑器内的下一步编辑建议（Next Edit Suggestion）：依据最近编辑轨迹预测下一处该改的位置与改法，以差异形式呈现、Tab
  接受；含轨迹采集、跳转定位、与行内补全的互斥及开关配置
---

# 功能规格说明：下一步编辑建议

**创建日期**：2026-10-26

## 概述

下一步编辑建议（下称「下一步建议」，英文 Next Edit Suggestion）与行内补全同属「编辑器内预测」族，但预测的东西不同：补全预测**光标处接着写什么**（追加型建议），下一步建议预测**下一次该改哪里、改成什么**——产物是一个可能删行、可能换位置、也可能整段重排的差异，建议位置可以离光标很远。

决定它的输入因此也不同：不是「光标前缀 + 后缀」，而是**近期编辑轨迹**——用户对本文件最近若干次编辑的差异记录，加上当前文件上下文。用户在写一个函数时通常有几处连锁改动，下一步建议的价值就在于接住第二步。

它与补全**共用同一条 CLI 通道与同一套模型来源**，但**各自开关、各自请求**：同样不经过 agent 的对话轮次（无工具、无 system prompt、无 hooks），不共享 `aiService` 的全局 1 次/秒间隔；同一会话同一时刻只有一个在途的下一步建议请求。补全与本能力在**同一时刻互斥**（见边界情况）。

首期不做本地模型推理，也不训练专用模型。

## 用户场景与测试 *（必填）*

### 用户故事：编辑停顿后给出下一步的修改建议（优先级：P1）

作为编码中的用户，我希望在完成一次编辑后停顿一下，编辑器就把「接下来该改的那一处」以差异形式指出来，以便不必自己回想这次改动牵连到的下一个位置。

**为什么是这个优先级**：这是本能力的本体——没有「编辑后停顿 → 出现差异建议」这条链路，其余能力（接受、跳转、开关）都无处依附。

**独立测试**：在 VS Code 中打开一个 TypeScript 文件，把某函数里的参数 `id` 改名为 `userId` 但只改了这一处，然后停顿，验证编辑器在另一处引用处以差异形式给出建议；在 JetBrains 中对同一文件重复同样操作，验证两端行为一致。

**验收场景**：

1. **假设** 下一步建议开关为开启、模型可用，**当** 用户完成一次编辑并停顿（停顿时长沿用补全的自适应防抖口径），**则** 插件向 CLI 发起一次下一步建议请求，并把返回的差异渲染在**建议所在区域**（可以与光标所在位置不同）。
2. **假设** 建议区域与当前区域内容只差空白或与原文完全相同，**当** 响应到达时，**则** 不渲染任何建议，用户看不到任何变化。
3. **假设** 模型返回的建议为空，**当** 响应到达时，**则** 不渲染任何建议。
4. **假设** 建议中既包含新增行也包含删除行，**当** 建议被渲染时，**则** 用户能区分哪些行将被加入、哪些行将被移除。
5. **假设** 建议已渲染，**当** 用户继续输入、移动光标、切换编辑器或切换标签页时，**则** 建议消失，其在途请求被取消；迟到的响应不得再渲染。
6. **假设** 建议请求失败（网络错误、模型不可用、鉴权失效、超时），**当** 请求返回错误时，**则** 静默跳过本次建议，不弹窗、不写状态栏、不影响用户编辑。
7. **假设** 建议内容超出用户实际可见的范围（如整段重排），**当** 用户接受它时，**则** 只有建议区域内被声明改动的部分发生变化，区域外内容一字不动。
8. **假设** 建议要替换或删除编辑器里已有的若干行（而不是在光标处纯插入），**当** 建议渲染时，**则** 那几行**在代码上**带有可见标记，用户不必依靠行号、也不必逐字比对浮层文本来判断建议落在哪几行。
9. **假设** 建议已渲染，**当** 用户接受它、拒绝它，或它因任何原因失效时，**则** 代码上的标记随之清除，编辑器里不残留任何标记或样式。

***

### 用户故事：接受或拒绝建议（优先级：P1）

作为用户，我希望按一个键就接受建议、按另一个键就拒绝，并且接受之后能像撤销自己的一次编辑那样撤销它，以便下一步建议的操作手感和自己打字一致。

**为什么是这个优先级**：建议的产物是差异而不是追加文本，接受动作会改动用户已有代码；「怎么接受、怎么撤销」不用好会让这个能力变成破坏性操作，因此与生成同属 P1。

**独立测试**：在建议已渲染的情况下按接受键，验证差异被写入文档、光标落在最后一处变更处、按一次撤销即可回到接受前状态；再在另一个位置按拒绝键，验证文档无任何变化。

**验收场景**：

1. **假设** 建议已渲染，**当** 用户按接受键时，**则** 差异被写入文档，建议消失，光标移动到本次建议中最后一处变更的位置。
2. **假设** 用户刚刚接受了建议，**当** 用户按一次撤销时，**则** 文档回到接受建议之前的状态（这次接受不与用户的上一次输入合并成同一步）。
3. **假设** 建议已渲染，**当** 用户按拒绝键时，**则** 建议消失、文档不变、光标不动。
4. **假设** 用户拒绝了某处的建议，**当** 用户没有新的编辑、也没有移动光标时，**则** 该处不再重复出现同一条建议；用户下一次编辑或移动光标后可再次请求。
5. **假设** 建议已渲染而用户在该区域直接开始打字，**当** 文档发生变化时，**则** 建议立即失效，不接受、不部分应用。
6. **假设** 建议以宿主原生的行内建议形式呈现，**当** 用户按接受键时，**则** 只有当前可见的那一条被接受，不会一次接受两种建议；**假设** 该宿主的行内建议 API 无法呈现这类改动（例如只能插入、不能替换已有行），**当** 建议呈现时，**则** 该宿主提供自己的接受动作（一次用户动作即完成整条建议），并在提示里写明这个动作是什么。

***

### 用户故事：编辑轨迹的采集与失效（优先级：P1）

作为用户，我希望下一步建议依据的是我最近真实做过的改动，而不是文件从打开以来的全部历史或别人的改动，以便建议对得上我手上的活。

**为什么是这个优先级**：轨迹是这能力的输入，采错就全错（把外部改动当成用户意图会产生误导性建议）；它同时是隐私边界，必须一开始就定清楚。

**独立测试**：连续做三次不相关的小编辑后停顿，验证建议与最近一次改动的意图相关；用外部命令改写该文件（如切换分支、运行格式化）后再停顿，验证这些改动不被当成用户编辑计入轨迹。

**验收场景**：

1. **假设** 用户在当前文件里做过若干次编辑，**当** 下一步建议请求发出时，**则** 轨迹只包含最近若干次**用户输入产生**的编辑，每条记录是一次编辑前后的差异。
2. **假设** 用户的编辑次数或编辑发生时间超过轨迹保留上限（条数与时间窗，均为默认值），**当** 新编辑产生时，**则** 最早的那些记录被丢弃，轨迹不会无限增长。
3. **假设** 文件内容由于保存者之外的原因整体变化（切换分支、外部格式化、被其他程序改写、编辑器重新打开），**当** 采集轨迹时，**则** 这类变化不被记为一条用户编辑。
4. **假设** 用户切换文件或切换到另一个编辑器，**当** 采集轨迹时，**则** 轨迹按文件隔离，不同文件的记录不混用。
5. **假设** 轨迹为空（例如刚打开文件还没编辑过一次），**当** 用户停顿触发时，**则** 不发起下一步建议请求（没有可用的编辑意图输入）。
6. **假设** 轨迹中最近一次编辑的差异采集失败，**当** 触发时，**则** 本次不发起请求，而不是退化成「无轨迹」再请求——没有轨迹的下一步建议退化为猜测，宁可不给。

***

### 用户故事：跳到建议所在位置（优先级：P2）

作为用户，我希望当建议不在我当前视线内时，能一键跳到它所在的位置再决定接受与否，以便我不用自己找。

**为什么是这个优先级**：这是「定位」那一半能力。没有它，建议仍能在光标附近工作（只是远处的位置用户看不见、等于没给），因此排在生成与接受之后。

**独立测试**：在一个长文件中把引用的符号改名但不滚动到该引用处，停顿后在可见范围内看不出变化；此时点击/触发跳转提示，验证编辑器滚动并把光标放到建议所在区域。

**验收场景**：

1. **假设** 建议所在区域既不在当前视口内、也无法在当前光标处呈现，**当** 建议渲染时，**则** 编辑器给出可跳转的提示，用户能看出「建议在别处」。
2. **假设** 存在可跳转的建议，**当** 用户触发跳转时，**则** 编辑器滚动到建议区域，光标落到该区域，建议仍处于待接受状态。
3. **假设** 存在可跳转的建议，**当** 用户没有触发跳转时，**则** 编辑器不自行移动光标或滚动（跳转必须是用户动作）。
4. **假设** 建议能在当前光标处以行内建议呈现，**当** 建议渲染时，**则** 不出现跳转提示（避免噪音）。
5. **假设** 用户触发了跳转但在此前文档已变化，**当** 跳转执行时，**则** 放弃这次跳转而不是跳到错误位置。

***

### 用户故事：触发时机、节流与与补全的互斥（优先级：P2）

作为用户，我希望下一步建议只在真正有可能的时候出现，不要和行内补全抢同一时刻，以便两者都不打断我的打字节奏。

**为什么是这个优先级**：这是体感层，缺失时能力仍可用（只是显得聒噪）；但它直接决定用户会不会把功能关掉。

**独立测试**：在一次接受的编辑后连续停顿，观察不会每次停顿都发请求；在光标位置让补全与下一步建议都可能出现，验证同一时刻只出现一种、且不会出现两个请求并发。

**验收场景**：

1. **假设** 用户在同一个位置重复停顿且没有新的编辑，**当** 停顿发生时，**则** 不重复发起下一步建议请求。
2. **假设** 一次补全请求在途，**当** 停顿达到触发条件时，**则** 下一步建议不发出请求（两者互斥，且不得由同一时刻的两次请求同时占用通道）。
3. **假设** 用户的输入来自输入法组合过程（拼音未上屏），**当** 组合进行中时，**则** 不触发下一步建议。
4. **假设** 用户在只读文档、大文件、多光标或空选区以外的选区中（与补全同一套跳过条件），**当** 停顿发生时，**则** 不触发下一步建议。
5. **假设** 单个请求超过时限（与补全同一口径的超时上限），**当** 超时发生时，**则** 按「无建议」处理并取消在途请求。

***

### 用户故事：开关、模型与设置项（优先级：P2）

作为用户，我希望下一步建议有自己的开关、默认不影响我，并且用我配的补全模型即可工作，以便我不想用的时候它绝不出现、想用的时候不必再配一套模型。

**为什么是这个优先级**：默认关闭 + 独立开关是这能力上线的安全前提（它会改用户已有代码，比补全风险高），但对已开启的用户不是额外功能。

**独立测试**：在设置页把开关关掉，验证任何停顿都不产生请求、不显示建议；开启后恢复；不做任何模型设置时验证建议仍能返回。

**验收场景**：

1. **假设** 下一步建议开关为关闭，**当** 用户在编辑器中任意输入与停顿，**则** 不发起任何下一步建议请求、不渲染建议（开关在 SDK 侧判定，关闭时连一次模型请求都不发出）。
2. **假设** 用户从未设置过开关，**当** 首次使用时，**则** 该能力处于关闭状态（与补全默认开启不同：本能力会改动用户已有代码，先关闭灰度）。
3. **假设** 用户未单独指定下一步建议所用模型，**当** 请求发出时，**则** 使用补全模型的那条解析链（补全模型 → 快速模型）；本规格**不要求**专用微调模型。
4. **假设** 用户拨动开关，**当** 拨动当下（不依赖底部「保存」，见 [agent-config.md](../core/agent-config.md)「设置页控件的保存时机」故事），**则** 该值立即写入 CLI 侧用户级设置文件的顶层键 `nextEditEnabled`，经 SDK 实时重载在下一轮生效，插件端不持有第二份配置。
5. **假设** 当前宿主没有代码编辑器（桌面端、远端网页等），**当** 渲染设置页时，**则** 不展示下一步建议的开关（与补全的宿主能力位判定同一套），CLI 侧键与语义照旧。

***

### 用户故事：建议的采纳度量（优先级：P3）

作为产品与研发，我希望下一步建议的展示与接受次数能像补全那样被记下来，以便判断这个能力是否值得保留。

**为什么是这个优先级**：没有指标不影响用户使用，但会影响我们是否继续投入，故为 P3。

**独立测试**：触发若干次建议并接受其中一部分，导出指标，验证展示与接受的计数与手工计数一致。

**验收场景**：

1. **假设** 指标导出已就绪，**当** 宿主渲染了一条下一步建议时，**则** 记一次「已展示」；**当** 用户接受它时，**则** 记一次「已接受」，两者与补全的采纳度量分开计（不得混入同一条比率）。

### 边界情况

* **通道独立于对话，也与补全分开排队。** 下一步建议走新增的会话级 RPC `getNextEdit`（请求含：编辑轨迹、建议候选区域、当前文件上下文、语言与文件路径），与补全的 `getCompletion` 平行；同一会话同一时刻只保留一个在途请求，新请求到达时旧请求被中止，因此迟到的响应不会渲染。补全与下一步建议**互斥**：两者不得在同一时刻各占一个在途请求。
* **建议的落点限定在「可编辑窗口」内。** 首期只预测离光标有限距离内的改动（窗口为默认值），更远的位置不主动预测——远处建议的接受率低且无法验证。跳转只在「已生成且不在视口内」时出现，不代表可以预测任意远处。
* **轨迹的隐私边界。** 发送的是**编辑差异**与当前文件上下文（与补全同源的精简片段），不上传整份文件历史、不上传仓库；轨迹只保留最近若干条，随会话结束即丢弃。
* **接受是唯一写入文档的动作。** 渲染、拒绝、跳转、失效都不写文档、不入撤销栈、不移动光标；只有接受会写入，且是一次独立的撤销步骤。
* **宿主行内建议 API 的能力边界。** 建议的产物是差异，而宿主的行内建议 API 未必能表达差异：VS Code 的行内补全只在**光标处的纯插入**时渲染，带非空替换区间的项会被 API 收下却不显示（2026-10-26 实测）；JetBrains 平台的行内补全同样只能往光标处插灰字。因此「怎么显示差异」由各宿主自绘（装饰层/浮层），接受动作也随之由宿主提供——这属于「渲染方式由宿主决定」的下限，本规格只要求**可观察行为一致**：用户能看到增删、能用一次动作接受、能一次撤销。
* **「建议在哪」由代码本身指示，浮层不承担这个职责。** 建议位置可以与光标不同，而浮层里的旧文本与编辑器里的文本往往只差几个字符，靠内容比对认位置不可靠；「只预测离光标有限距离内」也不保证目标行一定在视口内。因此宿主自绘差异时，被替换/删除的代码要在编辑器里就地标记，浮层只负责「改完长什么样」。这条是 2026-10-26 截图评审暴露的：浮层盖住了目标行、又没有行号，连我们自己都看不出建议落在第几行。Continue 两端是同一做法——IntelliJ 侧对被删字符加 `EffectType.STRIKEOUT` 高亮器、并把浮层定位到光标所在行行尾（`NextEditWindowManager.kt` 的 `renderDeletionDecorations` / `showCodePopup`），VS Code 侧是 SVG 窗口 + deletion decorations。
* **触发条件与补全共用一套宿主侧判断**（只读/未命名/虚拟文档、体积阈值、多光标与非空选区、输入法组合、文档版本漂移时丢弃响应、聚焦编辑器持有在途请求），本规格不重复定义，见 [代码补全](code-completion.md) 的同名边界情况。
* **不重复同行踩过的两个坑**：① 触发时刻与补全完全同一刻（Continue 的自陈问题）——本规格要求两者互斥而是不并列触发；② 最近一次编辑的差异采集不可靠时仍发起请求——本规格要求采集失败即放弃本次。
* **模型能力风险登记。** 首期不训练、不微调专用模型，用通用补全模型加结构化提示词实现；若实测接受率不可接受，退路是关闭该能力或引入专用模型，而不是降低触发门槛去凑数据。
* **不做本地推理**，与补全一致。

## 假设

* 用户在 IDE 中已登录 wave，且 CLI 侧已配置至少一个可用模型（或已配置快速模型作为回退）。
* 插件与 CLI 的 stdio 连接已建立（见 [Stdio 传输层](../ui/stdio-transport.md)）；下一步建议复用该连接与既有会话，不额外 spawn 进程。
* 两端（VS Code 扩展与 JetBrains 插件）共享请求链路与参数默认值，差异仅在宿主的建议渲染、快捷键接入、跳转提示与轨迹采集接口——具体渲染方式由各宿主平台能力决定，本规格只约束可观察行为。
* 规格中的数值（可编辑窗口、轨迹条数与时间窗、超时、防抖口径）均为**默认值**，可在实现与调优阶段调整，调整后需同步更新本规格。
* 采纳度量沿 [OpenTelemetry 集成](../enterprise/opentelemetry.md) 的指标族扩展，事件名与属性在该规格登记。
