Skip to content

fix(chat): preserve interrupted history for cancelled turns - #977

Open
dangzitou wants to merge 2 commits into
TencentCloud:developfrom
dangzitou:codex/fix-cancelled-turn-history
Open

dangzitou wants to merge 2 commits into
TencentCloud:developfrom
dangzitou:codex/fix-cancelled-turn-history

Conversation

@dangzitou

@dangzitou dangzitou commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #944. 从 #942 拆出的独立取消状态修复,仅涉及回合取消与历史落库。

harness 收到取消后会正常结束异步迭代器,原先下游把部分回复记录成 complete。现在 AgentManager 为当前活动调用追踪取消并发出内部标记,共享 RecordingTracker 据此将回合保留为 interrupted。Dashboard、IM 和 HITL 恢复共用同一判定,新旧历史格式后续再次收尾也不会将中断覆盖为完成。

  • 取消标记只属于活动调用;正常结束、异常或关闭迭代器均清理,空闲时取消不积累状态,不影响下次回合或其他线程。
  • 保留部分回复和既有计费记录;内部标记不透传给 Dashboard。
  • 不改变已有停止入口;fix(chat): request cancellation over HTTP without optimistic completion #876 的 HTTP 停止请求是独立改动。

Target branch

  • Base is develop
  • Base is main

Type of change

  • Bug fix

Test plan

  • 在 ec70e5f4 rebase 后完成本地核对:合并 Unreleased changelog 冲突,功能补丁的 stable patch-id 未变,git diff --check 通过。此前本地 versioned history / WebSocket / HITL 回归 75 passed;最新 Linux / Windows 结果见 GitHub Checks。
  • 此前 rebase 到 develop 的 963cb140(head b421e8dd):功能 diff 未变;agent manager / HITL / versioned history / WebSocket 定向回归 177 passed, 1 skipped。
  • 首轮 rebase 到 develop 的 728ca521 后,make all PYTEST_JOBS=2 通过:Ruff / 格式 / mypy;3733 passed, 17 skipped。
  • 随后同步到 develop 的 6f7815bb,功能 diff 未变。此前 head 306a5fb5 的 make lint typecheck 及 agent manager / HITL / versioned history / WebSocket 回归通过:177 passed, 1 skipped。
  • 覆盖活动调用取消与清理、跨线程隔离、Dashboard / IM / HITL 的新旧历史格式,以及 WebSocket cancel 到 SQLite 的中断状态落库。
  • 保留 fix(history): 回合中断时写入终态 is_error 与失败原因 #940 的 error chunk / CancelledError 路径,仅处理本 PR 的内部取消标记与历史状态。

本地验证使用 macOS / Python 3.14。上述全量结果来自最后一次仅同步上游之前;最新推送 head 的 Linux / Windows 全量结果以 GitHub CI 为准。

Reproduction evidence (before / after)

WS turn cancelled mid-stream (harness cancel semantics — generator returns cleanly): on develop the turn is archived status='complete' with no client signal; on this branch it is archived status='interrupted', partial content preserved, no internal marker leaked.

Before (develop @ 4667bd7b):

before

After (this branch):

after

