首页
/ Cline VS Code 扩展:修复历史重开任务时隐藏的模式切换/续跑提示"复活"成用户消息的问题

Cline VS Code 扩展:修复历史重开任务时隐藏的模式切换/续跑提示"复活"成用户消息的问题

2026-09-04 19:47:44作者:劳婵绚Shirley

本篇围绕 Cline 仓库中一条变更说明 hidden-mode-prompts-history.md 展开:它修复了 VS Code 扩展(changeset 指向 claude-dev 包,patch 级修订)中的一个历史回放缺陷——当任务从历史记录中被重新打开时,Plan/Act 模式切换自动续跑的隐藏提示和 [TASK RESUMPTION] 任务续跑提示会再次以"用户消息"的身份出现在会话界面里。读懂这篇文章后,你将理解 Cline SDK 架构下"持久化对话历史"与"可视化转写记录"之间的映射机制、合成(synthetic)用户消息的判定规则,以及消息重新水合(rehydration)时如何正确隐藏这些提示并保留其模式语义。

一、缺陷本身:哪些提示是"隐藏"的,为什么会重新出现

Cline 的 Plan/Act 双模式与断点续跑能力依赖两类由运行时自动发送的提示词,它们会进入 SDK 的持久化消息历史,但在实时会话中从不作为用户气泡回显

