Skip to content

fix(tasks): preserve review status after session cancellation - #546

Merged
xintaofei merged 2 commits into
xintaofei:mainfrom
lizzjin:lizzjin/tasks/preserve-review-after-session-cancel
Aug 23, 2026
Merged

fix(tasks): preserve review status after session cancellation#546
xintaofei merged 2 commits into
xintaofei:mainfrom
lizzjin:lizzjin/tasks/preserve-review-after-session-cancel

Conversation

@lizzjin

@lizzjin lizzjin commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

我在这次修改中解决了 #543:任务完成后从会话继续聊天,任务状态被错误标记为“已取消”。

原本的问题

我确认任务正常完成并进入“待验收”状态后,如果用户回到对应会话继续聊天,或者此时触发会话状态同步,一个较晚到达的智能体“已取消”结束事件仍可能把任务状态从“待验收”改成“已取消”。

这会覆盖任务已经完成并等待用户验收的真实状态,让用户误以为任务被主动取消,同时丢失正确的后续处理入口。

问题根因

我检查后发现,实时会话结束回调和断线后的状态对账在处理 cancelled 事件时,都调用了通用的任务取消方法 cancel

这个通用方法还承担用户从任务面板明确执行“取消任务”的功能,因此它允许取消处于“待验收”状态的任务。问题在于,智能体产生的结束事件也复用了同一条状态转换路径:当旧事件延迟到达,或者任务对应的运行批次已经结束时,它仍然可以覆盖当前有效状态。

我做的修改

  1. 我新增了 cancel_running_generation,专门处理由任务引擎产生的取消事件。
  2. 我让这项数据库更新同时校验任务编号、运行批次 run_seq 和当前状态。只有同一运行批次且仍处于“运行中”或“等待输入”的任务,才能被引擎改为“已取消”。
  3. 我把实时会话的 cancelled 结束回调和断线后的会话状态对账都切换到这项受限操作,确保两条引擎取消路径遵循相同规则。
  4. 引擎确认取消当前运行批次时,我会同时清理会话连接和待合并状态,并记录对应的状态变化事件。
  5. 我保留了原有通用 cancel 方法的行为,用户仍然可以从任务面板明确取消处于“待验收”状态的任务。
  6. 我补充了两个回归测试:一个验证延迟到达的取消事件不会覆盖“待验收”状态,另一个验证当前运行批次收到取消事件后仍会正常进入“已取消”状态。

为什么这样修改

我没有简单地禁止取消所有处于“待验收”状态的任务,因为这会破坏用户主动取消任务的既有能力。这里需要区分的是取消事件的来源,而不是一刀切地收窄所有取消操作。

通过校验 run_seq,我可以阻止旧运行批次的事件影响新批次;通过限制当前状态,我可以阻止已经完成并进入“待验收”的任务被引擎事件回退;通过数据库单次条件更新完成校验和状态写入,我也避免了先读取、再判断、最后更新所产生的并发竞态。

这样既修复了延迟事件覆盖有效状态的问题,也保留了用户明确操作时应有的状态控制能力。

验证结果

  • 我运行了 cd src-tauri && cargo test --no-default-features --lib work_task,共 133 项测试通过,0 项失败。
  • 我运行了 git -c safe.directory=/workspace diff --check,检查通过。

Signed-off-by: lizzjin <lizzjin37@gmail.com>
@xintaofei
xintaofei marked this pull request as ready for review August 23, 2026 01:54
Share the conditional UPDATE between the user cancel and the engine's
generation-scoped one, parameterized by expected statuses, run_seq, actor and
reason, so the column set a cancel has to clear cannot drift between them (the
engine copy had already lost the scheduled_at clear).

Also pin the run_seq half of the engine CAS: a stale connection's cancelled
event must not land on the run after it, which is `running` again and passes
the status gate on its own.
@xintaofei

Copy link
Copy Markdown
Owner

感谢这个 PR 🙏 方向我认同,也已经在上面追加了一个小提交,准备合入。下面是这轮评审的结论,供你参考。

结论:改动本身是对的

cancel_running_generation 的 CAS(id + status ∈ {running, awaiting_input} + run_seq + deleted_at IS NULL)写法正确,事务/回滚的处理也和 failsettle_review 一致。引擎自己发起的跃迁本来就应该绑定代次——紧挨着的那条 fail 一直是 (&[Running, AwaitingInput], Some(run_seq)),偏偏 cancelled 这条走的是通用 cancel,这次正好把两条路径拉齐了。

cancel_running_generation 没有像通用 cancel 那样清 scheduled_at,这点我单独核过:set_schedule 只接受 todo 状态,而所有 claim 路径都会清掉计划,所以 running/awaiting_input 的行上 scheduled_at 恒为 NULL,实际不会出问题。

我追加的提交(5a12876

1. 把两条 cancel 合到同一条写入。 新增私有 cancel_inner(expected, run_seq, actor, reason)cancelcancel_running_generation 都变成薄封装。理由正是上面那条 scheduled_at——两份将近 40 行的重复 SQL 已经开始漂移了,合并之后列集合不可能再对不齐。用户手动取消的行为(状态集合、actor = "user"、reason 载荷的 trim/空值过滤)保持逐字不变。

2. 补上 run_seq 那一半的回归测试。 原来的两个用例其实都只钉住了「状态」这一半:a_late_cancelled_event_does_not_cancel_a_task_in_review 里 run_seq 是匹配的,真正拦住它的是状态闸门。新增的 a_previous_generation_cancelled_event_spares_the_current_run 走真实链路(settle_reviewclaim_for_runbegin_setupmark_running)进入下一代,再投递旧连接的 cancelled 事件,断言任务仍是 running。这条用例是 load-bearing 的:把 run_seq 过滤条件去掉后它会以 Canceled != Running 失败,加回来才通过。

校验:desktop cargo test --features test-utils 2901 passed / 0 failed、server --lib 2876 passed / 0 failed,desktop / server / mcp 三套 clippy -D warnings 全绿(前端未触及)。

一点需要说明的(不影响合并)

我没能在当前 main 上复现出 issue #543 描述的那条路径,静态看下来这两处改动都到不了「review 中的任务被引擎取消」:

  • TaskEngine::index 是连接→任务的唯一关联,只在 launch 时写入;而 on_turn_complete 在 settle 之前就把它删掉了(engine.rs:1991 早于 2047),所以同一个连接的第二个 TurnComplete 会在函数开头直接 return。
  • 从侧边栏重新打开会话拿到的是新的 UUID 连接(manager.rs:474),引擎从来没索引过它;前端也只有看板的取消弹窗会调 workTaskCancelchat_channel/** 里没有任何 work_task 耦合。
  • reconcile_once 只扫 running / awaiting 的行,而且和 on_event 跑在同一个 tokio::select! 循环里(engine.rs:322),没法与一次在途的 settle 交错。

所以我的处理是:这个 PR 作为「引擎跃迁必须绑定代次」的加固合入,但 #543 暂时不关,等拿到 Windows 上的实际复现再定位真正的写入方(最好能带上 task id、run_seq、前后两个 connection id,以及那条 status_changed 事件的 actor——加固之后引擎路径会记成 engine,和用户手动取消的 user 正好能区分开,这点顺带也帮了排查)。

另外评审途中找到一条真实的「陈旧索引」路径,也正是这个 PR 的守卫真正挡住的那种交错:recover_mergingmerging → review 弹回时不会清理 indexengine.rs:4142 附近),而残留的条目还会让 remove_worktree_locked 的 live-index 前置检查误判(engine.rs:4256)。这条我单独跟进,不塞进这个 PR。

再次感谢,测试和 PR 描述都写得很清楚 👍

@lizzjin

lizzjin commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

感谢你这么细致的评审,也感谢你直接补上提交 5a12876。把两条取消路径收口到 cancel_inner,并补充跨 run_seq 的回归测试,确实让这部分的行为边界更加完整,也避免了两套数据库写入逻辑后续继续产生差异。

我理解为什么暂时不关闭 #543:目前无法在最新的 main 上复现原问题,现有调用链也不足以证明“待验收”状态是被这两条引擎取消路径覆盖的。因此,这个 PR 更适合作为“引擎状态跃迁必须绑定运行批次”的加固,而不能直接认定已经解决了 #543

我可以再检查一下 #543,重点尝试复现 Windows 环境中的实际路径,并收集任务编号、run_seq、前后连接编号以及 status_changed 事件的 actor,继续追踪真正写入“已取消”状态的来源。如果有更明确的发现,我会先更新到 Issue 中。

再次感谢你的评审、补充测试和后续处理。

@xintaofei
xintaofei merged commit 0870d33 into xintaofei:main Aug 23, 2026
7 checks passed
xintaofei added a commit that referenced this pull request Aug 23, 2026
Google Antigravity joins the built-in agents.
Sub-agent transcripts and task details slide in from the right instead of opening as a dialog you have to close.

## New

- **Google Antigravity is a built-in agent.** Install and launch it from the agent list — history, resume, skills, experts, Office tools, MCP and delegation all wired up — with a settings page for its four sign-in methods. Apple Silicon, Linux and Windows; Google publishes no Intel Mac build.
- **Panels slide in beside your work instead of covering it.** The mobile sidebar, aux panel, terminal, settings navigation, skill details and task details are inset drawers now, with nothing dimmed behind them.
- **Session viewers stack.** Sub-agent, work-task and grok child transcripts nest inside one another rather than burying each other, and no longer close when their card scrolls away or you switch tabs.
- **Repository items open in a side panel.** The full description, every label and the same Start action — without leaving for the forge and losing your filters, page and scroll position.
- **Choose which navigation rows the sidebar shows.** Automations, To-dos and the Repository panel each switch off in the view options and stay reachable from quick actions.
- **A session takes its title as soon as the agent picks one**, along with any forum topic bound to it. (#526, @Adam-Dalloul)
- **Workspace backgrounds accept GIF**, and animated ones play through the blur and the frosted panels.
- **Reclaim a finished task's leftover worktree.** One button in the task drawer removes the checkout and its work branch; the task stays on the board.

## Improved

- **Updated bundled agents.** DeepSeek Harness 0.6.0, OpenCode 1.18.21, Cline 3.0.57.
- **DeepSeek Harness 0.6.0 support.** Image prompts keep their pictures, compaction reports tokens and elapsed time, and forking, image upload and multi-provider models turn on when the agent advertises them.
- **The sidebar's view options are sorted out.** Toggles move into submenus so Sort by and Section order come first, and expand/collapse-all becomes its own header button that folds the flat sections too.
- **System messages show a preview.** Claude Code's post-`/compact` summary was a shut accordion; it renders clamped now, with a toggle only when there is more to see.

## Fixed

- **Codex sessions created by other tools display properly.** One recorded without Codex's usual event channel came back as a bare "Used 25 tools". (#452)
- **pi keeps its status lines out of its answers**, and routes retries to the shared banner. (#525)
- **A proxy typed without `http://` no longer breaks agent installs**, which used to abort with an unexplained `ERR_INVALID_URL`; `socks5://` addresses are left alone.
- **Claude's model picker no longer lists `null`.** Binding a provider with no model wrote literal nulls into `~/.claude/settings.json`.
- **Office lock files stop previewing themselves**, each costing a tab and a watcher over a `~$` file that can never render.
- **A GitLab account shows its initials when its avatar can't load**, instead of an empty circle.
- **The comment written back on an issue stops linking to the wrong page**, and a locally merged task no longer reads as work that shipped.
- **A repository row keeps its task badge.** A stale lookup blanked it, so an issue already being worked on offered Start — and got a second task.
- **A finished task's worktree removal isn't refused.** Merge recovery left a dead connection behind that read as an agent still working in there.
- **A task in review survives a cancelled session.** (#546, @lizzjin)
- **An agent home written as `~/…` resolves to the right place.** Hermes built a literal `~` directory beside its launch directory; Antigravity and DeepSeek were refused write access to the tree they actually use.

Thanks to @Adam-Dalloul and @lizzjin for contributing to this release.

-----------------------------

# 发布版本 0.28.0

Google Antigravity 加入内置智能体。
子会话、任务详情改从右侧滑出,不再是看完必须关掉的弹窗。

## 新增

- **Google Antigravity 成为内置智能体。** 可从智能体列表安装启动,历史会话、恢复、技能、专家、Office 工具、MCP 与委托均已接通,并有独立设置页配置它的四种登录方式。支持 Apple 芯片 Mac、Linux 与 Windows;Google 未发布 Intel Mac 版本。
- **面板改从侧边滑出,不再盖住整个界面。** 移动端侧边栏、辅助面板、终端、设置导航、技能详情、任务详情都换成留边抽屉,背后不再压一层灰罩。
- **会话查看器可以层叠。** 子智能体、看板任务、grok 子会话的转写层层嵌套而不互相盖掉,卡片滚出屏幕或切换标签页时也不会自己关掉。
- **仓库条目在侧边面板里打开。** 完整描述、全部标签和同一个「开始」操作都在,不必跳去网页再回来重找筛选、页码和滚动位置。
- **侧边栏显示哪些导航入口可以自己选。** 自动化、待办任务、仓库面板都能单独关掉,关掉后仍可从快捷操作进入。
- **智能体刚定下标题,会话就立刻改名**,绑定的论坛话题同步跟上。(#526@Adam-Dalloul)
- **工作区背景支持 GIF**,动图在磨砂与模糊图层下照常播放。
- **已结束任务残留的工作树可单独回收。** 任务抽屉里一个按钮删掉检出目录与工作分支,任务仍留在看板上。

## 改进

- **内置智能体版本更新。** DeepSeek Harness 0.6.0、OpenCode 1.18.21、Cline 3.0.57。
- **适配 DeepSeek Harness 0.6.0。** 带图提问不再丢图,上下文压缩会显示 token 数与耗时;会话分叉、图片上传、多供应商模型在智能体自报支持时自动开启。
- **侧边栏显示选项重新归置。** 各类开关收进子菜单,排序方式与分区顺序回到最上;全部展开/折叠独立成标题栏按钮,也能折叠聊天、最近这类扁平分区。
- **系统消息直接显示预览。** Claude Code 执行 `/compact` 后的续写摘要此前只是个折叠条,现在限高显示,内容超出时才出现展开按钮。

## 修复

- **其他工具创建的 Codex 会话能正常显示了。** 缺少 Codex 常规事件通道的记录打开后只剩一句「使用了 25 个工具」。(#452)
- **pi 不再把自己的状态提示混进回答**,重试改走统一提示条。(#525)
- **代理地址没写 `http://` 不再让智能体装不上**,此前会以一句没头没尾的 `ERR_INVALID_URL` 失败;`socks5://` 等已带协议的地址保持原样。
- **Claude 的模型选择器不再列出一排 `null`。** 绑定没有配置模型的供应商时,会往 `~/.claude/settings.json` 里写入 null。
- **Office 锁文件不再自动预览**,此前每个 `~$` 文件都白占一个标签页和一个监听进程。
- **头像加载不出来时 GitLab 账号显示名称首字母**,不再是个空白圆圈。
- **回写到议题下的评论不再链到无关页面**,就地合并的任务也不再宣称成果已经推送。
- **仓库条目的任务标记不会莫名消失。** 过期查询会把它抹掉,于是已在处理的议题又显示「开始」,再点一次就多一个重复任务。
- **已完成任务的工作树不再拒绝删除。** 合并恢复留下的死连接被当成「还有智能体在里面干活」。
- **进入评审的任务不会被会话取消顶掉。**(#546@lizzjin)
- **写成 `~/…` 的智能体主目录能正确解析。** Hermes 会在启动目录旁建出真名为 `~` 的文件夹;Antigravity 与 DeepSeek 则反被挡在真正所在的目录之外。

感谢 @Adam-Dalloul@lizzjin 为本次发布做出的贡献。
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