Red-test run (this PR's new tests executed against origin/develop, i.e. the bug state):

########## BEFORE: new tests on origin/develop ##########
$ pytest tests/unit/gateway/test_versioned_history.py -k cancelled_processor -q
6 failed, 6 passed                                  # the failures are exactly the cancelled=True parametrizations
E   AssertionError: assert 'complete' == 'interrupted'   # turn archived complete after a user cancel

########## AFTER: this PR head ##########
$ pytest tests/unit/gateway/test_versioned_history.py -k cancelled_processor -q
12 passed                                          # dashboard/im/resume x v1/v2 x cancelled
$ pytest tests/integration/test_chat_ws.py -k cancel_persists -q
1 passed                                           # real WS cancel frame -> history_v2 row archived interrupted
$ pytest tests/unit/agents/test_agent_manager.py -k stream_cancellation -q
8 passed                                           # marker belongs to the active invocation only; cross-thread isolation; cleanup on every ending

Marker leak check — octop_stream_cancelled is appended only by AgentManager.stream / resume_hitl after the harness stream returns cleanly. Both processor consumers (iter_turn_chunks, iter_hitl_resume_chunks) call history_tracker.observe() first and then continue, so the marker both drives the interrupted status and never reaches the client or triggers a flush. The IM projection path stream_project._project_chunks also observes before dispatch, and its if/elif chain matches no branch for this type, so nothing is emitted. Team rooms ignore it (_RELAY_CHUNK_TYPES does not include it) and cron/delivery.py ignores unknown chunk types as before.

Checklist

  • Updated CHANGELOG.md
  • README / docs:无新增接口或配置,无需更新。

@dangzitou

Copy link
Copy Markdown
Contributor Author

@jubaoliang 按你在 #942 的建议,将取消回合的历史落库修复独立到这个 PR,OAuth 安全修复留在 #942。

取消状态按当前 invocation 跟踪,并通过共享的历史记录逻辑保留 interrupted 状态,避免旧版 Dashboard/HITL 后续收尾又将其覆盖为 complete。已提交正常/取消、Dashboard/IM/resume、v2/legacy 路径的回归测试,以及实际 WebSocket 取消后检查 SQLite 历史状态的测试。

本地 make all PYTEST_JOBS=4 通过(3658 passed, 17 skipped),提交钩子和 dashboard 构建通过。当前提交 4778a02 的 Linux CI 已通过,Windows CI 仍在运行。烦请独立评审这部分,谢谢!

@dangzitou
dangzitou force-pushed the codex/fix-cancelled-turn-history branch from 4778a02 to 8438d58 Compare September 22, 2026 14:05
@jubaoliang

Copy link
Copy Markdown
Collaborator

对照最新 develop 评估:点停止后 history 仍标 complete,值得合。和 #940 不是重复——本 PR 修的是 harness 干净收尾,#940 修的是 error chunk / CancelledError 轨迹终态。

现在只撞 CHANGELOG.md。请 rebase 最新 develop 后再推。不要改 processor.py 里属于 #940 的那条路径。

@dangzitou

Copy link
Copy Markdown
Contributor Author

@jubaoliang 已 rebase 到最新拉取的 develop(6f7815bb),当前 head 为 306a5fb5。CHANGELOG 冲突已处理,功能 diff 未变;保留 #940 的 error chunk / CancelledError 路径,本 PR 只增加内部取消标记及 interrupted 历史落库处理。

验证:首轮 rebase 后 make all PYTEST_JOBS=2 通过(3733 passed / 17 skipped);最后同步上游后 lint、mypy 以及 agent manager / HITL / versioned history / WebSocket 回归通过(177 passed / 1 skipped)。PR 描述已区分全量测试基线与最终 head 的补跑范围,最新 Linux / Windows CI 等待本次推送后的结果。

请按你的节奏测试;后续我会继续跟进 develop 并及时 rebase。

@dangzitou
dangzitou force-pushed the codex/fix-cancelled-turn-history branch from 306a5fb to b421e8d Compare September 23, 2026 09:54
@dangzitou

Copy link
Copy Markdown
Contributor Author

@jubaoliang 同步到新 develop(963cb140),head b421e8dd。这次只需合并 CHANGELOG 条目,功能 diff 逐行未变,未触碰 #940 的取消错误路径。本地 agent manager / HITL / versioned history / WebSocket 回归 177 passed / 1 skipped。新 head 的 Linux / Windows CI 正在运行,我会继续跟进。

@dangzitou
dangzitou force-pushed the codex/fix-cancelled-turn-history branch 20 times, most recently from 5c03212 to 0fef591 Compare September 27, 2026 10:29
@dangzitou
dangzitou force-pushed the codex/fix-cancelled-turn-history branch 12 times, most recently from 183f841 to 4496981 Compare October 3, 2026 19:04
@dangzitou
dangzitou force-pushed the codex/fix-cancelled-turn-history branch from 4496981 to 00d1a56 Compare October 4, 2026 13:42
@dangzitou

Copy link
Copy Markdown
Contributor Author

@jubaoliang 这个是自己用的时候踩到的:点了停止之后,历史里半截回答还是标成 complete,看着像答案给全了,其实没有。

你 9/23 看过说值得合,当时只提了 rebase CHANGELOG,后来也一直跟着最新 develop 在走,刚又推了一版。修复就一件事:取消的回合保留 interrupted 状态,没动 #940 那条路径。

正文最底下贴了修复前后的验证截图,麻烦有空再看一眼。

@dangzitou
dangzitou force-pushed the codex/fix-cancelled-turn-history branch from 00d1a56 to 79a8be6 Compare October 5, 2026 17:04
@dangzitou
dangzitou force-pushed the codex/fix-cancelled-turn-history branch from 79a8be6 to 84ec01a Compare October 8, 2026 18:16

This branch has not been deployed

No deployments
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