Skip to content

feat(plan): enforce recoverable runtime plan state machine - #3

Merged
JuiXiang merged 2 commits into
mainfrom
dev
Sep 7, 2026
Merged

feat(plan): enforce recoverable runtime plan state machine#3
JuiXiang merged 2 commits into
mainfrom
dev

Conversation

@JuiXiang

@JuiXiang JuiXiang commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

变更摘要

将 Plan Mode 从提示词约束和 Shell 白名单升级为运行时强制执行的、可恢复的状态机。只有用户确认最新的 planId + revision + digest 后才能执行;普通回复、退出规划或恢复会话都不构成执行授权。

本 PR 为 dev → main,仅包含提交 bbd02c9,覆盖 Core、CLI/TUI、桌面端、回归测试和文档。

关联 Issue

无本仓库关联 Issue:本次依据已确认的 Plan Mode 改进方案实施,不自动关闭其他仓库的 Issue。

目标分支

  • 常规贡献:目标为 dev
  • 发布 PR:dev → main,由维护者评审和决定合并时机。
  • 紧急修复:目标为 main

实现说明

工具边界

  • 使用统一的 plan_access: observe | control | deny 分类,未声明的工具默认拒绝;工具注册和执行前检查采用同一分类。
  • Plan 中不注册 Bash、写入、编辑、测试、构建或包管理工具;grep/find 使用纯 Python 后端。
  • 新增 git_statusgit_loggit_diffgit_show。通过参数数组启动 Git,关闭 pager、提示、optional locks 和 fsmonitor;diff/show 禁用 external diff、textconv 和颜色,log 禁止输出 patch。
  • 原生 DeepSeek 搜索同样遵循 Plan 工具边界,不能绕过本地工具注册限制。保留 Default 模式的搜索能力和 Shell 重定向兼容行为。

显式提交与状态重放

  • 新增独占的 submit_plan({title, markdown}):仅 drafting 可用,成功后持久化 revision 并结束规划回合。普通文本、中止或截断的回复不能生成新 revision。
  • 新 revision 写入 v1 digest,将 schema、plan ID、revision、标题和正文共同纳入确认保护;标题最多 200 字符,正文最多 64 KiB,只将 CRLF 规范化为 LF。
  • Plan 状态由活动 JSONL 分支经 reducer 重放得到。非法顺序、错误 digest、问题/答案不匹配及损坏日志进入 recovery_error,禁止执行。

执行与交接

  • 执行前持久化 started,再注册 live run、启用 Default 工具;结束时持久化终态后通知客户端。
  • 无 live owner 的 started 恢复为 uncertain,不自动重试。终态写盘失败也释放运行所有权并重放状态。
  • 保留持久化状态 completed,客户端投影为 settled,仅表示 Agent 回合正常结束,不宣称任务已验证完成,也不据此生成任务完成记忆。
  • 新会话交接只复制确认的 revision 和来源元数据,保留父子会话关联,不复制规划对话、问题或工具输出。目标停在 ready,需再次确认。
  • 先持久化目标,再记录来源交接;失败时保留来源 ready 和已落盘的子会话,不自动执行或清理。

客户端与文档

  • Core 发布完整 plan.stateChanged 快照;扩展 session.snapshot,两端不再自行推进 Plan phase。
  • 桌面端保留上游虚拟化时间线,加入恢复卡片、交接操作及 RPC 拒绝后的快照刷新。
  • TUI 在启动、恢复和分支切换后重建问题/计划控件;裸 /plan 显示状态菜单,新增 --handoff-plan [REVISION],保留旧命令别名。
  • 恢复 Plan Mode 规范与 ADR,更新中英文 README 链接;CI 的测试、构建和桌面任务覆盖 Windows、Ubuntu。

兼容性与限制

  • 继续读取并验证 v0 digest,不重写历史;只有 <proposed_plan> 文本的旧会话显示为候选,必须重新提交后才能执行。
  • Plan 状态仍是 branch-local,不改成 session-global。
  • 不提供 OS 沙箱或通用 hostile Git config 沙箱;external diff/textconv 以外的 Git 配置仍属于 Project Trust 边界。
  • 取消、停止、切换分支或关闭会话不会回滚文件、Git 或外部副作用。

测试计划

以下检查已在 Windows 本地完成:

  • uv run ruff check .
  • uv run pyright --project pyrightconfig.release.json
  • uv run pytest -q:998 passed,1 skipped。
  • uv build --all-packages
  • uv run python scripts/check_versions.py
  • uv run coding-agent --help
  • 桌面端 pnpm test:54 passed。
  • 桌面端 pnpm typecheckpnpm build

