Repository navigation
fix(chat): prevent provider input from entering after disconnect - #6824
Conversation
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.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
使用已连接项目或管家会话的人,在断开连接或取消 Agent 目标时,需要确保迟到的消息不能继续进入执行队列,也不必等待缓慢的身份查询才能断开。
旧实现只让网页消息在提交队列时持有绑定锁,提供方消息可能在撤销后仍入队;目标配置又把远端身份查询放在锁内,使断开请求等待。当前让所有已绑定来源复用同一提交边界,并把该身份查询移到锁外后重新核对绑定。
本机真实 HTTP、File/SQLite 队列和普通安装包验证了两种顺序:撤销先发生时拒绝消息且不生成队列项;提交先发生时断开等待提交完成,并取消同一个 Turn。缓慢身份查询期间,断开可先返回,迟到的目标配置被拒绝。
本次仅修复现有绑定会话的撤销和提交竞态,不新增连接、授权、配置或确认步骤,也不声称完成整个多方协作 RFC、真实提供方采用或长期效果验收。
完整 RFC 的常驻授权、真实提供方与 App 采用、跨参与者结果返回及长期成本仍由原有能力交付承担;当前精确 head 的必需 Ruff 仍有基线原有的未使用导入错误。
改动思路
复用原有 typed 绑定 owner 和同一队列提交锁,把慢提供方 IO 放到锁外并在提交前核对当前行;没有新建权限、队列或状态 owner。
当前边界是现有绑定会话的消息提交、断开和目标配置,保留原 Session、队列取消与恢复,不扩展到完整协作协议或安装采用。
锁保护“核对当前绑定—提交同一队列项”的本地事务边界。身份观察可以慢,但不能使用户无法断开;观察结果也不能覆盖观察期间已经撤销或替换的绑定。网页继续使用真实 web 来源,提供方保留 lark/external 来源,后者不会重复登记本地网页 ingress。附加 Agent 原 Session 的独立执行权限边界没有合并进来。
下表从当前 head 的调用链归纳顺序,执行证据另列于风险部分:
| 竞争顺序 | 旧风险 | 当前可观察结果 |
|---|---|---|
| 身份观察期间断开先提交 | 提供方随后仍可能入队;目标配置可能阻塞断开 | 断开先返回,迟到观察经当前行核对被拒绝,不产生队列项或复活目标 |
| 队列提交先取得绑定锁 | 断开可能跨过提供方提交边界 | 断开等待该提交完成,然后取消同一个 Turn,保留原消息和线程身份 |
具体改动
完整差异四个文件 +197/-23。两份生产 Python adapter 修正既有入口,两份已有回归模块加入真实 HTTP 与队列竞态。依据为实际不可变基线 55d6337 的 capable-manager semantic handoff RFC,当前 PR 说明与现有 task-first frontend contract;先读规范与评论,再检查完整差异:
- 5.3-current-authority / §5.3 current authority:保存的 Session 上下文选择待核对身份,只有当前绑定与新鲜身份观察能授权。撤销、替换或来源不符都不能靠旧上下文继续。
- A3-revoked-queued-mutation / 子边界:撤销先发生则拒绝后续入队;提交先发生则由原取消 owner 处理同一个 Turn。这没有证明 A3 的所有常驻授权、重启和不可信 effect 场景。
- 10-First-CoreSessionqueue / §10 First:继续使用原队列、线程、claim、停止与恢复,不加 bridge ledger 或重复权限 owner。完整 First milestone 的提供方采用和跨参与者结果返回没有在本 PR 验收。
关键代码讲解
(1)input_context 接受实际 origin,先在锁外观察,再在同一个绑定锁内核对 exact row、调用既有 typed resolver,并让调用者在该上下文内完成 canonical queue commit。web 的 private_human_message 仍为 false,不能伪装成提供方私聊事件。
(2)_session_source 把原 session_context 已有的 private/group 来源构造提取为纯 helper,保留 operator、source、group/topic 和 audience;session_context 仍使用同一个 resolver,没有新建状态或泛化权限。
(3)change_agent_target 在锁外做提供方身份观察,取得锁后核对当前行是否仍是刚才那一行,再走既有 expected_revision/CAS。慢查询期间 DELETE 可以先返回200,随后 grant/revoke POST 因绑定已变返回400,不会重建授权。
(4)_enqueue_bound 由所有已绑定来源共用,重读 open Session 并比较 project/steward/channel/Goal/Agent 字段,保留 executor scope 校验。只有 web 补本地 ingress;create_queued_turn 在锁内,resume_queue 在锁外。普通未绑定输入与 attached Agent 的原 Session 路径继续独立。
对主干的风险
独立源代码验证运行五个完整模块:private agents、private conversations、bound owner web input、ordinary project、conversation bindings,305 passed、0 skipped、512.69s。本次为串行且不带 coverage,不能写成两 worker 或覆盖率资格。实际边界包括本地 HTTP、File/SQLite、文件锁、原队列和 TypeScript Core;外部身份与执行 host 使用合成 fixture。
相同20个当前竞态 oracle 注入实际基线 55d6337 的原测试模块,保持基线生产和 fixture 不变:8 passed、12 failed、0 errors。8个身份阶段输入本来已安全;8个提交阶段在“revocation crossed canonical queue publication”断言失败,另外4个目标 grant/revoke 的 DELETE 因身份锁阻塞而超时。当前完整模块覆盖这20项均通过。这区分了“先检查过权限”与“权限持续到实际提交”,未放松超时或断言。
普通 Chat 构建、wheel 构建及独立非 editable 安装成功。通过普通安装包运行原生队列与目标 HTTP 的48项:48 passed,两份变更生产模块的 SHA 与精确 head 一致,加载的709个 loopx 模块全来自安装包。测试驱动来自仓库,身份/host 仍为合成;这是打包后端资格,不是已安装 App 或真实 Lark 采用。初始 runner 因缺 test support 路径收集失败,修正运行又被中断,均保留;确认没有遗留自有进程后,完成隔离 runner 的48项,未把中断当作通过。
当前 head 的规定 Ruff 范围仍失败。 实际基线和当前 head 使用同一命令,完整356字节诊断逐字节相同:未修改的 tests/control_plane/test_checkpoint_retention.py:4:21 有 pathlib.Path F401。作者正文的“Ruff passed”不能代表本次实际结果;后来主干经 #6820/#6825 带入的清理也不在这个 reviewed head。该失败有独立 base/head 归因,变更不触及其因果路径,队列不变量另有305/48与基线反例证据,故按当前评审策略属于 pre-existing unrelated,代码结论可以 APPROVE,必需 lint/quality/readiness hold 仍须保留。
额外 ruff check . 广域探针出现647项历史违规,基线与 head 的完整输出相同;它不替代规定 Ruff,也没有被写成通过。规定 mypy、四路径 public scan、标准 premerge 6 direct + 2 selected、diff-scoped semantic advisory 后的完整 semantic smoke 均通过。premerge 没有自动选中 runtime profile,故以完整模块、真实基线反例和普通包路径补足当前风险。本轮没有查询、轮询或等待 CI。
语义与 CI 对齐
本次复用既有 typed binding/source/origin 词汇与唯一 TypeScript 决策 owner;Python 留在现有 provider/HTTP/FS adapter 边界,提取同域 source helper,没有创建并行策略。diff advisory 没发现受支持的新增 vocabulary carrier;手工检查完整调用链与完整 semantic smoke 后,仍只主张受检边界,不把扫描零项当成所有语义都已证明。
这不是新 opt-in/default-off 功能,变化是现有已绑定来源必须遵守同一撤销边界。普通未绑定与 attached 路径的完整测试和未变 companion 验证保留。新增 stale/unsupported 错误是执行前强制拒绝,不能叫作可忽略 guidance;project 可见、provider ready、Session 文本或回执均不是授权。受影响的正确性路径仍覆盖 File 与默认 SQLite,没有用默认 provider 性能规则豁免 File 正确性。
我的整体评价
APPROVE,精确 head 1c9eb4e;当前完整差异没有新增代码阻塞发现。 这是有真实用户结果的 justified increment:同一绑定中的取消和入队有可验证顺序,断开不会被慢身份观察挡住,不引入新的必需准备、导航或确认。原 Session FIFO、重放、原路结果恢复及停止后重新做有用工作的路径由305项里的既有 owner 保留;这支持 long_horizon preserved,用户的断开/错误恢复支持 user_experience improved,未测得整体长期增益或资源节省。
当前范围与机制成本相称,已有 _session_source 和共用 admission fence 是适当的同域简化。旧 local_input 只有一个共同部署调用者,随调用者替换;持久化 v0 binding、Session、队列身份与公共 session_context 均保留,不需要旧请求兼容分支或额外框架。真实提供方/App 采用、跨平台、完整 RFC 和完整成本仍未验收;保留当前必需 lint/quality 限制,任何新 head 都需重新判断,批准本身不授予合并或安装权限。
English verdict: APPROVE — 1c9eb4e. Independent complete source305, actual-base20 race counterexample (12 fail/8 pass), and normal installed-wheel48 verify revocation/queue ordering and nonblocking identity observation. Configured Ruff retains the byte-identical pre-existing F401 in an untouched file; required lint/quality/readiness holds remain. No CI observation, live-provider/App adoption, full-RFC or performance qualification is claimed.
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Goal and delivered outcome
A provider message could pass its source check, pause before publishing its queue entry, and enter the queue after disconnect had already returned and scanned for work to cancel. Separately, target configuration held the binding lock during remote identity IO, delaying disconnect.
This keeps remote identity IO outside the publication fence and uses the existing fence through canonical queue publication. If input commits first, disconnect then cancels the known Turn. If revocation wins during identity IO, input is rejected before queue publication. Target configuration also rechecks its exact binding before writing.
Author declaration
Written by OpenAI Codex (GPT-6, model_agent). Specification: these reproduced defects and invariants, against current target main at
3203b55843d2dcca5b77822ed857442adf944587.Scope and continuation
Four files, two existing production adapters. Provider and local Web input share one admission boundary; the existing Session-source projection is reused. The obsolete local-only helper is removed. Attached Agent input retains its existing fence. No new policy grant, persisted vocabulary, queue, executor, capability or recipient type.
Complete for these revocation defects. This does not implement Goal-less project recipients, collaboration execution or original-route return. Those remain separate acceptance work. No frontend presentation or CLI entrypoint changes; existing provider and HTTP entrypoints are exercised.
Validation
Tested head:
f0803d8f09c3c1f72145a8008e4cefae601cfe0f. Run state: finished. Inputs: synthetic, public_fixture.Current target
mainwas merged without rewriting the proposal's history. The PR remains four paths, +197/-23; upstream discovery and lint-cleanup changes belong to the base, not the proposal. The prior review's required Ruff hold is now addressed by adopting that canonical cleanup instead of copying a separate fix.3203b558: 8 identity cases passed, 8 queue-publication assertions failed, 4 target-disconnect Future timeouts, 0 errors. The raw baseline exit remains 1. Current source and installed suites pass these cases; no deadline or assertion changed.tests,loopx/canary,loopx/control_plane,loopx/domain_packs,loopx/presentation) and the four changed paths passed. Configured mypy passed its 17 declared sources. This replaces the prior head's lint hold, not its historical failed observation.cqr_f30ae8066c5467842c62passed. Four-path public/private scan and bilingual guard passed. No CI was queried or waited on.Failure history is retained. The first installed test driver pointed its fixture's TypeScript loader at an absent source sibling (68 setup errors and 4 fixture-initialization failures); only that copied helper's package-root IO was corrected to the actual installed package, preserving product code, assertions, workloads and deadlines. All 72 then passed. An initial build command used the wrong package directory and was corrected. The private baseline verifier initially guessed a diagnostic phrase; readback confirms the actual typed Future TimeoutError at the unchanged three-second disconnect boundary. No product or test rule was changed to make these observations green.
The earlier packaged two-client settings evidence applies only to the unchanged target-configuration method and settings interaction; no live-provider delivery or installed App adoption is inferred. PostgreSQL authority/routing is unchanged and was not tested.
Review and remaining acceptance
The previous exact-head review approved the code at
1c9eb4ewhile retaining the mandatory lint/quality hold. This new head incorporates current main, clears that lint failure and requalifies the affected paths; it still needs fresh exact-head review. The old review and its failures remain historical.No new frontend presentation or CLI entrypoint is introduced by this four-file proposal. This does not implement Goal-less project recipients, collaboration execution, real provider adoption or original-route result return; those remain the existing work items' acceptance. No live installation, merge, new queue/Session/permission owner, or full-RFC completion is claimed. All three proposal commits carry DCO sign-off.