提示 触发时机 发送方式
Act 模式续跑提示(常量 ACT_MODE_CONTINUATION_PROMPT"The user approved switching to act mode. Continue with the approved plan now." 用户在 Plan 模式下产出计划后批准切换 Act 模式 以 fire-and-forget 方式发出,实时界面不产生 user_feedback
任务续跑提示 任务中断后恢复执行时 内容以 [TASK RESUMPTION] 前缀开头,同样无可见气泡

这两类提示的共同特征是:存在于 SDK 历史中,但不在可视转写(visible transcript)里。问题出在从历史记录重开任务的路径上:旧实现把持久化的 SDK 消息逐条翻译成 ClineMessage 时,没有识别并跳过这些合成提示,于是重开任务后它们会"复活",渲染成一条看似用户发送过的消息,既误导用户,也会打乱后续消息的序号映射。

二、提示的注入源头:模式协调器与合成判定层

2.1 续跑提示从哪来

sdk-mode-coordinator.ts 中的 SdkModeCoordinator 负责用户发起的模式切换:当用户在下一条消息前切换了模式,协调器会把 <mode_notice> 元素(例如 "The user switched from plan mode to act mode before sending this message.")戳到下一条出站消息上;批准 Plan → Act 切换时,它再以 fire-and-forget 方式发送固定文案的续跑提示。测试 sdk-mode-coordinator.test.ts 多处断言了该文案的出现(约 L154、L189、L214、L312),可据此确认提示的确切措辞。

[TASK RESUMPTION] 提示则由任务控制/会话生命周期路径在恢复运行前注入,其文本前缀是固定的判定依据。

2.2 合成用户消息的判定规则

核心判定集中在 sdk-user-message-mapping.ts

  • ACT_MODE_CONTINUATION_PROMPT(第 9 行):续跑提示的常量定义。特意放在这个"叶子模块"而非协调器中,注释说明原因:展示层消费者(message-translator、ordinal 映射)不必为了一个字符串把协调器的重依赖图拉进测试。
  • isSyntheticUserPrompt(text)(第 50-66 行):先经 normalizeUserInput 剥掉 <user_input mode="..."> 包装、再经 stripModeNotices 剥掉可能前置的 <mode_notice>,然后匹配三种情形——[TASK RESUMPTION] 前缀、与续跑提示完全相等、或 <hook_context 前缀(Hook 注入的上下文,仅面向模型)。剥除 <mode_notice> 是细节中的关键:用户主动切换模式时,<mode_notice> 会拼在固定续跑文案前面,若不去除,合成提示会被误判为可见用户消息,导致其后的编辑/重生成(edit/regenerate)序号整体偏移一格。
  • isSyntheticSdkUserMessage(message)(第 129-139 行):在文本判定之上再加两层——metadata.displayRolesystem/status 的运行时消息(Hook 上下文、压缩摘要)直接判定为合成;以及"仅附件不算用户输入"规则(hasAttachmentBlocks 区分 tool_resultimagefile 块,携带图片/文件的续跑消息仍计为可见用户消息)。

2.3 为什么"跳过"它们会影响全局:序号映射

sdk-user-message-mapping.ts 中的 findSdkUserMessageIndexByOrdinal(第 146-160 行)把"N 条可见用户消息"(1-based 序号,对应 task/user_feedback 行)映射到持久化 SDK 历史中的下标。如果隐藏提示没有被跳过,其后的每一条用户消息映射都会提前一格,Edit / Regenerate / 断点选择等依赖该映射的功能会全部错位。getSdkCheckpointRunCountForMessageIndex(第 168-188 行)则体现了另一面:隐藏的模式/续跑提示虽然没有 webview 行,但仍推进 checkpoint 运行计数器(仅 recovery_notice 除外)——"不显示"与"不参与计数"是两件独立的事。

三、修复的落点:历史重新水合时跳过合成提示

3.1 历史重开任务的调用链

重开任务时,sdk-task-history.ts 中的 SdkTaskHistory.getClineMessages(taskId) 从 SDK 会话宿主读取持久化消息(host.readMessages),经 message-translator.tssdkMessagesToClineMessages(第 2306 行导出)翻译成 webview 消费的 ClineMessage[]。该翻译器头部的映射表(第 7-27 行)说明了 SDK 事件到 say/ask 类型的规则,例如 chunksay:"text"(partial)、attempt_completionsay:"completion_result" 等。修复正是发生在这一"从磁盘历史到可视消息"的翻译路径上:对 isSyntheticSdkUserMessage 判定为真的用户消息,不再生成 say:"user_feedback" 行(也不产生 say:"task" 行),从而让这些提示在重开后保持"隐藏"。

3.2 隐藏提示的模式语义仍要保留

隐藏的代价是:这一轮真正发生的模式(plan 还是 act)不再能从可见的用户消息上读出。翻译器因此保留了提示携带的模式信息,用于样式化该轮的"推断完成行":当一轮以纯文本收尾而非完成工具时,最后一条文本行会被就地重标(retag)为 say:"completion_result"(act/yolo,绿色完成框)或 say:"plan_completion_result"(plan,计划框),随后追加一条 ask:"completion_result",使重开的任务显示完成/续跑入口而不是卡住的转圈。

测试 sdk-task-history.test.ts 中的用例 hides the plan -> act auto-continuation prompt when rehydrating from history(第 263-285 行)覆盖了完整场景:持久化历史为「plan 模式提问 → 计划文本 → 带 <mode_notice> 的合成续跑提示(act 模式)→ "Implemented."」。断言有三层:

expect(result.filter((m) => m.say === "user_feedback")).toHaveLength(0)
expect(result.map((m) => m.text).join("\n")).not.toContain("The user approved switching to act mode")
// 隐藏提示仍携带该轮模式:完成行必须是 act 模式完成框,而不是计划框
expect(result).toContainEqual(expect.objectContaining({ type: "say", say: "completion_result", text: "Implemented." }))

即:续跑提示不出现、全文不再包含其文案、但完成行仍按 act 模式渲染(而非沿用上一轮的 plan 样式)。hides [TASK RESUMPTION] prompts when rehydrating from history(第 287-304 行)对 [TASK RESUMPTION] Please continue where you left off. 做了同构断言。

3.3 配套的边界规则(同组测试中的可验证事实)

同文件中的相邻用例还界定了"推断完成行"的触发边界,它们与隐藏提示逻辑共享同一套重水合规则:

  • 只有当会话记录 status === "completed" 时才重标终止文本(第 306-318 行用例);failed/cancelled/running/pending/idle 都不视为干净收尾(第 320-339 行,注释指出 idle 也可能是被中止回合的状态,不能信任)。
  • 以悬空 tool_use 结尾的转写不做重标(第 354-369 行)——中止回合的文本保持普通 say:"text"
  • 无会话记录(结局未知)时同样不重标(第 341-352 行)。

四、验证路径与延伸阅读

五、小结

这条 patch 级变更的本质,是在"持久化历史"与"可视转写"之间补上了对合成提示的正确处理:isSyntheticUserPrompt / isSyntheticSdkUserMessage 提供统一的判定(剥掉 <user_input><mode_notice> 包装后再匹配 [TASK RESUMPTION]、续跑提示全文、<hook_context 前缀与 displayRole 元数据),SdkTaskHistory.getClineMessages 在重水合时据此跳过这些消息,同时保留其携带的模式信息用于完成框样式。修复同时保住了两个不变量——重开的任务界面不再出现"假用户消息",以及可见用户消息与 SDK 历史下标之间的 ordinal 映射不发生偏移。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341