补充验证:

  • 提交 bbd02c9push CI 已通过:Windows、Ubuntu 的 test/build/desktop 共 6 项任务均成功。PR 触发的检查以本页面后续结果为准。
  • 回归覆盖 Shell 绕过、恶意 Git helper sentinel、独占提交、stale revision、损坏 JSONL、写盘失败、崩溃恢复和交接隔离。
  • 使用模拟模型响应覆盖“提问/回答 → 提交 → 修改 revision → 拒绝旧 revision → 新会话复核 → 执行 → settled”的端到端流程。
  • 尚未进行真实模型的手动端到端冒烟或 TUI/桌面截图核验。

提交前检查

  • 已选择 dev → main,并核对最新远端基线。
  • PR 仅包含 Plan Mode 改进及与上游功能合并所需的适配。
  • 新增和变更行为已有回归测试覆盖。
  • 面向用户的行为与接口变化已更新 README、规范和 ADR。
  • 发布版本与 CHANGELOG.md 更新由维护者在发布流程中确认;本 PR 不更改版本号。
  • 未提交凭据、会话、日志、缓存、构建产物或无关本地脚本。
  • 已阅读 CONTRIBUTING.md;按 dev → main 发布 PR 流程提交评审,不自动合并。

@JuiXiang
JuiXiang requested a review from SSK988I September 7, 2026 15:43

@SSK988I SSK988I left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审核基于 bbd02c9。建议修复两个已复现的 P2 问题后再合并:

  1. 新会话交接后,继续复核或修改时模型上下文缺少交接的计划正文。
  2. JSONL 尾部截断且没有换行时,取消规划记录会与损坏行拼接,重开会话后恢复操作失效。

验证:在 Windows 上运行 test_plan_lifecycle.py、test_plan_mode.py、test_plan_tui_state.py、test_plan_tool_access.py 和 test_tools_git.py,共 120 passed。另以两个独立用例分别捕获交接后首次模型请求的上下文,以及验证截断日志取消后再次打开的状态;两项均复现失败。具体触发条件、影响及修复建议见对应行内评论。

reason="handoff",
related_session_id=source.header.id,
)
target.append_plan_revision(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 将交接 revision 提供给后续复核/修改的模型上下文

这里仅把计划保存为 PlanRevisionEntry,随后 _attach_session_manager 清空消息;build_session_context 只从消息条目重建对话,而有效 system prompt 也没有包含交接 revision。因此新会话虽然在 UI 显示计划,用户选择“继续修改”并发送“复核交接方案并增加回滚步骤”时,模型只收到这句要求,收不到原计划正文。已用带唯一标记的计划交接后捕获首次 stream_fn 的 system_prompt/messages,确认标记不存在。请在子会话继续复核/修改时将该 revision 作为明确的计划上下文提供给模型(保持规划历史隔离和再次确认要求),并增加上下文级回归测试。

with open(path, "ab", buffering=0) as stream:
offset = stream.tell()
try:
written = stream.write(encoded)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 在损坏且无换行的 JSONL 尾部之后保留新记录边界

append 模式会将 encoded 直接接到文件末尾。如果进程崩溃留下不带换行的半条 JSON(例如末尾为 {"type":),read_entries 能识别 recovery_error,但用户取消规划时,新 CollaborationModeChangeEntry 会拼进同一损坏行。当前内存显示 cancelled,重新打开后该取消记录被整行丢弃,状态又变成 recovery_error;重新规划后的后续条目也可能出现悬空 parentId。已用“ready → 追加无换行截断尾部 → 重开 → cancel_plan_mode → 再重开”复现。请在保留损坏历史的同时确保后续追加记录从独立行开始,并补充无末尾换行的恢复回归测试;现有坏行测试都额外写入了换行,因此未覆盖此情况。

Handoff revisions were persisted outside model messages, leaving review prompts without the source plan. Project the active handed-off revision into planning context without copying source dialogue or authorizing execution.

Unterminated JSONL tails swallowed later recovery entries. Append a separator when needed while preserving damaged history and rolling back the separator on write failure.

Add regression coverage for attached and reopened handoff review, branch-local context, truncated-tail recovery, valid tail endings, and failed append rollback. Local validation: 1011 Python tests passed (1 skipped), 54 desktop tests passed; Ruff, Pyright, Python builds, desktop typecheck/build, version checks and CLI smoke passed.
@JuiXiang
JuiXiang merged commit ae616af into main Sep 7, 2026
12 checks passed
@JuiXiang
JuiXiang deleted the dev branch September 7, 2026 16:28
@JuiXiang
JuiXiang restored the dev branch September 7, 2026 16:30

@JuiXiang JuiXiang left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

交接上下文:已在 d43ec0c 修复。模型可读取交接计划,保留来源聊天隔离和再次确认要求;已补充恢复、复核及分支切换测试。
JSONL 尾部:已在 d43ec0c 修复。追加前补齐行边界,保留损坏历史;已覆盖取消、重新规划及写入失败回退测试。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants