feat(todo): Todo Schema v7重构与运行时洞察UI全链路接入#558
Conversation
- Budget 常驻 StatusBar + 点击 popover 展示 ledger 对账详情 - 聊天流内联:Checkpoint 微标识(一键撤回)、Verification 折叠卡、Acceptance 决策卡 - 右侧 InsightPanel + Todo/Verification/Checkpoint 三 tab - switchSession 并发预拉取 session.todos.list + checkpoint.list - eventBridge 改造:verify/acceptance/checkpoint 事件直接生成聊天内联消息 - 新增 11 个单测,Web 42/42 全绿 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- session/todo.go:CurrentTodoVersion 6→7,新不变量 blocked_reason!="" ⇔ status=="blocked";去掉 BlockedReasonValue 兼容方法,wire 出口直读 raw 字段(omitempty 自动隐藏空值) - ReplaceTodos / todo_write action="plan" 拒绝空 items,工具层与 session 层双层守卫;清空意图请走 set_status / remove - TodoStrip 仅在 status=blocked 时渲染 blocked_reason;useRuntimeInsightStore.setTodoSnapshot 对空 items 视为 no-op,前端兜底防回归 - 新增 normalize/wire/lifecycle 不变量测试,docs/todo-schema-migration.md 同步重写 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BudgetResolver 接口从 (int, string, error) 改为返回 BudgetResolution 结构体, ContextWindow 从模型目录经 TurnBudgetSnapshot -> TurnBudgetDecision -> BudgetCheckedPayload 一路传递到前端 BudgetIndicator popover 中展示。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nto html_gui_build
- Budget 常驻 StatusBar + 点击 popover 展示 ledger 对账详情 - 聊天流内联:Checkpoint 微标识(一键撤回)、Verification 折叠卡、Acceptance 决策卡 - 右侧 InsightPanel + Todo/Verification/Checkpoint 三 tab - switchSession 并发预拉取 session.todos.list + checkpoint.list - eventBridge 改造:verify/acceptance/checkpoint 事件直接生成聊天内联消息 - 新增 11 个单测,Web 42/42 全绿 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- session/todo.go:CurrentTodoVersion 6→7,新不变量 blocked_reason!="" ⇔ status=="blocked";去掉 BlockedReasonValue 兼容方法,wire 出口直读 raw 字段(omitempty 自动隐藏空值) - ReplaceTodos / todo_write action="plan" 拒绝空 items,工具层与 session 层双层守卫;清空意图请走 set_status / remove - TodoStrip 仅在 status=blocked 时渲染 blocked_reason;useRuntimeInsightStore.setTodoSnapshot 对空 items 视为 no-op,前端兜底防回归 - 新增 normalize/wire/lifecycle 不变量测试,docs/todo-schema-migration.md 同步重写 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BudgetResolver 接口从 (int, string, error) 改为返回 BudgetResolution 结构体, ContextWindow 从模型目录经 TurnBudgetSnapshot -> TurnBudgetDecision -> BudgetCheckedPayload 一路传递到前端 BudgetIndicator popover 中展示。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nto html_gui_build
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
发现 3 个值得处理的问题:
-
web/src/utils/eventBridge.ts
新增了_latestVerificationMsgId/_latestDoneToolCallId/_latestCheckpointId这组模块级游标,并提供了resetEventBridgeCursors(),但没有把它接到会话切换、新建会话或清空聊天的主路径上。这样上一会话残留的_latestCheckpointId会被下一会话第一次ToolStart直接复用到fileChanges,用户在新会话里点“拒绝更改”时会尝试恢复旧会话的 checkpoint。其余两个游标也会继续跨会话串线。 -
internal/runtime/todo_run_boundary.go
shouldResetTodosForUserRun()现在只对白名单里的 5 个精确字符串返回“续做当前任务”。真实对话里更常见的是继续修这个、继续刚才的改动、continue with the failing test这类带补充语义的输入,它们都会落到default分支并清空现有 todo。这样会把用户明确想续做时的 in-progress / blocked todo 误删掉。 -
internal/runtime/run.go
多文件tool_diff这里只靠WasNew折算成added/modified,没有保留“删除”或“未变化”的信息。结果是move/copy的源文件会被前端当成modified,而delete/remove_dir这类路径也无法在多文件场景下标成deleted。另外toolResultMultiDiffs()上游会把所有 pre-snapshot 都塞进 payload,像copy的 source 即使 diff 为空也会进入列表,文件变更面板会出现一个 0/0 的“modified”条目。
其余代码质量 / 性能 / 安全 / 文档检查未见需要单独指出的问题。
| return false | ||
| } | ||
| switch goal { | ||
| case "continue", "继续", "继续执行", "继续任务", "继续上一个任务": |
There was a problem hiding this comment.
这里把“续做当前任务”的识别做成了精确匹配;像 继续修这个、继续刚才的改动、continue with the failing test 这类更常见的 follow-up 输入都会误判成新任务并清空 todo。
修复 PR 1024XEngineer#558 review 指出的白名单过窄问题。原实现仅识别 5 个精确字符串, "继续修这个"/"continue with X"/"接着做"/"继续。"等带补充语义或标点的输入 都会落到 default 分支并清空 in_progress/blocked todo。 调整为前缀+标点容忍策略: - TrimRight 去除尾部中英文标点 - 中文前缀:继续 / 接着 / 续做 / 再继续 / 再来 - 英文前缀:continue / keep going / keep doing / go on / resume / carry on 英文要求精确或后跟空格,避免 "keep it simple" 单词误命中 新增 TestShouldResetTodosForUserRunContinueVariants 覆盖 25 个用例。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
修复 PR 1024XEngineer#558 review 指出的多文件 tool_diff change kind 不全问题。 原实现仅基于 WasNew 做 added/modified 二分,导致: - move 的 source 文件被标为 modified(实际应为 deleted) - delete_file/remove_dir 多文件场景无法标记 deleted - copy 的 source(diff 为空)进入面板,显示 0/0 modified 占位 调整方案(向后兼容): - file_snapshot.go 新增 Kind() 与 FileChangeKind* 常量,根据 pre.existed 与 post 存在性输出 added/modified/deleted/unchanged - toolexec.go 收集层调用 Kind(),unchanged 直接跳过(过滤 copy 源) - events.go FileDiffEntry 加 Kind 字段(omitempty 保兼容) - run.go buildToolDiffPayload 优先取 entry.Kind,缺失时回退 WasNew 二分 - toolResultMultiDiffs 兜底再过滤一次 unchanged 测试: - file_snapshot_test.go 新增 TestFileSnapshotKind 覆盖 4 态 - tool_diff_helpers_test.go 新增 metadata kind 优先 / unchanged 过滤 / deleted 保留 / 兼容 fallback 共 4 个 case Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
修复 PR 1024XEngineer#558 review 指出的跨会话游标泄漏问题。eventBridge.ts 中的 _latestVerificationMsgId / _latestDoneToolCallId / _latestCheckpointId 仅在 MessageItem.tsx restoreCheckpoint 后调用 resetEventBridgeCursors(), 未覆盖会话切换/新建/清空入口。 后果:用户切换到新会话后,首次 ToolStart 写入的 fileChange 会继承上一会话 的 _latestCheckpointId,点击拒绝更改时会回退旧会话的 checkpoint。 修复:在 useChatStore.clearMessages 内部统一调用 resetEventBridgeCursors()。 switchSession/createSession/prepareNewChat 已统一走 clearMessages, 单点收敛保证一致;MessageItem.tsx 显式调用保留(restoreCheckpoint 不清 messages)。 测试: - eventBridge.test.ts beforeEach 新增 resetEventBridgeCursors + fileChanges 重置 保证测试隔离 - 新增 "clearMessages resets eventBridge cursors so new session does not inherit prior checkpoint" 验证完整跨会话场景 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| } | ||
|
|
||
| // continuationChinesePrefixes 中文续做关键词,落到 strings.HasPrefix 直接匹配。 | ||
| var continuationChinesePrefixes = []string{"继续", "接着", "续做", "再继续", "再来"} |
There was a problem hiding this comment.
结论
是,但范围要缩小一点说。
当前这版里,只有“新一轮用户 Run 开始时,要不要保留上一轮的 todo”这个边界判断,是靠用户输入文本做启发式匹配,不是语义级判定。
代码链路是:
internal/runtime/run.go:146先把本轮用户输入收敛成state.userGoalinternal/runtime/run.go:153紧接着调用resetTodosForUserRun(...)internal/runtime/todo_run_boundary.go:16里如果shouldResetTodosForUserRun(state.userGoal)返回false,才会保留旧 todointernal/runtime/todo_run_boundary.go:46这个函数本身就是:trim/lowercase/去尾标点之后,再走isContinuationIntentinternal/runtime/todo_run_boundary.go:62和internal/runtime/todo_run_boundary.go:65里是硬编码关键词,中文走HasPrefix,英文走“精确匹配或前缀+空白”
所以严格讲,现在保留/清空旧 todo 的入口条件,确实是特殊文本模式匹配。
补充说明
但 todo 状态本身 不是靠文本保存的,而是结构化会话状态:
internal/session/todo.go:64的Session.Todos []TodoIteminternal/runtime/todo_snapshot.go:9只是把结构化 todo 转成快照/事件给外层消费
也就是说:
todo 是否存在、内容是什么、状态怎么演进:是结构化数据新一轮用户输入进来后,沿用旧 todo 还是清空旧 todo:目前是文本启发式
如果你问的是 fennoai 前面指出的风险,那它说的是对的:这个判定现在仍然是 heuristic,不是显式的任务边界协议。
There was a problem hiding this comment.
这是你那个没完成得妥协政策,判断继不继续来清不清空todo,否则verify不过
| "enum": blockedReasonEnum, | ||
| "type": "string", | ||
| "enum": blockedReasonEnum, | ||
| "description": "仅当 status == \"blocked\" 时填写;其他状态请省略本字段。unknown 仅用于\"已经阻塞但无法给出具体原因\"的场景。", |
概述
本PR完成了Todo Schema v7重构,并实现了运行时洞察UI的全链路接入,同时增强了Gateway协议与前端预算面板的上下文传递能力。
主要变更
1. Todo Schema v7重构
docs/todo-schema-migration.md迁移指南2. 运行时洞察UI全链路接入
eventBridge.ts完整实现运行时事件到UI的映射3. 文件变更面板增强
patchParser.ts支持Git patch格式解析FileChangePanel.tsx实时查看代码修改4. Gateway协议扩展
jsonrpc.go新增135行协议支持5. 预算面板上下文传递
6. 测试覆盖
变更统计
测试验证