Repository navigation
Preserve queued private Lark messages across reconnects - #8668
loopx-agent wants to merge 16 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.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
评审提交 42cc3dba9059ff7fcf68ea68fed401fe0c68cf6b,对照主干 167b0a1f3a3307c284b021ea9a09c7c0b5e5a11f。
[P2] 私聊换绑会丢弃共享监听器的群聊后续消息。 同一个已验证 App 同时承载私聊和旧式 direct_session 群聊 Topic。首条群聊正在处理,第二条已排队,此时设置保存新的私聊工作区。 同样的群聊路由下,主干完成两条并写入两笔 Inbox ACK。候选在首条释放之前终止旧 reader,后续只有一笔 ACK。 把私聊绑定变更限制在私聊准入,或在原 App 租约下排空仍有效的群聊队列,再更换监听器。旧私聊事件仍要校验原绑定。 共享 worker 的指纹 刷新时停止 worker 停止时取消待处理消息
动机 / Motivation
这项改动涉及通过 Lark Bot 私聊继续工作的用户,以及同一 Bot 上独立使用群聊 Topic 的成员。 第一条私聊消息还在准入,第二条已进入监听器的队列。主干收到历史缺口的重连请求后直接停止 worker,第二条会消失。 候选提交让旧连接先处理已经读到的私聊消息。即使提供方没有重发,第二条也按顺序进入原会话。 同一个 App 更换私聊工作区时,候选提交仍会清掉已排队且路由未变的群聊消息。 评审覆盖源码监听器、现有群聊 Inbox 和原生私聊准入。生产 Lark 长连接、真实 Host 与手机端未验证。
改动思路 / Approach
历史缺口观察器提交重连提示。服务持有原 App 租约并记录本机现有消息子进程。关闭这条消息流触发既有分发队列排空,随后同一个 App 才能开始替换连接。原生私聊准入仍核对来源和绑定,群聊 ACK 仍由原 Inbox 写入。
重连可以复用消息流已有的排空路径。私聊绑定 ID 变化触发整条流的停止,影响范围超过这次配置操作。 本 PR 应交付已读私聊的重连恢复,以及共享 App 上旧私聊授权的校验和群聊队列的连续处理。
具体改动 / Changes
关键代码讲解
_reconcile_private对比当前 worker 和消息子进程的代际,只对相符的进程请求退出。流排空后才释放原 App 租约。 当前重连分支_poll_profile记录消息进程,捕获私聊绑定 ID,并通过现有stream_lark_goal_topic_profile调度。显式停止仍由原 worker stop 处理。 监听与释放_profile_worker_fingerprint现在把 App ID 和私聊绑定 ID 一起用于刷新判断。相同 App 的私聊工作区变化会触发整条共享流退休。 工作区变化的判断LarkPrivateConversations.admit在捕获之前核对预期绑定,后续继续走现有 Core 及消息去重。测试通过真实 listener、Core 和临时存储检查重连、停止、撤权及重连中的换绑。 准入 私聊测试
验收依据为 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md 在主干 167b0a1f3a3307c284b021ea9a09c7c0b5e5a11f 的条款。 共享监听器 历史缺口恢复
| 条目 | 当前提交的观察 |
|---|---|
| shared-app-route-isolation | 未满足。同一 App 的群聊绑定、目标和来源都没变,保存私聊工作区仍会取消等待的群聊项。 |
| live-gap-reconnect | 已验证。模拟缺口重连时,已读私聊在子进程退出后进入原会话。移除测试中的重发,仍按顺序读回。 |
| original-private-binding | 已验证。重连中更换绑定后,旧私聊拒绝写入。新 listener 收到的下一条使用新绑定。 |
对主干的风险 / Risks
独立夹具控制提供方输入和群聊首条处理时机,并读取真实群聊 Inbox 的处理计数。只有群聊的对照中,主干和候选均处理两条。显式私聊断开在这条旧式群聊路径上两边均只处理一条。新增回归发生在同 App 更换私聊工作区,主干处理两条,候选只处理一条。用户从群里得不到第二条的处理结果。
语义与 CI 对齐
188 项源码聚焦测试、Ruff 和 quick premerge 的 6 项直接检查及 2 项选中检查通过。配对测试复现了共享队列回归。重跑时让共享 App 的首条群聊处理停在 Inbox ACK 前,发送第二条并等待读取,保存同 App 的新私聊工作区后放行首条,再检查两个群聊 ACK 和旧私聊拒绝。托管 CI 依当前配置未查询。
我的整体评价 / Verdict
保留私聊重连时的排空和原绑定检查。合入前需要修复同 App 的共享群聊队列。队列丢失影响持续工作和其他群成员的消息回执。补测应沿用现有 Chat 服务和共享监听器,验证一次换绑后原群聊与新私聊都能继续。变更发生在 Lark 监听器和准入,现有设置页及消息呈现代码没有改动。 评审覆盖源码监听器、现有群聊 Inbox 和原生私聊准入。生产 Lark 长连接、真实 Host 与手机端未验证。
English verdict: REQUEST_CHANGES. The private queue drains on reconnect without provider redelivery. Rebinding the private workspace on the same App drops an already-queued group message. Preserve valid group intake while fencing stale private input.
| binding_id = str(profile_config.get("binding_id") or "") | ||
| # A same-App replacement changes which workspace receives live input. | ||
| # Keep the App lease identity, but retire the old worker on refresh. | ||
| worker_ref = f"{app_ref}:{binding_id}" if app_ref and binding_id else app_ref or binding_id |
There was a problem hiding this comment.
同一个 App 已共享私聊和旧式 direct_session 群聊 Topic 时,保存新的私聊工作区会改变这里的 worker 指纹。refresh 随后设置共享的 stop,仍有效的群聊队列随之丢弃。配对夹具先阻塞群聊首条、排入第二条,再换绑私聊。群聊路由前后相同,主干处理并确认两条,本提交只确认一条。请在原 App 租约下保留仍有效的群聊消息,同时让旧私聊继续按原绑定被拒绝。
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
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
评审提交 27610a908cf827da34f2de32c5576a0f4d056381,对照主干 441c6077e7c5c8e896ea79562761fd483baf157e。
[P2] 旧群聊只保存 profile 时,增删私聊绑定仍会丢掉已读消息。 群聊连接只保存 sender profile,Bot 地址仍可验证。首条群聊处理中,第二条已排队。此时通过原设置接口新增或解除同一 App 的私聊绑定。 设置返回 HTTP 200,群聊路由完全相同。主干和当前提交均只确认第一条,第二条没有进入原群聊处理。保存 App ID 的对照在当前提交确认两条。 用已验证的 App 与旧 profile 的租约关系识别仍有效的共享群聊,让旧流先排空再调整租约。继续拒绝旧私聊绑定,并保留 App 真的换了、撤权和服务关闭时的停止行为。 共享连接的停止判断
动机 / Motivation
同一个 Lark App 同时接收私聊和群聊 Topic。群成员可能在管理员调整私聊工作区时继续发消息。 群聊第一条消息还在处理,第二条已被监听器读入。此时新增或解除私聊绑定,旧 worker 一旦被整体停止,第二条就会失去回复和 Inbox 确认。 新版让保存了 App ID 的共享连接先排空群聊。没有提供方重发时,私聊的缺口重连也会继续处理已经读到的消息。 只保存 profile 的旧群聊配置仍会在新增或解除私聊绑定时丢掉第二条已排队消息。 验证覆盖源码 HTTP、监听器、群聊 Inbox 和私聊准入。生产 Lark 长连接、桌面安装版与移动端尚未验证。
改动思路 / Approach
保留单 App 监听器及现有授权。旧配置加入私聊后补齐了 App ID,解除时又回到 profile。直接比较两份传输指纹会把这个过渡判成整条连接失效。 本 PR 的交付范围是排空已读消息、校验旧私聊绑定,以及让同一 App 下仍有效的群聊继续处理。 普通重连会关闭当前消息进程,保留 worker,交给已有分发器处理已经读到的消息。群聊的确认仍由 Inbox 写入,私聊继续由 Core 核对来源和授权。判断同一 App 的前提应来自已经验证的连接身份和现有租约。
具体改动 / Changes
关键代码讲解
_disconnect_message_consumer请求当前消息进程退出。worker 的 stop 保持为 false 时,既有分发器会在释放原租约前排空已读消息。历史缺口与合格的设置变更共享这个路径。 消息进程退出_profile_worker_fingerprint把私聊绑定与传输字段分开。旧群聊目标省略 App ID 时,配置合成只在私聊存在时补入该 ID,造成同一连接的两份传输投影不相等。 传输指纹 群聊与私聊配置合成refresh在传输指纹相等时排空,否则设置整个 worker 的 stop。后者会取消未开始处理的群聊事件。私聊准入另有原 binding ID 检查,可以继续拒绝撤权或换绑的旧事件。 排空与停止分支 私聊准入test_shared_listener_drains_group_inbox_when_private_workspace_changes验证增删换三种操作,夹具明确给群聊目标写入了 App ID。复评在相同监听路径加入历史配置,保留有效 Bot open ID 以独立验证群聊收件。 共享监听器测试
验收依据为 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md 在 441c6077e7c5c8e896ea79562761fd483baf157e 的共享连接约定。 独立路由及旧配置双租约 历史缺口恢复
| 条款 | 当前提交观察 |
|---|---|
| shared-route-isolation | 部分满足。保存 App ID 的增删换都确认两条。profile-only 的新增、解除各只确认一条。 |
| legacy-profile-lease | 私聊共存时继续持有 profile 与 App 两种租约。切换过程中群聊队列仍需保护。 |
| live-gap-drain | 移除提供方重发后,第二条私聊仍按原顺序被接收。 |
| stale-private-binding | 旧私聊在换绑后被拒绝。新连接下下一条使用新的私聊绑定。 |
对主干的风险 / Risks
| 群聊配置及操作 | 主干 ACK | 旧评审提交 ACK | 当前提交 ACK |
|---|---|---|---|
| 有 App ID,新增或解除私聊 | 1 | 1 | 2 |
| 无 App ID,新增或解除私聊 | 1 | 1 | 1 |
| 同 App 更换私聊工作区,两种配置 | 2 | 1 | 2 |
| 只有群聊,两种配置 | 2 | 2 | 2 |
当前和主干结果来自实际设置 HTTP handler、现有监听器和 Inbox。旧提交采用同样的持久绑定与监听器入口。HTTP 成功读回和群聊路由相同的情况下,两项旧配置失败在主干已经存在,仍落在本次声明修复的增删场景。没有向真实 Lark 发消息。
语义与 CI 对齐
72 项源码测试、独立的私聊无重发用例,以及 standard premerge 的 6 项直接检查与 10 项选中检查通过。两个旧配置反例在精确提交重现。复用共享监听器测试,保留 Bot open ID 并省略群聊 bot_app_id。挂起首条、排入第二条,分别用私聊设置 POST、DELETE,再检查两笔群聊 ACK、旧私聊拒绝以及 profile/App 双租约。 本次使用本地验证。
我的整体评价 / Verdict
原来的换绑回归已经修复。旧群聊配置仍会在一次私聊设置操作中丢掉已读消息,群成员只能等待提供方重发或自行补发。保留现有排空与撤权处理,并在同一个传输 owner 内补足旧 profile 的增删过渡。设置页继续调用现有 POST 和 DELETE 接口。面板代码与布局未变,复核通过源码 HTTP 和持久 Inbox 计数观察结果。 验证覆盖源码 HTTP、监听器、群聊 Inbox 和私聊准入。生产 Lark 长连接、桌面安装版与移动端尚未验证。
English verdict: REQUEST_CHANGES for 27610a9. Shared group rebind and App-ID-present add/remove now drain correctly. Supported profile-only group routes still lose a queued event on private add/remove. Preserve the active group queue while transitioning the verified App and profile leases.
| consumer = self._message_consumers.get(profile) | ||
| if ( | ||
| replacement is not None | ||
| and previous.transport == replacement.transport |
There was a problem hiding this comment.
旧群聊目标省略 bot_app_id 时,新增或解除私聊会在配置合成阶段改变 transport.app_id,尽管 profile 和群聊路由相同。这里走 stop.set(),设置 API 返回 200 后,第二条已读群聊在主干和本提交都没有 ACK。请先排空仍有效的群聊,再调整已验证的 App/profile 租约,并保持旧私聊拒绝和真实 App 变更的停止行为。
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
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
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
评审提交 1c7f8199d1a126d69300e9d07f863f8ce8e6aff7,对照主干 09886ba7afec07dba709229cc9dfb7fabf15d03a。
[P2] 一次 profile 读取失败会丢弃旧群聊的已读消息。 旧群聊只保存 sender profile,私聊未启用。首条正在处理、第二条已入队时,刷新遇到一次本地 profile list 读取失败。 群聊配置和路由保持一致。主干在相同条件下写入两笔 Inbox ACK,当前提交停止旧 worker,只写第一笔。健康读取对照仍写两笔。 在已启动监听器的生命周期内保留已确认的传输事实,暂态读取失败时沿原租约继续排空并重试观察。明确的 App 更换、关闭或撤权继续走现有拒绝和停止路径。 失败分支 停止旧 worker
动机 / Motivation
Lark 群聊 Topic 的使用者可能在管理员调整同一 App 的私聊绑定时继续发消息。 第一条群聊消息还在处理,第二条已读入队列。主干在新增或删除私聊绑定时会取消旧 worker,导致第二条失去回复和 Inbox ACK。 当前提交在保存 App ID 和只保存 profile 两种配置下,新增、删除及换绑均能处理两条原群聊消息并按序确认。 本次验证覆盖本地 HTTP 设置、监听器与持久 Inbox。真实 Lark 进程、线上重投和已安装客户端仍待验证。
改动思路 / Approach
子进程结束后,由已有 dispatcher 处理已读消息,再释放原 App 和 profile 租约。原私聊输入绑定到连接打开时的 binding,权限由原 Chat 准入重新校验。新增的 profile 查询能让旧群聊识别同一 App。交付边界是同一 App 的消息排空、原私聊绑定校验和群聊独立连续性。 群聊配置和路由保持一致。主干在相同条件下写入两笔 Inbox ACK,当前提交停止旧 worker,只写第一笔。健康读取对照仍写两笔。 保留原 worker 的传输事实并重试观察,修复应落在同一生命周期 owner。 传输判定
具体改动 / Changes
关键代码讲解
_disconnect_message_consumer 结束已登记子进程,让原队列排空。_poll_profile 持有原租约,并把私聊事件交给绑定了旧 ID 的回调。configured_profile_apps 从本地 CLI 读 App 身份。refresh 比对传输和私聊绑定,决定排空或停止。新增 ingress 用例覆盖无重投、撤权、换绑、真实 App 变更和两种可执行路径。基线更新后的公开 diff 仅有两个运行文件和 ingress 测试。 排空入口 profile 读取
以 09886ba7afec07dba709229cc9dfb7fabf15d03a 的 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md 为准。shared-route-isolation 在暂态读取失败时仍有缺口。legacy-profile-lease 在正常读取和设置变更时保留。live-gap-drain 与 stale-private-binding 由监听器及私聊入口回归覆盖。 已接受的契约
对主干的风险 / Risks
同一夹具中,主干新增或移除私聊绑定的四个组合各丢一条。候选八个组合均写两条 ACK。另用同一 profile-only 队列对照,健康读取两版均确认两条,一次读取失败时主干两条、候选一条。错误在本地 inventory 注入,真实 dispatcher 与 Inbox 产生结果。复用 profile-only 的双消息夹具,只让旧 CLI 的 profile list 失败一次。分别断言主干与候选的 worker stop、两条按序回复和 Inbox ACK,再核真实换 App、撤权与关闭。
语义与 CI 对齐
本地 Lark 用例 16 passed, 40 deselected in 59.88s,Ruff 及 quick premerge 六项直接检查和两项选中检查通过。上轮新增和解除私聊绑定的发现已复验通过。生产网络、安装态与托管 CI 待核验。
我的整体评价 / Verdict
请求修改。共享 App 的正常换绑已有可回读证据。请在失去本地 profile 读取时保住现有群聊队列,并保留真实换 App、关闭及撤权的回归。改动位于监听、准入和队列。验证通过原设置接口与 Inbox 读回,公开 diff 无页面组件。
English verdict: REQUEST_CHANGES. At 1c7f819, normal shared App settings changes preserve queued group messages. One transient legacy profile read failure still stops the worker and loses the second Inbox ACK.
| if cli_bin not in apps_by_cli: | ||
| try: | ||
| apps_by_cli[cli_bin] = inventory(cli_bin=cli_bin) | ||
| except (OSError, LarkCliUnavailableError): |
There was a problem hiding this comment.
[P2] 一次 profile 读取失败会丢弃旧群聊的已读消息。 旧群聊只保存 sender profile,私聊未启用。首条正在处理、第二条已入队时,刷新遇到一次本地 profile list 读取失败。 群聊配置和路由保持一致。主干在相同条件下写入两笔 Inbox ACK,当前提交停止旧 worker,只写第一笔。健康读取对照仍写两笔。 在已启动监听器的生命周期内保留已确认的传输事实,暂态读取失败时沿原租约继续排空并重试观察。明确的 App 更换、关闭或撤权继续走现有拒绝和停止路径。 复用 profile-only 的双消息夹具,只让旧 CLI 的 profile list 失败一次。分别断言主干与候选的 worker stop、两条按序回复和 Inbox ACK,再核真实换 App、撤权与关闭。
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
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
评审提交 901ed0f93fb5a69d30fb06986aeb39f70cac9adf,对照主干 bbf87884e568d1f6fe74458f4467924086573777。
[P2] 延迟重试会覆盖更新的 profile 读回并丢弃群聊消息。 群聊只保存旧 sender profile。一次 inventory 读取失败后,定时重试拿到一份没有该 profile 的旧结果。该结果在较新的 refresh 已读到原 App 之后才返回,此时两条群聊输入仍在处理和排队。 迟到的重试仍按旧结果替换 worker,原流被终止,第二条消息没有回复或 Inbox ACK。主干与拒绝过期结果的隔离对照均写入两条 ACK。 在同一 worker 生命周期内给 refresh 的发现结果加提交顺序校验。较旧结果在接触 worker 前失效,并按当前配置重新观察。真正的 App 变更、撤权与关闭仍由当前配置处理。 重试入口 提交旧发现的分支
动机 / Motivation
使用 Lark 群聊 Topic 的成员需要在同一 App 的私聊配置变动和本地 profile 读取波动时继续发消息。 第一条群聊消息还在处理,第二条已被监听器读入队列。主干新增或解除共享 App 的私聊绑定时,旧监听器可能在排空前退出。 当前提交通过正常的新增、解除和换绑对照。只有一次本地 profile 读取失败时,原 worker 也继续处理并按序确认两条消息。 本轮验证覆盖本地设置 HTTP、监听与持久 Inbox。线上 Lark 进程、重投行为和已安装客户端仍待验证。
改动思路 / Approach
现有 Lark worker 持有进程和 App 租约,Chat 重新校验原私聊绑定,群聊继续使用自己的 dispatcher 和 Inbox。 在同一 worker 生命周期内给 refresh 的发现结果加提交顺序校验。较旧结果在接触 worker 前失效,并按当前配置重新观察。真正的 App 变更、撤权与关闭仍由当前配置处理。 定时重试与前台刷新可以同时读配置。锁保护 worker 修改,却没有校验结果是不是当前的观察。将顺序校验留在原生命周期,既保住已读输入,也能让确认的 App 变更走原停止路径。 发现与更新边界
具体改动 / Changes
关键代码讲解
_disconnect_message_consumer 结束原消息子进程,让 dispatcher 先排空。原私聊回调携带打开时的 binding ID,真正准入仍按当前绑定核验。_listener_configs 从旧 profile 查 App 租约,读不到 inventory 时保留原 worker,定时器随后重试。后续 _refresh_profile_workers 取完配置才加锁。旧结果可以把当前 App 当成已移除,并经 hard stop 丢掉第二条已读消息。新增 ingress 用例已覆盖单次读取失败、换绑、撤权、真正的传输切换和现有群聊路径。 读取和持有原传输 原 binding 准入
以 bbf87884e568d1f6fe74458f4467924086573777 的 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md 为准。shared-route-isolation 在重试完成顺序上仍有缺口。legacy-profile-lease、live-gap-drain 和 stale-private-binding 由本轮正例及本地 ingress 覆盖。 群聊和私聊的接入契约
对主干的风险 / Risks
同一夹具中,单次读取失败在当前头已恢复到两条有序 ACK。并发反例里,主干两条、当前头一条,当前头的健康对照仍为两条。隔离对照仅在应用结果前拒绝旧的发现顺序,同样放行被延迟的读取,确认恢复到两条 ACK。真实 timer、dispatcher 和持久 Inbox 均参与。本地 fixture 控制 provider 和读回顺序。用两个按序的群聊输入和一个可阻塞的本地 profile list 复现。先让后台重试保存早期 inventory,再完成较新的 refresh,最后放行旧结果。断言原 worker 未停止,且两个来源 ID 都写入 ACK。
语义与 CI 对齐
本地 19 个聚焦 ingress 用例通过。Quick premerge 六项直接检查和两项选中检查通过。正常设置与单次失败合计十个独立用例通过,并发重试反例在当前头失败。托管 CI 未查询。
我的整体评价 / Verdict
请求修改。上轮的单次读取失败已复验修复。请让后台结果只在仍然适用时影响当前 worker,并保留真正换 App、关闭和撤权的回归。页面布局没有变化。通过本地设置与 Inbox 读回核验监听和准入。
English verdict: REQUEST_CHANGES. At 901ed0f, the single-failure recovery is fixed. An older automatic inventory retry can still supersede a newer refresh, stop the worker and drop the second group Inbox ACK.
| return | ||
| return False | ||
| for profile in unavailable: | ||
| replacement = desired.pop(profile) |
There was a problem hiding this comment.
[P2] 延迟重试会覆盖更新的 profile 读回并丢弃群聊消息。 群聊只保存旧 sender profile。一次 inventory 读取失败后,定时重试拿到一份没有该 profile 的旧结果。该结果在较新的 refresh 已读到原 App 之后才返回,此时两条群聊输入仍在处理和排队。 迟到的重试仍按旧结果替换 worker,原流被终止,第二条消息没有回复或 Inbox ACK。主干与拒绝过期结果的隔离对照均写入两条 ACK。 在同一 worker 生命周期内给 refresh 的发现结果加提交顺序校验。较旧结果在接触 worker 前失效,并按当前配置重新观察。真正的 App 变更、撤权与关闭仍由当前配置处理。 用两个按序的群聊输入和一个可阻塞的本地 profile list 复现。先让后台重试保存早期 inventory,再完成较新的 refresh,最后放行旧结果。断言原 worker 未停止,且两个来源 ID 都写入 ACK。
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
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
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
评审提交 f957afcb2af5675b96257496f72c4bdfcd74b31f,对照目标主干 7bb1b01687c5ecb36971b19fc432d6019d3e5492。
[P2] 新配置已读回时,旧重试仍可停止群聊监听器。 群聊仅记录旧 sender profile。配置短暂不可读后,定时重试取得一份缺少该 profile 的旧 inventory。配置恢复,前台 refresh 读到当前 App 后暂停在队列恢复,旧重试先进入 worker 更新。 旧序号仍大于最近一次已应用序号,因而可以停止原监听器。第二条已读入队列的群消息没有回复或持久 Inbox ACK。 比较已应用序号 原评审线程
动机 / Motivation
使用 Lark 群聊 Topic 的成员需要在同一 App 的私聊配置变动和本地 profile 读取波动时继续发消息。 丢失已读群消息需要上游重投或人工补发,会打断原 Topic 的处理顺序。 现有群聊处理第一条消息时,第二条已被监听器读取。并发更换私聊绑定或短暂读不到本地 App inventory 时,原群聊还需要处理这批消息。 新刷新先提交时,原反例的两条 ACK 都能写入。最新提交还保留了在定时器收尾期间到达的重试请求,随后再次读取 inventory。健康监听、换绑、真正移除和关闭分支通过。 本轮验证覆盖本地设置 HTTP、监听与持久 Inbox。线上 Lark 进程、重投行为和已安装客户端仍待验证。
改动思路 / Approach
现有 Lark worker 持有进程和 App 租约,Chat 重新校验原私聊绑定,群聊继续使用自己的 dispatcher 和 Inbox。 在停止或替换原 worker 前核对发现结果的时效。如前台刷新已取得更新观察,延后旧结果并按当前配置重查,同时保留确认的移除、撤权和关闭行为。 已应用序号覆盖“前台先提交”的顺序。前台发现和 worker 更新之间还有 _resume_session_queues,这段期间旧定时器仍可按过时 inventory 停止群聊。 发现和队列恢复
具体改动 / Changes
关键代码讲解
同 App 传输需要重连时,现有 dispatcher 先排空,私聊请求沿原 binding 重新准入。读不到 inventory 时保留旧 worker,后台定时器负责重查。f957afcb2a 在定时器收尾时记下前台触发的重试,下一次定时器继续读取。新序号在发现前分配,直到 worker 更新才写入 _applied_profile_refresh_sequence。旧结果如果在这两步之间先提交,仍会将存活的 worker 判为过期。 重试补调度 同传输断开后排空 原 binding 准入
按 7bb1b01687c5ecb36971b19fc432d6019d3e5492 的 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md 验收。shared-route-isolation 仍在新发现尚未提交时失败。legacy-profile-lease、live-gap-drain 和 stale-private-binding 由当前入口和 ingress 用例核对。 群聊与私聊的接入契约
对主干的风险 / Risks
同一夹具里,主干在两种顺序和健康对照都写入两条 ACK。候选在前台先提交和健康对照各写入两条。前台只完成读回时,旧定时器先停止 worker,候选只写入第一条 ACK。隔离实验只改比较依据,原 worker 随即保住两条 ACK。将两条群消息读入原监听器,分别让前台先提交、以及读回新配置后暂停再放行旧定时器。两种顺序都应保持原 worker 并写入两条 ACK。另验真实移除、暂时不可读和服务关闭。
语义与 CI 对齐
候选聚焦矩阵 21 项通过,1 项失败。普通设置 HTTP 与持久 Inbox 用于验证正常路径,失败分支经实际 refresh、定时器和 dispatcher,provider 和模型使用合成夹具。本地 standard premerge 六项直接检查和十项选中检查通过。托管 CI 未查询。
我的整体评价 / Verdict
请求修改。当前提交修复了旧定时器在新配置已提交后的覆盖,以及在定时器完成时遗漏重试请求的情况。worker 停止前仍需要核对新发现的时效,并用两种提交顺序验收。
English verdict: REQUEST_CHANGES. A timer can still stop the original group listener after a foreground refresh has read the restored profile but before it applies that inventory. The second already-read group message loses its reply and durable ACK.
| if self._closed.is_set(): | ||
| return | ||
| return False | ||
| if sequence < self._applied_profile_refresh_sequence: |
There was a problem hiding this comment.
[P2] 新配置已读回时,旧重试仍可停止群聊监听器。 群聊仅记录旧 sender profile。配置短暂不可读后,定时重试取得一份缺少该 profile 的旧 inventory。配置恢复,前台 refresh 读到当前 App 后暂停在队列恢复,旧重试先进入 worker 更新。 旧序号仍大于最近一次已应用序号,因而可以停止原监听器。第二条已读入队列的群消息没有回复或持久 Inbox ACK。 在停止或替换原 worker 前核对发现结果的时效。如前台刷新已取得更新观察,延后旧结果并按当前配置重查,同时保留确认的移除、撤权和关闭行为。 将两条群消息读入原监听器,分别让前台先提交、以及读回新配置后暂停再放行旧定时器。两种顺序都应保持原 worker 并写入两条 ACK。另验真实移除、暂时不可读和服务关闭。
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
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
评审提交 b489c80075c85dfbdf0a27b012d6d97454aabc8f,对照目标主干 e94c549e8d5384938f5e695371731417ea914dce 与上一评审头 f957afcb2af5675b96257496f72c4bdfcd74b31f。
[P2] 同一 App 的两个旧 profile 会丢掉一个群聊的消息。 两个有效的旧群聊 Topic 分别保存不同的 sender profile,均未保存 bot_app_id。本地 profile inventory 将两个别名解析为同一 App,且没有私聊绑定。先启动一个群聊监听,再载入第二个群聊。 候选按同一 App 租约让第二个 worker 进入 standby。活跃 worker 的事件路由仍只认识自己的旧 profile,第二个群聊的消息进入 target_unmatched,没有回复或持久 Inbox ACK。 只给 listener 配置注入 App ID
动机 / Motivation
需要持续处理已配置群聊 Topic 的 Lark 成员。 旧群聊只持有 sender profile 时,私聊恢复也会影响它所使用的 Lark listener。 上一轮的并发反例已修。新 refresh 读到恢复后的 inventory 后,即使仍在恢复队列,旧定时器也不会停止原 worker。两种顺序及健康对照各保留两条 ACK。配置中已存 App ID,或两 profile 实际属于不同 App 时,两条群聊消息均有回复和 ACK。 验证经过源码 Chat 设置 HTTP、Lark 监听及实际本地 Inbox。provider 和模型为隔离夹具,线上 Lark 和已部署客户端未验证。
改动思路 / Approach
Lark worker 继续拥有 App 租约和进程回收。群聊使用原 Topic 的路由与 Inbox,Core 校验私聊绑定。 当前候选在同一 transport 重连时结束子进程,让既有 dispatcher 处理完已排队消息再释放租约。私聊消息进入原 binding 的准入检查。App inventory 的已验证同一性应供租约和既有群聊 target matcher 使用一次,保留各群聊的独立授权。 排空前结束消息管道 原私聊 binding 校验
具体改动 / Changes
关键代码讲解
_refresh_profile_workers 现在在恢复 session queue 前记录已完成的发现,旧 timer 返回时会重查。旧群聊 profile 通过 inventory 得到 App ID,_poll_profile 同时取得 profile 和 App 租约。另一个别名因 App 租约进入 standby。群聊事件仍读取原 snapshot。原 target 没有 App ID 时,_target_for_profile_chat 只能按 sender profile 匹配,活跃别名无法替它处理消息。 并发顺序保护 App 租约 既有群聊匹配
按主干 RFC 的原 Topic、独立授权与 FIFO,以及现有 App consumer 恢复边界验收。private-binding-fence、live-gap-drain 和 stale-observation 已验证。shared-route-isolation 在只存 profile 的同 App 群聊上失败。 Topic FIFO 与原来源授权 私聊缺口与 App 租约
对主干的风险 / Risks
主干为两个只存 profile 的旧 Topic 分别打开 consumer。相同输入得到两条原群聊的回复和 ACK。 当前头只有一个活跃 consumer,两个群聊合计只写入第一条 ACK。补齐同一个测试中的已验证 App 身份后,原候选租约仍只允许一个活跃 consumer,两条消息均沿各自 Topic 回复并写入 ACK。不同 App 和两个 target 已存同一 App ID 的对照也都通过。为同一 App 配置两个仅存 profile 的旧 Topic,先启动一个监听,再载入另一个。从同一 App 流发送两群消息,核对一个活跃 consumer 和两条原 Topic 的回复及 ACK。同时跑不同 App 和已有 bot_app_id 的对照。
语义与 CI 对齐
64 项本地 ingress 测试通过。上一轮并发反例的三个本地分支全部保留两条 ACK。新增同输入矩阵在主干三项通过,候选两项通过、一项失败。Standard premerge 六项直接、十项选中检查通过。 托管 CI 未查询。
我的整体评价 / Verdict
请求修改。让同一份已验证的 App/profile 映射同时用于租约和群聊路由,继续核验各 Topic 原有的绑定、群聊与 root。沿用现有 target matcher、dispatcher 和 ACK。
English verdict: REQUEST_CHANGES. At b489c80, the stale-refresh race is fixed, but sharing one App lease across two profile-only legacy groups leaves the standby alias's Topic unroutable.
| # snapshot and its separate group and owner grants. | ||
| profiles[profile] = { | ||
| **config, | ||
| "bot_app_id": app_id, |
There was a problem hiding this comment.
[P2] 同一 App 的两个旧 profile 会丢掉一个群聊的消息。 两个有效的旧群聊 Topic 分别保存不同的 sender profile,均未保存 bot_app_id。本地 profile inventory 将两个别名解析为同一 App,且没有私聊绑定。先启动一个群聊监听,再载入第二个群聊。 候选按同一 App 租约让第二个 worker 进入 standby。活跃 worker 的事件路由仍只认识自己的旧 profile,第二个群聊的消息进入 target_unmatched,没有回复或持久 Inbox ACK。 让同一份已验证的 App/profile 映射同时用于租约和群聊路由,继续核验各 Topic 原有的绑定、群聊与 root。沿用现有 target matcher、dispatcher 和 ACK。 为同一 App 配置两个仅存 profile 的旧 Topic,先启动一个监听,再载入另一个。从同一 App 流发送两群消息,核对一个活跃 consumer 和两条原 Topic 的回复及 ACK。同时跑不同 App 和已有 bot_app_id 的对照。
A private history gap asks the current Lark listener to reconnect. The old stop path could discard messages the listener had already read. The same problem affected a legacy group topic when its shared App gained, lost, or replaced a private workspace binding.
The service ends the message subprocess and drains queued messages before releasing the App lease. A private binding change on the same transport uses that path, so the existing group inbox still processes and acknowledges its queued messages. Private events keep the binding under which their connection opened. Revoked events cannot enter the replacement workspace. A service shutdown or a changed transport still stops the old worker. For older group settings that store only the sender profile, the service resolves the App from the configured profile before comparing listener leases. The listener also compares resolved executable paths when a group stores a bare CLI command and Chat stores the full path to that same executable. A temporary failure to read local profile inventory keeps an already active group connection and its leases. A deferred configuration read observes a real App change before replacing that connection. The service records the newest discovery before resuming session queues and rechecks that order under the worker lifecycle lock. This keeps a delayed result from stopping the listener even while queue recovery has the newer refresh paused. It reuses the same timer for a fresh read and retains a retry requested while that timer is finishing.
The regression uses the existing Lark listener, group inbox, and native private admission with an isolated provider. It reproduces a dropped second group message on the first PR commit, then verifies ordered group replies and Inbox ACKs during private binding changes. The private cases cover reconnect, provider redelivery, shutdown, revocation, and workspace replacement. The patch also fixes the existing Ruff failure in the restored-transcript test fixture. The added cases use the Chat settings API to verify that adding or removing a private binding preserves queued group messages for both saved App IDs and profile-only group settings. They also cover an actual App replacement. A bare command and its resolved path keep the same group queue. Switching to a different executable retains the stop behavior. The inventory cases cover a successful retry, an observed App change, and a delayed missing-profile result that arrives after a newer successful or unavailable read. The two queued group messages retain their original reply order and individual Inbox ACKs. Tests also cover real profile removal, shutdown during a read, and a foreground failure while the retry timer is finishing. A regression test completes the older read while the foreground refresh is inside queue recovery. The previous commit stops the original worker after one Inbox ACK. With the fix, the original worker replies to both messages in order and persists both ACKs.
The current revision includes main at
e94c549e8d53. Local validation passed 166 source tests, with 27 tests skipped in that source run. A separately installed standalone Core passed eight targeted CLI and doctor tests. The exact application wheel passed six listener timing regressions and served the verified Chat bundle over HTTP in isolation. Ruff and quick premerge passed. The listener tests use an isolated provider and exercise the production listener and inbox entry points.Independent review and five open review discussions remain for the current head. Earlier CI failures remain available in prior runs. Research navigation also failed on its matched base and CI wheel. The previous kernel type-check error at
decision_context/runtime.py:207reproduces on the current main and candidate with the same Python 3.11 command.