Repository navigation
Prevent duplicate private bot retries after a resend - #8651
loopx-agent wants to merge 3 commits into
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max
本次评审对应提交 a5ec7db92250ef0dcb5d6cfd6c1ce4336dc2db88。
[P2] 识别已排队但外层回执未写完的重发。 实际回读仍有一份原失败和一个重发 queued Turn。head 把旧请求恢复再排入队列,队列从一条增至两条,并多发一条排队通知。随后重投重发事件复用原 Turn,两条队列记录仍在。 证据读取
动机 / Motivation
在私聊收到额度或传输失败、随后重新发送同一文字的操作者。私聊机器人先报告原请求的额度失败。用户在同一 Session 重新发送相同文字,重发还在队列中时,恢复 worker 可能再为原请求排一个 Turn。新版本在重发回执完整写入时保留原失败和重发队列,阻止额外排队。其他来源及不同文字仍能按原规则恢复。使用隔离 Lark worker、真实 Core 队列和 TypeScript planner,外部 Provider 和模型读回由合成 fixture 提供。实际 Discord、已安装私聊和模型输出尚未验证。Core 已持久化同一 Session 的重发 Turn,但外层回执写入中断时,worker 仍多排一个原请求的恢复 Turn。
改动思路 / Approach
新规则复用了 typed planner 和现有 source fence。它目前只从外层回执确认重发,写入中断时会漏掉已提交的 Core Turn。在原 source fence 下复用准确 client Turn 的已排队回执,修复范围落在重发与旧请求恢复的对账。 Python 在原来的失效缓存中保留摘要、来源和 Session,避免重复读取未变化的消息体。TypeScript 先核对已准入恢复的原始身份,再用后来输入阻止新排队。正常输入的对照表明现有 owner 足够,缺口在重发已经进队列但外层记录还未确认。
具体改动 / Changes
关键代码讲解
_ordinary_request_text_digest和source_control_fields保留有资格的普通私聊文本摘要。回执需为 accepted 并带有 Session 和 Turn。 发现投影_context_recovery_plan在失败 Codex 请求中查同一 binding、source、Session 的较新同文输入,并把已有布尔值交给 TypeScript。planContextRecovery保留已准入恢复的相关性修复,再拦截新准入。 Python 入口 类型化判断_admit_prepared先持久化 Core 队列,再更新外层 accepted/turn_id。故障注入刻意中断第二笔写入。常规 Lark 测试和 TS 新断言分别保护完整重发回执及已经准入的恢复。 写入边界 测试
按 docs/product/releases/v2.1.1.md 的 708c68e7040559b2a4368002439570b90bc080b2 审核。Original-Context 的失败与交付保持不变。Verified-Continuation 仍须用现有 Core 回执处理重发的部分写入。 发布契约
对主干的风险 / Risks
正常同文重发在 base 会从一条变两条,在 head 保持一条。故障注入只中断外层 accepted 写入,真实 Core 已有重发和 turn.queued 事件。两个版本的 worker 都再排一次旧请求。重投同一重发事件能对账到已有 Turn,但无法撤掉已经多排的恢复。其他来源和不同文本仍保持原有行为。使用同一 source fence 和 client Turn 身份即可完成缺失的验证。
语义与 CI 对齐
PASS,标准 premerge 的 6 项直接检查、10 个 canary 和 63 项原生 TypeScript 测试。File 夹具的四组 base/head 实际 worker 对照保留了原失败字节及通知。FAIL,部分提交后仍多排一个恢复 Turn。验证使用合成传输及模型读回。
我的整体评价 / Verdict
REQUEST_CHANGES。完整回执的同文重发已不再多排,原失败及独立请求路径保留。Core 排队与外层记录有独立的写入结果,恢复先运行时仍会多排。下一版需要使用原 client Turn 读回,并覆盖回执中断和重投顺序。本次评审覆盖现有宿主与 CLI 行为。使用隔离 Lark worker、真实 Core 队列和 TypeScript planner,外部 Provider 和模型读回由合成 fixture 提供。实际 Discord、已安装私聊和模型输出尚未验证。
English verdict: REQUEST_CHANGES. Head a5ec7db handles a fully recorded resend, but a committed Core queue entry with a lost outer receipt still triggers a duplicate recovery Turn; reconcile it under the existing source fence.
| """Match only canonical, admitted private text; omit the body from discovery.""" | ||
| message = row.get("message") | ||
| source = row.get("source") | ||
| if (row.get("status") != "accepted" or row.get("command") is not None |
There was a problem hiding this comment.
[P2] 识别已排队但外层回执未写完的重发。 同一私聊重发相同文字。Core 成功创建并记录 queued Turn 后,外层 accepted 回执尚未写入即中断,旧失败回报 worker 先于重发回执修复运行。实际回读仍有一份原失败和一个重发 queued Turn。head 把旧请求恢复再排入队列,队列从一条增至两条,并多发一条排队通知。随后重投重发事件复用原 Turn,两条队列记录仍在。admit 写入 session_id 后调用 enqueue_turn,外层 accepted/turn_id 在另一笔文件写入中落盘。_ordinary_request_text_digest 只认 accepted 且含 turn_id 的行,prepared 因而被排除。在现有 source fence 内读取重发的准确 client Turn receipt。当同一来源和 Session 的 prepared 记录已有真实排队回执时,先复用现有对账和抑制规则,保留未提交请求及已准入恢复的处理。用隔离 Lark worker 在真实 Core enqueue 后拒绝外层回执写入,先执行旧请求的恢复,再重投同一重发事件,断言只有一个 queued Turn,原失败回执及通知保留。
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max
本次评审对应提交 f86e81d91ae06e4021f5b368d0cb9059d418bdf5。
[P2] 修复新增 fixture 的 Ruff F811。 新测试直接声明导入的 ordinary fixture,缺少本文件已有的局部 F811 标注。相同 Ruff 命令在原文件无诊断,新 head 在第 190 行报错。
[P2] 保留扫描测试的两条独立输入。 扫描测试用同一来源和 Session 的两条相同文字证明读取只做一遍。后输入现在合法抑制前一条,原测试仍要求两条都是候选,在进入共享读断言前失败。
动机 / Motivation
私聊里收到原失败通知、随后重发同一文字的人,需要旧请求停止另排队,新的请求按自己的回执继续。旧提交在 Core 已排队、外层 accepted 回执尚未写完时仍恢复旧请求,产生第二个 queued Turn 和一条多余通知。新提交只保留重发的一个队列记录,原失败和通知不变;重投复用相同 Turn。尚未进 Core、同 ID 但来源不符、不同文字和不同来源的输入仍允许原恢复。验证覆盖真实本地 Core 队列、Lark worker 和 TypeScript planner,Provider 及 native 只读结果使用合成 fixture;没有验证实际安装、Discord 或真实模型。有两项必跑验证仍红:新测试直接声明导入的 ordinary fixture,缺少本文件已有的局部 F811 标注。相同 Ruff 命令在原文件无诊断,新 head 在第 190 行报错。扫描测试用同一来源和 Session 的两条相同文字证明读取只做一遍。后输入现在合法抑制前一条,原测试仍要求两条都是候选,在进入共享读断言前失败。
改动思路 / Approach
复用现有 source fence 和 Core client Turn。真实队列回执可以证明重发已经接纳。 历史原失败仍保留;扫描和静态测试也应保留各自的验证目的。 候选发现只收集来源和输入摘要。Core 仍负责接纳;有原次回执就读回,补外层记录时复用同一身份。
具体改动 / Changes
关键代码讲解
实际 diff 基准 56fc2a1。按 ORIGINAL-CONTEXT、VERIFIED-CONTINUATION、REQUIRED-VALIDATION 核对。发布契约 要求保留原失败及权限,并避免重放不确定效果。来源投影 将 prepared 同文输入绑定到准确的 Core client Turn,来源不同或没有回执时不拦截。现有 planner 优先修复已准入相关性,再拦截新恢复。新增测试 实际复现队列和回执的写入顺序;它缺少测试文件相邻位置已有的 fixture lint 标注。扫描测试 仍以两条同文请求要求两条候选。
对主干的风险 / Risks
原 Stop、当前 source fence、只读发现与真实准入区分保留。需要修的两项均是验证:新 F811 与同文扫描样本。基准另有一条独立 F811,由 #8657 修复;该 PR 不会修到这里的新报错。
语义与 CI 对齐
隔离 Lark 的六组对照保留真实队列、失败和通知。63 项 TypeScript 及 6 项直接检查、10 项 premerge 检查通过。定向 Python 120 通过、1 失败。基准和新头复现了扫描分歧;不同文本保留两条且共享读,同文保留较新候选,两项独立对照通过。当前 Ruff 比基准多一条。未查询托管 CI。
我的整体评价 / Verdict
旧的部分写入问题已关闭。请同时修复扫描用例和新增 fixture 的行级 lint,再运行同一组验证。 本次评审覆盖现有宿主与 CLI 行为。验证覆盖真实本地 Core 队列、Lark worker 和 TypeScript planner,Provider 及 native 只读结果使用合成 fixture;没有验证实际安装、Discord 或真实模型。
English verdict: REQUEST_CHANGES. The interrupted outer receipt no longer creates a duplicate recovery, and independent retry cases are preserved. The exact head introduces a Ruff F811 and breaks the discovery fixture's two-candidate expectation. Fix both test validations without weakening recovery.
|
|
||
|
|
||
| @pytest.mark.parametrize("lost_receipt", [False, True], ids=["accepted", "lost-outer-receipt"]) | ||
| def test_original_retry_yields_to_a_newer_equivalent_lark_request(ordinary, monkeypatch, lost_receipt): |
There was a problem hiding this comment.
[P2] 新 fixture 触发 Ruff F811。 新测试直接声明导入的 ordinary fixture,缺少本文件已有的局部 F811 标注。相同 Ruff 命令在原文件无诊断,新 head 在第 190 行报错。 沿用相邻测试的行级 # noqa: F811,保留普通 fixture 的名字和断言;勿关闭文件或项目级规则。 #8657 处理的是另一文件;对当前行添加同类标注后,独立 Ruff 对照通过。
| // A later source stop vetoes new execution. Correlation repair for an | ||
| // already admitted Turn above remains read-only and keeps its exact identity. | ||
| if (recovery.stop_observed === true) return no; | ||
| // A source stop or a newer admitted equivalent vetoes new execution. |
There was a problem hiding this comment.
[P2] 保留扫描测试的两条独立输入。 扫描测试用同一来源和 Session 的两条相同文字证明读取只做一遍。后输入现在合法抑制前一条,原测试仍要求两条都是候选,在进入共享读断言前失败。 给共享读测试的第二条历史记录换成不同文字,保留两条候选、每轮一次读和零 Provider 调用断言;继续单独验证同文只保留新候选。 基准通过,当前 head 失败;当前 head 的不同文本双候选与同文本单候选对照都通过。
A private bot can admit an extra recovery Turn after the owner resends the same text. The resend may already be in the original Core queue while the external request still shows
preparedbecause its receipt write was interrupted.Recovery now checks the accepted request journal or the existing Core receipt for the resend's exact client Turn. It matches the binding, source, Session, original text, and origin. A request still being prepared with no Core receipt leaves the original recovery path eligible. When Core already accepted the resend, the worker keeps the original failure and reuses the queued resend when its receipt is repaired.
An isolated Lark worker reproduced the extra recovery after Core queued the resend and before the external receipt write. The regression verifies that recovery preserves the queued Turn and failure notice, and that repeating the isolated ingress settles the original resend receipt. The focused Python and TypeScript suites and the repository's quick pre-merge gate passed. Review finding