已开启
fix(weixin): 审批链路全程非阻塞,修复并发审批丢事件与决定不带 call_id 导致的会话卡死 #3
fix(weixin): 审批链路全程非阻塞,修复并发审批丢事件与决定不带 call_id 导致的会话卡死 #3
已开启
gcw_HKwsZ0n2创建于 17 天前
gcw_HKwsZ0n2
17 天前

背景

2026-09-15 12:48 微信通道事故:一个 turn 内并发 3 个 bash 工具调用,daemon 连发 3 条 permission_request,桥只注册了 1 个审批且回填不带 call_id,turn 永久卡死,该用户后续所有消息排队无响应。

三个根因

  1. 并发审批丢弃:同一 turn 的第 2、3 条 permission_request 被"已有挂起"守卫直接丢掉,daemon 等满员决定才放行 → turn 卡死;
  2. 决定不带 call_id:sendPermission 只发 {session_id, decision},daemon 对不上具体调用,兜底"拒绝"也解不开锁;
  3. 5 分钟超时默认拒绝与审批挂起语义矛盾,且超时路径静默清理队列,故障后现场全无(排障时队列空空如也)。

修复

  • 新增 approval-queue.js(仓库内实现,替代 file:/// 绝对路径的本地依赖):审批记录支持多个 call_id 并入;原子写(临时文件 + rename);提供 list/status/decide CLI;沿用 ~/.atomcode/weixin/approval 的 pending/decided 目录,本机既有 CLI 不受影响;
  • bridge.js onPermission:并发审批并入同一条挂起(不丢弃后续事件);
  • atomcode-backend.js sendPermission:签名扩展为 (decision, sessionId, callId),可选参数向后兼容;
  • bridge.js watchApproval:去掉超时自动拒绝;决定到达后逐 call_id 回填;
  • bridge.js handleInbound:审批挂起时,用户回复「同意/拒绝」直接落决定;普通消息触发"跳过老操作"(deny 落决定,回填复用 watcher 单一路径),对话永不因审批卡死;
  • approval-format.js:审批通知带真实 id(可直接复制进 decide 命令)、多工具合并展示。

兼容性

  • sendPermission 第三参数可选,旧调用形态不变;
  • 队列目录与旧版 approval.ts 相同,CLI 用法一致;
  • 已在本机实测:rebase 到 c4a3a25 后三个文件 node --check 通过,队列模块 13/13 冒烟全绿,桥重启加载新代码运行正常。

🤖 Generated with AtomCode

likedislike
合并受阻
atomgit-bot
atomgit-bot成员
17 天前 评论:

变更摘要

本 PR 针对微信通道审批链路的全程非阻塞改造,修复一次事故中"同一 turn 并发多个工具调用导致会话永久卡死"的三个根因:并发 permission_request 被"已有挂起"守卫丢弃、sendPermission 决定不带 call_id 导致 daemon 对不上具体调用、以及 5 分钟超时默认拒绝与审批挂起语义矛盾且静默清空队列。改动核心是新增仓库内实现的本地审批队列 approval-queue.js(替代 file:/// 绝对路径本地依赖),让 bridge.js 的 onPermission 将并发审批并入同一条挂起记录、handleInbound 直接落决定或"跳过老操作"解锁,并扩展 atomcode-backend.js 的 sendPermission(decision, sessionId, callId) 支持按 call_id 回填,使对话在审批未决时也不会卡死。

主要改动

  • 新增本地审批队列 approval-queue.js:审批记录模型支持多个 call_id 并入(createApproval 创建、appendCallId 追加),采用临时文件 + rename 原子写,沿用 ~/.atomcode/weixin/approval 的 pending/decided 目录,并提供 list/status/decide CLI 供本机模型/用户操作。
  • 并发审批并入同一条挂起记录:bridge.js 的 onPermission 在存在挂起审批时调用 appendCallId 把后续 call_id 并入现有记录并直接返回,不再丢弃第 2、3 条 permission_request;审批通知中嵌入真实审批 id 与可复制的 decide 命令(approval-format.js 支持多工具合并展示)。
  • 扩展 sendPermission 签名:atomcode-backend.js 的 sendPermission(decision, sessionId, callId) 新增可选的 sessionId/callId 参数,请求体带上 session_id 和可选的 call_id,旧调用形态保持不变、向后兼容。
  • 审批决定按 call_id 逐条回填且取消超时自动拒绝:bridge.js 的 watchApproval 改为按审批 id 定向轮询 pollDecided,取到决定后遍历 hit.call_ids 逐个调用 sendPermission 回填,不再 5 分钟超时自动拒绝。
  • handleInbound 审批闸门改造:挂起审批期间,微信用户回复「同意/拒绝」直接调用 decideApproval 落决定并即时回话(不进 turn 队列);普通消息则触发"跳过老操作"(deny 落决定、回填复用 watcher 单一路径),避免审批未决时对话被卡死。
likedislike
不准确?
atomgit-bot
atomgit-bot成员
17 天前 评论:

代码审查

我已完成全部 4 个变更文件的逐一审查。以下为审查结论与发现清单。

审查结论

各文件审查情况

plugins/weixin/adapter/src/approval-format.js — 3 项发现

  • P2: 通知文案"5 分钟未回复自动拒绝"与本 PR 移除超时自动拒绝的行为矛盾(第 124 行)
  • P2: DECIDE 命令硬编码机器专属绝对路径且指向被替换的旧 approval.ts(第 3 行)
  • P2: 输出契约变更未同步测试,approval-format.test.js 现有断言必挂(第 111-128 行)

plugins/weixin/adapter/src/approval-queue.js — 2 项发现

  • P3: CLI 入口自检对相对路径 argv[1] 失效,文档用法 node approval-queue.js list 静默无效(第 170 行)
  • P3: 未使用的 dirname 导入(第 22 行,可选清理)

plugins/weixin/adapter/src/bridge.js — 5 项发现

  • P2: 审批记录 call_ids 为空时 watcher 一条决定都不回填,daemon turn 永久卡死——在无 call_id 事件场景下原样复现本 PR 要修的故障(第 102-104 行)
  • P2: 旧审批的 watcher 无条件 clearPending,竞态下抹掉新审批的挂起状态,审批回复失效并可重新死锁(第 105 行)
  • P2: notifyToast 硬编码 C:/Users/GO2se/.../toast.ps1 且无 spawn error 监听(第 27-28 行)
  • P3: pollDecided 先删决定文件,回填失败时决定永久丢失、无重试(第 96-107 行)
  • P2: 审批异步化后未更新 bridge.test.js,现有审批用例多条断言失败且测试会写真实审批目录(第 142-150 行)

plugins/weixin/atomcode/src/atomcode-backend.js — 无问题。sendPermission 第三参数可选、向后兼容,现有 atomcode-backend.test.js 断言(decision、session_id 捕获)均不受影响。

按优先级统计

  • P0: 0 项
  • P1: 0 项
  • P2: 7 项
  • P3: 3 项

总体风险判断

本 PR 的修复方向正确(队列化 + 多 call_id 并入 + 按 id 定向回填 + 去超时),但存在多处可重新触发"会话卡死"的缺陷:最关键是 call_ids 为空时 watcher 不回填任何决定(旧 approval.ts/无 call_id 事件场景下 turn 依旧永久挂起)与 旧 watcher 无条件清 pending 可能抹掉新审批(竞态下审批回复失效、重新死锁);其次是用户可见文案与实际行为矛盾、硬编码机器专属路径带来的可移植性问题,以及未同步更新的既有测试会导致 CI 必挂。建议在合入前优先修复上述 P2 正确性问题并同步测试。

类型 数量
🔴 阻塞 1
🟡 建议 7

⛔ 需要修改

likedislike
不准确?
atomgit-bot
atomgit-bot成员
17 天前 评论:

AtomGit AI 助手使用指南

在 PR 评论中使用命令即可触发。支持在普通评论和代码行评论中使用。

功能 命令 说明
代码审查 /ai review 检查代码质量、潜在问题、安全风险
PR 摘要 /ai summary 生成 PR 变更内容的结构化摘要
代码解释 /ai explain 解释代码变更的逻辑和意图
自由提问 @atomgit-bot <问题> 询问关于本 PR 的任何问题
帮助 /ai help 显示此帮助信息

免责声明

AI 助手可能存在误判,请结合自身判断。可以对评论点 👍 或 👎 帮助我们改进。

likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/approval-queue.js
@@ -0,0 +19,4 @@
19+import { homedir } from 'node:os';
20+import { randomUUID } from 'node:crypto';
21+import { fileURLToPath } from 'node:url';
22+import { dirname } from 'node:path';
atomgit-bot
atomgit-bot17 天前评论:

🔵 Low Priority

变更行:approval-queue.js:22 import { dirname } from 'node:path';。

受影响行为/契约:该模块内 dirname 从未被引用(路径构造全部用 join 与 fileURLToPath),属死导入。

失败模式:无运行时失败,但属于声而未用的死代码,后续阅读者会误以为存在目录定位逻辑;按仓库 JS 规范应清理。

建议:删除未使用的 dirname 导入,保持模块干净。

likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/approval-format.js
@@ -50,1 +125,4 @@
125+ `本机备用:${DECIDE} ${id ?? '<id>'} yes|no`,
126+ );
127+ return lines.join('\n');
50128}
atomgit-bot
atomgit-bot17 天前评论:

🟡 Medium Priority

变更行:approval-format.js 重写了 formatApproval 输出(第 111-128 行)及 actionLine(第 85-109 行)。

受影响行为/契约:新输出不再包含旧文案「回复 y 同意 | n 拒绝」,write_file/create_file 的提示从「写文件」改为「📄 新建文件」。

失败模式:仓库内未修改的 plugins/weixin/adapter/test/approval-format.test.js 有多条断言必然失败,导致 CI 红:
(第 17 行 …(共5000字)、第 36 行 /weird_tool/ 仍可通过。)

建议:同步更新 plugins/weixin/adapter/test/approval-format.test.js,将断言改为匹配新输出格式(「新建文件」、「回复「同意」= 允许」等);本次改动改动了对外输出契约,应一并提交测试更新。

likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/approval-format.js
@@ -50,0 +121,4 @@
121+ }
122+ lines.push(
123+ '',
124+ '👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(5 分钟未回复自动拒绝)',
atomgit-bot
atomgit-bot17 天前评论:

🟡 Medium Priority

变更行:approval-format.js:124 新增文案「👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(5 分钟未回复自动拒绝)」。

受影响行为/契约:本 PR 的核心变更之一就是去掉 5 分钟超时自动拒绝(approval-queue.js:10「无超时自动拒绝:审批未决就一直挂起」,bridge.js watchApproval 的轮询没有任何超时分支,PR 背景也明确说明删除该语义)。

失败模式:微信用户看到提示后,若 5 分钟内不回复,会以为操作已被自动拒绝、事情已了结;实际上决定永远不会产生,该 turn 一直挂起,直到用户再发一条普通消息才会被"跳过"解锁。这是一条直接面向用户的、与代码实际行为相反的错误指引,由本 diff 引入。

建议:将第 124 行文案改为与新行为一致,去掉"5 分钟未回复自动拒绝"的说法,改为「未决定将一直挂起,直接发任意消息可跳过」。若确实需要超时,应在 watchApproval 中实现超时拒绝并同步文案。

改动建议
124
- '👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(5 分钟未回复自动拒绝)',
124
+ '👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(未决定将一直挂起,直接发消息可跳过)',
应用建议
likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/bridge.js
@@ -70,1 +147,4 @@
147+ `🔐 审批#${rec.id.slice(0, 8)}\n${formatApproval(rec.tool_name, rec.arguments, { said: acc, id: rec.id })}`,
70148 sessions.getContextToken(fromUserId));
149+ notifyToast(`🔐 审批请求#${rec.id.slice(0, 8)}`, `${rec.tool_name} 待决定(微信回复「同意/拒绝」,或发消息跳过)`);
150+ watchApproval(fromUserId, rec.id, rec.sessionId);
atomgit-bot
atomgit-bot17 天前评论:

🟡 Medium Priority

变更行:bridge.js:142 sessions.setPending(fromUserId, { pending: true, approvalId: rec.id })、第 146-150 行新通知与 watcher、第 177-188 行审批回复改为落队列由 watcher 异步回填。

受影响行为/契约:审批从"同步 sendPermission"改为"落队列 + 700ms 轮询异步回填",pending 结构从 {pending:true} 变为 {pending:true, approvalId}。

失败模式:仓库内未修改的 plugins/weixin/adapter/test/bridge.test.js 第 38-58 行的"审批"用例多条断言必然失败,CI 变红:
另外该用例的 onPermission 事件无 call_id,watcher 的 for 循环一条决定也不发(见 bridge.js:102 独立问题),即便等待也不会产生 'allow'。同时测试会真实写入 ~/.atomcode/weixin/approval 目录(污染开发机真实队列,若桥在运行可能被真实 watcher 取走),且 setInterval 会让测试进程多挂 700ms。

建议:本次改动改变了审批回复的处理时序与 pending 数据结构,必须同步更新 bridge.test.js 对应用例(用临时审批目录、缩短/可控轮询间隔、等待 watcher 回填后断言),否则该测试必挂并可能拖住测试进程。

likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/bridge.js
@@ -60,0 +105,4 @@
105+ sessions.clearPending(user);
106+ await ilink.sendMessage(user, hit.status === 'allow' ? `✅ 已允许:${hit.tool_name}` : `⛔ 已拒绝:${hit.tool_name}`, ctx);
107+ } catch (e) { console.error('watchApproval 错误:', e); }
108+ }, 700);
atomgit-bot
atomgit-bot17 天前评论:

🟠 High Priority

变更行:bridge.js watchApproval(94-108 行)中 pollDecided(approvalId) 在 approval-queue.js 119-124 行执行 rmSync(decidedPath(id))(拿走即删),随后才逐 call_id 调用 sendPermission;clearInterval(timer) 只在成功路径(98 行)执行,catch(107 行)只打日志。

影响行为/契约:决定文件的消费(删除)先于决定的投递,且失败路径不停止定时器。

失败模式:(1) 若 sendPermission 内 this.fetch 网络异常抛出,决定文件已被删除,决定永久丢失;catch 后定时器继续运行,而 pollDecided 已返回 null,if (!hit) return 使回调永久空转——700ms 一次的无用轮询永不停止(CPU 泄漏、定时器泄漏);(2) 异常发生在 sessions.clearPending(user)(105 行)之前,该用户的 pending 审批状态永远不清理,此后每条消息都被 handleInbound 的审批闸门拦截成"审批回复/跳过",用户无法正常对话;(3) 即便不抛异常,sendPermission 对非 2xx 只 console.error 不抛错(atomcode-backend.js 75 行),watcher 仍会 clearPending 并回"✅ 已允许"假成功,而 daemon 实际未收到决定,老 turn 不解锁,per-user 串行队列里后续 turn 全部排在永不结束的老 turn 之后,形成死锁。

建议:在 try/catch/finally 中确保任何路径都 clearInterval;将 pollDecided 的删除语义改为"发送成功后再删"或失败时把记录写回 decided 目录以便重试;失败时同样清理 pending 闸门,避免用户被永久锁在审批状态。

likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/approval-queue.js
@@ -0,0 +19,4 @@
19+import { homedir } from 'node:os';
20+import { randomUUID } from 'node:crypto';
21+import { fileURLToPath } from 'node:url';
22+import { dirname } from 'node:path';
atomgit-bot
atomgit-bot17 天前评论:

🔵 Low Priority

变更行:approval-queue.js 22 行 import { dirname } from 'node:path';。经全文检索,dirname 在文件中从未被使用(路径拼接全部走 join/fileURLToPath)。

影响行为/契约:无功能影响,属残留死代码。

建议:删除 import { dirname } from 'node:path'; 这一未使用导入。

改动建议
22
- import { dirname } from 'node:path';
22
+ import { join } from 'node:path';
应用建议
likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/approval-format.js
@@ -50,0 +121,4 @@
121+ }
122+ lines.push(
123+ '',
124+ '👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(5 分钟未回复自动拒绝)',
atomgit-bot
atomgit-bot17 天前评论:

🔵 Low Priority

变更行:approval-format.js 124 行 '👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(5 分钟未回复自动拒绝)'。

影响行为/契约:本 PR 在 bridge.js 明确"去掉超时自动拒绝:审批未决就一直挂起,由用户回复或下一条普通消息触发跳过解锁",但发送给微信用户的通知文案仍承诺"5 分钟未回复自动拒绝"。

失败模式:用户看到文案后可能等待 5 分钟等自动拒绝,实际永远不会有;审批一直挂起,只有再发一条消息才会被"跳过"解锁。误导用户、与修复后的真实行为不一致,属于本 PR 改动引入的契约与文案矛盾。

建议:同步更新通知文案,去掉"5 分钟未回复自动拒绝",改为说明"未决定则持续等待,发新消息自动跳过"。

改动建议
124
- '👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(5 分钟未回复自动拒绝)',
124
+ '👉 直接回复:「同意」= 允许,「拒绝」= 拒绝(未决定会一直等待,发新消息可跳过)',
应用建议
likedislike
不准确?
atomgit-bot
atomgit-bot成员17 天前进行代码检视1
plugins/weixin/adapter/src/approval-queue.js
@@ -0,0 +1,0 @@
1+// approval-queue.js — 微信通道本地审批队列(仓库内实现,替代 file:/// 本地依赖)。
atomgit-bot
atomgit-bot17 天前评论:

🔵 Low Priority

变更行:approval-queue.js 为全新文件(172 行),实现 createApproval/appendCallId/decideApproval/pollDecided/atomicWrite 等核心逻辑;本 PR 未新增任何针对该模块的测试(变更清单仅 4 个 src 文件)。

影响行为/契约:该模块是本 PR 修复"并发审批丢弃/决定丢 call_id"的关键路径,包含并发并入、临时文件+rename 原子写、decided 取走即删、跨进程(CLI 与 bridge)共享目录等高风险语义;PR 描述声称"队列模块 13/13 冒烟全绿",但这些冒烟测试未随仓库提交,无法回归。

失败模式:上述并发并入、空 call_ids、decided 记录兼容旧 approval.ts(无 call_ids 字段)等边界/竞态问题(见本批次其它 finding)没有任何测试拦截,后续改动极易回归到 turn 卡死。属于关键路径缺失关键测试。

建议:新增 approval-queue.test.js,覆盖空 call_id、多 call_id 并入、decide/poll 生命周期、旧格式记录兼容等用例,并把 PR 描述中的"13/13 冒烟"纳入仓库测试。

likedislike
不准确?
Ggcw_HKwsZ0n2
16 天前 推送  1 个提交:abe27414-feat(weixin): 审批交互四模块——短语闸门/溯源通知/思考心跳/会话白名单
atomgit-bot
atomgit-bot成员
16 天前 评论:

AtomGit AI 助手使用指南

在 PR 评论中使用命令即可触发。支持在普通评论和代码行评论中使用。

功能 命令 说明
代码审查 /ai review 检查代码质量、潜在问题、安全风险
PR 摘要 /ai summary 生成 PR 变更内容的结构化摘要
代码解释 /ai explain 解释代码变更的逻辑和意图
自由提问 @atomgit-bot <问题> 询问关于本 PR 的任何问题
帮助 /ai help 显示此帮助信息

免责声明

AI 助手可能存在误判,请结合自身判断。可以对评论点 👍 或 👎 帮助我们改进。

likedislike
不准确?
atomgit-bot
atomgit-bot成员
16 天前 评论:

变更摘要

本 PR 修复微信通道审批链路导致的会话卡死问题:同一 turn 内并发多个工具调用时,后续 permission_request 被"已有挂起"守卫丢弃、决定回填不带 call_id 且 5 分钟超时默认拒绝,导致 daemon 等不齐决定、turn 永久卡死。核心思路是让审批链路全程非阻塞:新增仓库内实现的 approval-queue.js 本地审批队列(替代 file:/// 绝对路径依赖),审批记录支持多个 call_id 并入、原子写入并提供 list/status/decide CLI;sendPermission 签名扩展为 (decision, sessionId, callId) 向后兼容;bridge.js 的 onPermission 将并发审批并入同一条挂起、watchApproval 去掉超时自动拒绝并在决定到达后按 call_id 逐个回填;handleInbound 配合新增的 phrase-gate.js 短语闸门,使审批挂起时用户回复「同意/拒绝」直接落决定,普通消息触发"跳过老操作"解锁,对话永不因审批卡死。同时新增「同意会话」工具白名单机制(sessions.js 的 getWhitelist/whitelist/isWhitelisted/clearWhitelist),并在 approval-format.js 中让审批通知携带真实 id、合并展示多工具信息。

主要改动

  • 新增 approval-queue.js 审批队列模块:审批记录支持同一 turn 并发多工具调用的多个 call_id 并入(createApproval/appendCallId),采用临时文件 + renameSync 原子写,提供 list/status/decide CLI,复用 ~/.atomcode/weixin/approval 的 pending/decided 目录保持兼容。

  • sendPermission 支持 call_id 回填:atomcode-backend.js 中签名扩展为 sendPermission(decision, sessionId, callId),请求体显式携带 session_id 并在有 callId 时附加 call_id,第三参数可选、旧调用形态不变,修复决定对不上具体调用导致的 turn 卡死。

  • 并发审批并入与无超时回填:bridge.js 的 onPermission 在已有挂起审批时通过 appendCallId 并入新 call_id(不再丢弃后续 permission_request);watchApproval 移除超时自动拒绝,轮询 pollDecided 定向取走决定后按 call_ids 逐个调用 sendPermission 回填。

  • 审批回复短语闸门与自动跳过:新增 phrase-gate.js,以归一化+整消息全等的锚点表(ANCHORS)判定 allow/deny/allow_session/allow_batch/hold/pass,数字与单字符永不误判为授权;handleInbound 中审批挂起时命中锚点直接 decideApproval 落决定,普通消息则自动以 no 落决定跳过老操作并清理挂起,配合 per-user 串行队列(enqueue)保证不并发不卡死。

  • 审批通知与会话白名单:approval-format.js 重构为"人话优先"渲染,通知携带真实审批 id(可直接复制进 decide 命令)与模型意图(said)、多工具合并展示,尾行提示锚点与 ANCHOR_HINTS 同源;sessions.js 新增会话级工具白名单存储,「同意会话」后同类工具免审(危险工具经 isDangerousTool 把关后入列),/cd 切目录开新会话时 clearWhitelist 清空授权。

likedislike
不准确?
gcw_HKwsZ0n2
16 天前 评论:

追加提交 abe2741:审批交互四模块

在根因修复(406d68a)之上,追加四个交互层模块:

1. phrase-gate.js 短语闸门(字节级确定性)

  • 归一化(去标点/全角转半角/小写)后整消息全等查表,绝无子串误判("我不同意"≠同意);
  • 数字与单字符(1/0/y,举手/测试惯用语)永不锚定,原样透传对话队列;
  • 锚点必须 ≥2 汉字;危险工具(rm/taskkill/format 等)黑名单永不免审;
  • 锚点表与通知尾行同源渲染(表即文档,用户每次都看到合法回复词)。

2. 溯源通知

审批通知升级为三要素:做什么(工具+参数)+为什么(模型审批前刚说的话)+怎么批(锚点行)。公理:每条消息自包含全部操作知识,用户默认失忆。

3. 思考心跳

iLink 无流式,思考期盲等——15s/40s/90s 三级心跳提示,首个 text 事件或 turn 结束即取消,零打断零 token。

4. 会话白名单

  • 「同意会话」→同类工具本会话免审(危险工具除外,/cd 切会话自动清空);
  • 「同意打包」→放行当前批次全部 call_id;
  • 「先别/等等」→保持挂起转模型解释。

验证

  • phrase-gate 31/31、approval-queue 13/13 沙箱冒烟全绿(含子串假阳性/数字透传/全角归一化反例);
  • 全部桥源文件 node --check 通过;
  • 桥重启加载实测:启动日志干净,运行中。

🤖 Generated with AtomCode

likedislike
atomgit-bot
atomgit-bot成员
16 天前 评论:

代码审查

审查结论

共报告 12 个问题:P1 × 1,P2 × 5,P3 × 6。

逐文件确认

  • plugins/weixin/adapter/src/approval-format.js — 发现问题 3 个:输出契约变更未同步测试(P2)、"5 分钟未回复自动拒绝"文案与实现矛盾(P3)、DECIDE 硬编码用户绝对路径且指向已被替换的旧 approval.ts(P3)。
  • plugins/weixin/adapter/src/approval-queue.js — 发现问题 3 个:pollDecided 先删后发送导致决定丢失(P1,锚定在 bridge 消费端)、call_ids 为空时零回填(P2,锚定在 bridge watcher)、未使用 dirname 导入(P3)、缺单元测试(P3)。
  • plugins/weixin/adapter/src/bridge.js — 发现问题 6 个:hold 分支缺 return 导致「先别」被自动拒绝(P2)、call_ids 为空时 watcher 零次回填(P2)、pollDecided 先删后发送(P1)、bridge.test.js 未同步(P2)、watcher 定时器永不清理(P3)、/cd 无参分支未清空白名单(P3)。
  • plugins/weixin/adapter/src/phrase-gate.js — 发现问题 1 个:isDangerousTool 正则过宽误伤且漏网 rd /s /q 等 Windows 删除命令,白名单旁路存在安全缺口(P2)。
  • plugins/weixin/adapter/src/sessions.js — 无问题(白名单存取逻辑与既有 _entry 模式一致,新增方法均被 bridge 正确消费)。
  • plugins/weixin/atomcode/src/atomcode-backend.js — 无问题(sendPermission 第三参数可选、body 构造向后兼容,this.sessionId 兜底链完整)。

总体风险判断

本 PR 修复方向正确(并发并入、call_id 回填、非阻塞审批、短语闸门),但存在两个足以复现"事故"级别的回归点:① watcher 在 call_ids 为空时零次回填,决定发不出、turn 重蹈卡死;② decided 记录先删后发送,任何一次回填失败即永久丢失决定并卡死串行队列。二者均落在线程最核心的审批回填路径上,建议合并前优先修复。其余为契约/测试未同步(CI 必红)、安全旁路正则缺口与文案/硬编码等中低危问题。整体风险:中等偏高,建议修复 P1/P2 后合入。

类型 数量
🔴 阻塞 0
🟡 建议 6

💬 仅评论

likedislike
不准确?
atomgit-bot
atomgit-bot成员16 天前进行代码检视1
plugins/weixin/adapter/src/approval-format.js
@@ -3,0 +1,3 @@
3-const TAIL = '\n\n回复 y 同意 | n 拒绝';
1+// 审批通知格式化:人话优先。
2+// 微信用户不读代码 —— 结构:🗣 它说(模型自己的话) → 一句话动作 → 至多一行代码预览 → 本机决定命令(填真实 id)。
3+const DECIDE = 'node C:/Users/GO2se/.atomcode/weixin/approval/approval.ts decide';
atomgit-bot
atomgit-bot16 天前评论:

🔵 Low Priority

变更行:approval-format.js 第 3 行 const DECIDE = 'node C:/Users/GO2se/.atomcode/weixin/approval/approval.ts decide';bridge.js 第 24 行 toast 脚本 'C:/Users/GO2se/.atomcode/weixin/toast.ps1'。
受影响行为/契约:这两处把单个机器用户(GO2se)的绝对路径硬编码进发送给每个微信用户的审批通知和本地 toast 调用;且 DECIDE 指向的是旧版本地 approval.ts——本 PR 正是把它替换为仓库内 approval-queue.js。
失败模式:在其他机器/其他用户部署时,通知里的"本机备用"命令与 toast 脚本路径必然不存在,功能失效;新装环境没有旧 approval.ts,通知中的备用命令是不可用的假入口。

建议:DECIDE 命令改为从 import.meta.url 推导本文件路径(即 approval-queue.js),toast.ps1 路径改为配置/环境变量注入。

likedislike
不准确?
Ggcw_HKwsZ0n2
16 天前 推送  1 个提交:a6fbd32e-fix(weixin): 审查修复——hold 分支缺 return 误跳过审批、空 call_ids 零回填、watcher 竞态清 pending
atomgit-bot
atomgit-bot成员
16 天前 评论:

AtomGit AI 助手使用指南

在 PR 评论中使用命令即可触发。支持在普通评论和代码行评论中使用。

功能 命令 说明
代码审查 /ai review 检查代码质量、潜在问题、安全风险
PR 摘要 /ai summary 生成 PR 变更内容的结构化摘要
代码解释 /ai explain 解释代码变更的逻辑和意图
自由提问 @atomgit-bot <问题> 询问关于本 PR 的任何问题
帮助 /ai help 显示此帮助信息

免责声明

AI 助手可能存在误判,请结合自身判断。可以对评论点 👍 或 👎 帮助我们改进。

likedislike
不准确?
atomgit-bot
atomgit-bot成员
16 天前 评论:

变更摘要

本 PR 修复微信通道审批链路的三个根因问题:并发审批被"已有挂起"守卫丢弃、决定不带 call_id 导致 daemon 对不上调用、超时默认拒绝与挂起语义矛盾。核心思路是让审批链路全程非阻塞——新增仓库内实现的 approval-queue.js 本地审批队列(支持多 call_id 并入与原子写),改造 bridge.js 的 onPermission/watchApproval/handleInbound 使并发审批合并、决定逐 call_id 回填、非审批消息自动"跳过老操作"解锁;同时扩展 atomcode-backend.js 的 sendPermission 签名、新增 phrase-gate.js 短语闸门与 sessions.js 会话级工具白名单,并升级 approval-format.js 审批通知展示。

主要改动

  • 新增 approval-queue.js 本地审批队列:审批记录支持多个 call_id(并发工具调用并入同一条挂起审批,不再丢弃后续 permission_request);采用临时文件 + rename 原子写;提供 list/status/decide CLI,沿用 ~/.atomcode/weixin/approval 的 pending/decided 目录,与旧版 approval.ts 兼容。
  • bridge.js 审批链路全程非阻塞:onPermission 对已有挂起审批的并发事件用 appendCallId 并入而非丢弃,并新增白名单免审与 notifyToast 本地提醒;watchApproval 移除超时自动拒绝,改为轮询 pollDecided 后按 call_ids 逐个回填 sendPermission。
  • handleInbound 审批闸门与自动跳过:有挂起审批时,用户回复「同意/拒绝/同意会话/先别」经 phrase-gate.js 归一化+整消息全等匹配后直接落决定;普通消息(数字/单字符/表外内容)触发 decideApproval(..., 'no') 自动跳过卡死的老操作,对话永不因审批卡死。
  • atomcode-backend.js sendPermission 签名扩展:由 (decision) 扩展为 (decision, sessionId, callId),session_id 显式传入避免多用户并发串号,call_id 可选(不带则 daemon 回退旧行为),保持向后兼容。
  • 新增 phrase-gate.js 短语闸门与 sessions.js 白名单:matchGate 提供字节级确定性匹配(锚点表 ANCHORS 与通知尾行 ANCHOR_HINTS 同源);「同意会话」将工具写入 sessions.js 新增的会话级白名单实现本会话免审(危险工具如 rm/taskkill/format 经 isDangerousTool 把关永不入列,/cd 切会话时清空)。
likedislike
不准确?
gcw_HKwsZ0n2
16 天前 评论:

追加提交 a6fbd32:代码审查修复

对 abe2741 做逐行审查(结合 PR 页自动审查报告核实),修复三处:

  1. hold 分支缺 return(真 bug):「先别/等等」分支发完提示后 fall-through 到下方"自动跳过"块,审批被 deny——与"保持挂起转模型解释"的意图完全相反。已补 return;
  2. 空 call_ids 零回填(核实 PR 页审查 P2):事件不带 call_id 时 watcher 拿到决定也一个都不回填,turn 卡死。兜底改为发一次不带 call_id 的决定;
  3. watcher 竞态清 pending(核实 PR 页审查 P2):旧 watcher 无条件 clearPending 会抹掉新审批的挂起状态。改为仅当挂起的仍是本审批时才清。

验证

  • phrase-gate 31/31、approval-queue 13/13 复跑全绿;
  • node --check 通过;桥重启加载实测运行中。

PR 页自动审查报告中的其余发现(approval-format 文案未同步、toast 硬编码路径、pollDecided 先删后发无重试、测试文件缺失)已记录,作为合并后 follow-up 处理。

🤖 Generated with AtomCode

likedislike
Ggcw_HKwsZ0n2
16 天前 推送  1 个提交:5a1478fc-fix(weixin): 心跳文案自包含——带任务摘要,锚点提示仅在真有挂起审批时出现
atomgit-bot
atomgit-bot成员
16 天前 评论:

AtomGit AI 助手使用指南

在 PR 评论中使用命令即可触发。支持在普通评论和代码行评论中使用。

功能 命令 说明
代码审查 /ai review 检查代码质量、潜在问题、安全风险
PR 摘要 /ai summary 生成 PR 变更内容的结构化摘要
代码解释 /ai explain 解释代码变更的逻辑和意图
自由提问 @atomgit-bot <问题> 询问关于本 PR 的任何问题
帮助 /ai help 显示此帮助信息

免责声明

AI 助手可能存在误判,请结合自身判断。可以对评论点 👍 或 👎 帮助我们改进。

likedislike
不准确?
atomgit-bot
atomgit-bot成员
16 天前 评论:

变更摘要

本 PR 修复微信通道审批链路的三个根因:同一 turn 内并发 permission_request 被"已有挂起"守卫丢弃、sendPermission 决定不带 call_id 导致 daemon 对不上具体调用、5 分钟超时自动拒绝与审批挂起语义矛盾且静默清队列。核心改动是新增仓库内实现的本地审批队列 approval-queue.js(替代 file:/// 绝对路径依赖,支持多个 call_id 并入同一条审批),扩展 sendPermission 签名支持 call_id 定向回填,去除超时自动拒绝,并通过 phrase-gate.js 短语闸门让微信回复「同意/拒绝」直接落决定、普通消息自动跳过卡死的老操作,使审批链路全程非阻塞,会话不再因审批卡死。

主要改动

  • 新增审批队列模块 approval-queue.js:审批记录支持多个 call_id 并入同一条挂起记录(appendCallId),采用临时文件 + rename 原子写,提供 list/status/decide CLI,沿用 ~/.atomcode/weixin/approval 的 pending/decided 目录,不再有超时自动拒绝。
  • atomcode-backend.js sendPermission 签名扩展:由 (decision) 扩展为 (decision, sessionId, callId),callId 可选、向后兼容;显式携带 session_id,多用户并发不串号,daemon 可按 call_id 对号入座,修复决定对不上调用导致的 turn 卡死。
  • bridge.js 并发审批并入与定向回填:onPermission 中同 turn 的后续 permission_request 通过 appendCallId 并入既有挂起而不丢弃;watchApproval 轮询到决定后按记录里的全部 call_ids 逐个回填 sendPermission,且只在挂起仍属于本审批时才清理状态,避免旧 watcher 抹掉新审批。
  • handleInbound 审批闸门与自动跳过策略:挂起审批时,回复经 phrase-gate.js 短语闸门(归一化 + 整消息全等,数字/单字符永不安锚)命中「同意/拒绝」直接落决定并立刻回话;普通消息则自动 deny 落决定跳过老操作,回填复用 watcher 单一路径,对话永不因审批卡死。
  • sessions.js 会话级工具白名单与危险工具把关:新增 whitelist/isWhitelisted/clearWhitelist,「同意会话」可将同类工具加入本会话免审;phrase-gate.js 的 isDangerousTool 黑名单(rm/taskkill/format 等)保证危险工具永不免审,/cd 切会话时清空白名单。
likedislike
不准确?
atomgit-bot
atomgit-bot成员
16 天前 评论:

代码审查

审查结论

逐文件核查结果(6/6):

  • plugins/weixin/adapter/src/approval-format.js — 报告 3 项:输出契约变更破坏既有测试(P1)、「5 分钟自动拒绝」文案矛盾(P3)、硬编码家目录路径且 DECIDE 指向旧 approval.ts(P3)
  • plugins/weixin/adapter/src/bridge.js — 报告 3 项:合并审批静默放行未见工具(P2)、watcher 回填中途失败致 turn 永久卡死(P2)、/cd 无参分支未清空白名单(P2)
  • plugins/weixin/adapter/src/phrase-gate.js — 报告 1 项:口语词「好的/行/可以」被直接视为审批同意,可能放行危险工具(P2)
  • plugins/weixin/adapter/src/approval-queue.js — 报告 2 项:未使用 dirname 导入(P3)、高危逻辑无单元测试(P3)
  • plugins/weixin/adapter/src/sessions.js — 无问题(白名单存取实现正确;唯一分歧点在 bridge.js 调用侧,已在上文报告)
  • plugins/weixin/atomcode/src/atomcode-backend.js — 无问题(sendPermission 第三参数可选,向后兼容,既有调用与测试均不受影响)

上一轮问题回查: hold 分支缺 return 与 call_ids 空数组零次回填两项已被本次 diff 修复;文案矛盾、硬编码路径、dirname 死导入、队列无测试四项仍存在,已在本次重新报告。

按优先级统计: P1 ×1(测试套件确定性失败,CI 阻塞)、P2 ×4(静默合并放行、回填失败无重试、白名单残留、口语词误授权)、P3 ×4(文案矛盾、硬编码路径、死导入、缺测试)。

总体风险判断: 本次改动的核心修复方向(并发审批并入、按 call_id 回填、审批回复不经过 turn 队列)逻辑自洽,正常路径可解原事故;但存在两处会重新引入"turn 卡死"的高风险路径(watcher 回填失败无重试、通知抛错后审批无 watcher),以及静默合并放行与口语词误授权两个授权边界安全缺口;同时既有测试套件被确定性地破坏而未同步更新,合入前应优先修复 P1/P2 项并补跑全部测试。

类型 数量
🔴 阻塞 1
🟡 建议 4

⛔ 需要修改

likedislike
不准确?
atomgit-bot
atomgit-bot成员16 天前进行代码检视1
plugins/weixin/adapter/src/sessions.js
@@ -23,0 +23,4 @@
23+ // ── 会话级工具白名单(「同意会话」授权包) ──
24+ // 用户回复「同意会话」时,把当前审批的工具记入白名单:同类工具本会话内不再逐条审批。
25+ // 危险工具(rm/taskkill/format 等,见 phrase-gate.isDangerousTool)永不入列——
26+ // 由调用方在入列前把关,这里只做存储。/cd 切目录开新会话时应一并清空(见 clearAll)。
atomgit-bot
atomgit-bot16 天前评论:

🔵 Low Priority

建议:将注释中的「见 clearAll」改为「见 clearWhitelist」,与实际方法名保持一致。

改动建议
26
- // 由调用方在入列前把关,这里只做存储。/cd 切目录开新会话时应一并清空(见 clearAll)。
26
+ // 由调用方在入列前把关,这里只做存储。/cd 切目录开新会话时应一并清空(见 clearWhitelist)。
应用建议
likedislike
不准确?
atomgit-bot
atomgit-bot成员
16 天前 评论:

代码审查

审查结论

共报告 11 个问题:P2 × 3,P3 × 8,无 P0/P1。整体判断:本 PR 的核心思路(审批落本地队列、并发并入、按 call_id 回填、去掉超时自动拒绝、普通消息自动跳过解锁)方向正确,上一轮报告的"hold 缺 return"与"call_ids 为空零次回填"两个问题已在本次修复;但仍存在若干可能重新触发"turn 卡死/决定丢失"的路径,以及多处与自身注释/文案矛盾的实现,建议合入前处理 P2 三项。

各文件审查情况

  • plugins/weixin/adapter/src/approval-format.js:发现问题 2 个——审批通知文案仍称"5 分钟未回复自动拒绝"(P3,与已移除的超时矛盾);DECIDE 命令硬编码机器特定绝对路径且指向已被替换的旧 approval.ts(P3)。其余格式化/截断逻辑无实质缺陷。
  • plugins/weixin/adapter/src/approval-queue.js:发现问题 2 个——未使用的 dirname 导入(P3,死代码);并发并入/原子写/跨进程读写等高危逻辑无任何单元测试(P3)。其 pollDecided 先删后发 的设计缺陷与 bridge.js 的回填丢失问题合并报告于 bridge.js。
  • plugins/weixin/adapter/src/bridge.js:发现问题 6 个——决定在回填成功前被删除、发送失败时决定永久丢失致 turn 重新卡死(P2);this.sessionId 跨用户共享兜底在记录缺 sessionId 时把决定发到别的用户会话(P2);hold 分支后下一条普通消息会静默 deny 挂起审批、「说同意继续」无法兑现(P2);toast spawn 缺 'error' 监听致进程崩溃风险(P3);多个无 call_id 请求合并后只回填 1 次(P3);嵌套三元违反规范(P3);「同意会话」仅首个工具入白名单(P3)。
  • plugins/weixin/adapter/src/phrase-gate.js:无问题(归一化+整消息全等的判定逻辑、危险工具黑名单审查通过,数字/单字符守卫有效)。
  • plugins/weixin/adapter/src/sessions.js:无问题(白名单存取与 /cd 清空逻辑一致)。
  • plugins/weixin/atomcode/src/atomcode-backend.js:发现问题 1 个(与 bridge.js:101 合并报告的跨用户 this.sessionId 兜底,根因在 sendPermission 第 69 行)。

风险判断

P2 三项(决定丢失、会话串号、hold 语义矛盾)均与本 PR 修复的"turn 卡死"事故直接相关,属于修复不完整或引入的变体,建议优先处理;其余 P3 项为文案/可移植性/规范/测试缺口,可随迭代修复。

类型 数量
🔴 阻塞 0
🟡 建议 3

💬 仅评论

likedislike
不准确?
Ggcw_HKwsZ0n2
16 天前 推送  1 个提交:426e376d-feat(weixin): 思维链摘要推送——reasoning 事件透传 + 里程碑式聚合
atomgit-bot
atomgit-bot成员
16 天前 评论:

AtomGit AI 助手使用指南

在 PR 评论中使用命令即可触发。支持在普通评论和代码行评论中使用。

功能 命令 说明
代码审查 /ai review 检查代码质量、潜在问题、安全风险
PR 摘要 /ai summary 生成 PR 变更内容的结构化摘要
代码解释 /ai explain 解释代码变更的逻辑和意图
自由提问 @atomgit-bot <问题> 询问关于本 PR 的任何问题
帮助 /ai help 显示此帮助信息

免责声明

AI 助手可能存在误判,请结合自身判断。可以对评论点 👍 或 👎 帮助我们改进。

likedislike
不准确?
gcw_HKwsZ0n2
16 天前 评论:

追加提交 426e376:思维链摘要推送

SSE 抓流实测:daemon 已流式转发思考模型的 reasoning delta(一次 turn 28 条,如 qwen3.8-27b 的思考原文),桥此前直接丢弃——本提交打通最后一环,纯桥侧实现、零 daemon 改动。

实现

  • atomcode-backend.js:dispatch 补 reasoning 分支,透传给 onReasoning 回调;
  • bridge.js 里程碑式聚合:delta 攒缓冲,每约 220 字切一块,思考期最多推 3 条(💭 前缀,绝不逐 delta 倾倒);
  • 与心跳协同:首条摘要替代 15s 心跳,40s/90s 心跳保留兜底;首个 text 事件即停;
  • 每条摘要自带锚点行(有挂起审批时),审批通知优先级不受影响。

终态消息序列

0s   🤔 已收到
~15s 💭 正在分析:「xxx」(+锚点行,如有挂起审批)
~40s 💭 下一段思路…
?s   🔐 审批通知(独占,如有)
终答  整段回复

验证

  • node --check 双文件通过;dispatch reasoning 透传冒烟 OK;
  • 桥重启加载实测运行中。

可回退性

本提交独立成 commit,git revert 426e376 即可单独撤除思维链功能,不影响前序修复。

🤖 Generated with AtomCode

likedislike
atomgit-bot
atomgit-bot成员
16 天前 评论:

变更摘要

本 PR 修复微信通道审批链路的三类事故根因(并发审批被"已有挂起"守卫丢弃、sendPermission 决定不带 call_id、超时自动拒绝与挂起语义矛盾导致 turn 永久卡死),将审批链路改为全程非阻塞:新增仓库内实现、支持多 call_id 并入与原子写的本地审批队列模块 approval-queue.js(替代 file:/// 绝对路径依赖);sendPermission 签名扩展为 (decision, sessionId, callId) 并向后兼容;bridge.js 并发审批并入同一条挂起记录、决定到达后按 call_id 逐个回填、去掉超时自动拒绝;审批回复经新增的短语闸门 phrase-gate.js 直接落决定,普通消息自动跳过挂起的老操作,并在 sessions.js 增加会话级工具白名单(「同意会话」免审、危险工具永不免审),同时 atomcode-backend.js 新增 reasoning 思维链事件转发。

主要改动

  • 新增审批队列模块 approval-queue.js:审批记录支持多个 call_id 并入(appendCallId),原子写(临时文件 + renameSync),沿用 ~/.atomcode/weixin/approval 的 pending/decided 目录并移除超时自动拒绝,提供 list/status/decide CLI,取代 file:/// 绝对路径的本地依赖。
  • atomcode-backend.js 的 sendPermission 签名扩展为 (decision, sessionId, callId):决定体新增 call_id 字段,使 daemon 能对号入座具体调用;sessionId 显式传入避免多用户并发串号,第三参数可选保持旧调用形态兼容。
  • bridge.js 的 onPermission 并发审批并入:同一 turn 后续 permission_request 通过 appendCallId 并入现有挂起审批(不再丢弃),并新增会话白名单免审逻辑(isDangerousTool 判定的危险工具永不自动放行)。
  • bridge.js 的 watchApproval 与 handleInbound 解除卡死:watchApproval 按 id 定向轮询决定并逐 call_id 回填 sendPermission;handleInbound 在审批挂起时按 phrase-gate.js 闸门结果直接落决定(「同意/拒绝/同意会话/先别」),非审批消息自动 deny 跳过老操作并交由 watcher 单一路径回填,对话不再因审批卡死。
  • 新增 phrase-gate.js 短语闸门与 sessions.js 会话白名单:归一化 + 整消息全等判定(数字/单字符/表外内容透传不误判授权),锚点表与通知提示同源;「同意会话」将同类工具记入 sessions.js 的会话级工具白名单,/cd 切会话时通过 clearWhitelist 清空,危险工具永不免审。
likedislike
不准确?
gcw_HKwsZ0n2
16 天前 评论:

追加提交 426e376:思维链摘要推送

SSE 抓流实测:daemon 已流式转发思考模型的 reasoning delta(一次 turn 28 条,如 qwen3.8-27b 的思考原文),桥此前直接丢弃——本提交打通最后一环,纯桥侧实现、零 daemon 改动。

实现

  • atomcode-backend.js:dispatch 补 reasoning 分支,透传给 onReasoning 回调;
  • bridge.js 里程碑式聚合:delta 攒缓冲,每约 220 字切一块,思考期最多推 3 条(💭 前缀,绝不逐 delta 倾倒);
  • 与心跳协同:首条摘要替代 15s 心跳,40s/90s 心跳保留兜底;首个 text 事件即停;
  • 每条摘要自带锚点行(有挂起审批时),审批通知优先级不受影响。

验证

  • node --check 双文件通过;dispatch reasoning 透传冒烟 OK;桥重启加载实测运行中。

可回退性

本提交独立成 commit,git revert 426e376 可单独撤除思维链功能,不影响前序修复。

🤖 Generated with AtomCode

likedislike
gcw_HKwsZ0n2
16 天前 评论:

追加提交 426e376:思维链摘要推送

SSE 抓流实测:daemon 已流式转发思考模型的 reasoning delta(一次 turn 28 条),桥此前直接丢弃——本提交打通最后一环,纯桥侧实现、零 daemon 改动。

  • atomcode-backend.js: dispatch 补 reasoning 分支,透传 onReasoning 回调;
  • bridge.js: 里程碑式聚合,每约 220 字切一块,思考期最多 3 条(💭);首条摘要替代 15s 心跳,40s/90s 兜底;首个 text 事件即停;摘要自带锚点行(有挂起审批时)。

验证:node --check 通过;dispatch reasoning 透传冒烟 OK;桥重启实测运行中。可回退:git revert 426e376 可单独撤除本功能。

🤖 Generated with AtomCode

likedislike
gcw_HKwsZ0n2
16 天前 评论:

追加提交 426e376:思维链摘要推送。SSE 实测 daemon 已流式转发 reasoning delta,桥补 onReasoning 透传 + 里程碑式聚合(约 220 字/块,思考期最多 3 条,首条替代 15s 心跳,text 即停,自带锚点行)。node --check 通过,透传冒烟 OK,桥重启实测运行中。可单独 revert。

likedislike
atomgit-bot
atomgit-bot成员
16 天前 评论:

代码审查

审查结论

各文件审查确认

  • plugins/weixin/adapter/src/approval-format.js — 已审查:输出格式重写导致既有测试必然失败(P2);「5 分钟自动拒绝」文案与实现矛盾(P3);DECIDE 硬编码机器路径且指向旧 approval.ts(P3)。
  • plugins/weixin/adapter/src/approval-queue.js — 已审查:队列模块本身逻辑(原子写、多 call_id 并入、CLI)未见新问题;遗留未使用的 dirname 导入(P3)。
  • plugins/weixin/adapter/src/bridge.js — 已审查:notifyToast 未监听 spawn error 事件导致非 Windows 环境崩溃(P2);审批决定先消费后发送、失败无重试会复现 turn 死锁(P2);非锚点肯定答复被自动 deny(P2);/cd 无参分支未清空白名单(P2);hold 分支消息丢弃与语义冲突(P3);watcher 定时器泄漏(P3)。
  • plugins/weixin/adapter/src/phrase-gate.js — 已审查:锚点全等与归一化逻辑本身正确,但"表外→pass→auto-skip deny"的策略在 bridge.js 层造成肯定答复误拒(见 bridge.js P2 条目);本文件无独立新问题。
  • plugins/weixin/adapter/src/sessions.js — 已审查:no issues(白名单存取逻辑正确;其与 /cd 的不一致在 bridge.js 有参/无参分支,已归入 bridge.js 的 P2 条目)。
  • plugins/weixin/atomcode/src/atomcode-backend.js — 已审查:no issues(sendPermission 第三参数可选、向后兼容,reasoning 事件转发有可选链保护,既有 atomcode-backend 测试兼容)。

按优先级统计

  • P0:0
  • P1:0
  • P2:5(bridge.js ×4、approval-format.js ×1)
  • P3:5(回查遗留 3 项 + 新增 2 项)

总体风险判断

本次改动的核心目标(并发审批并入、call_id 回填、非阻塞队列)方向正确,并发审批不再被丢弃、决定带 call_id 回填这两条主线修复是成立的。但改动仍存在实质性风险:①审批决定"先消费后发送、无重试"的路径在 daemon 短暂不可用时会让决定永久丢失,恰可复现本 PR 声称修复的 turn 死锁;②"非锚点消息自动 deny"策略会把高频的「好/嗯/ok/同意吧」等肯定答复误判为拒绝,属于高概率的用户意图误执行;③notifyToast 的 spawn error 事件未处理,在非 Windows 环境(含 CI)首个审批到达即进程崩溃;④formatApproval 输出重写未同步测试,CI 必然挂 4 个用例。整体判定:功能修复方向正确但可靠性/回归防护不足,建议合并前先修复 P2 各项(尤其决定消费时序与 spawn 崩溃),并同步测试与文案。

类型 数量
🔴 阻塞 0
🟡 建议 6

💬 仅评论

likedislike
不准确?
atomgit-bot
atomgit-bot成员16 天前进行代码检视1
plugins/weixin/adapter/src/bridge.js
@@ -98,0 +270,4 @@
270+ ilink.sendMessage(user,
271+ skipped.ok
272+ ? `⏭️ 上一个操作(${skipped.rec?.tool_name ?? '审批'})未决定,已自动跳过,你这条消息继续处理。若要允许类似操作,届时回复「同意」。`
273+ : `⏭️ 上一条审批已不在待决状态(${skipped.message}),你这条消息继续处理。`, ctx);
atomgit-bot
atomgit-bot16 天前评论:

🟡 Medium Priority

建议:把"非锚点消息自动 deny"收敛为"确属新对话消息才跳过":可在 phrase-gate 增加近义肯定/否定集合,或将 pass 消息改为仅当包含明确否定词时才 deny,否则保持挂起并回复锚点提示。

likedislike
不准确?
atomgit-bot
atomgit-bot成员16 天前进行代码检视1
plugins/weixin/adapter/src/bridge.js
@@ -9,29 +22,30 @@
22+ try {
23+ const ps = spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
24+ 'C:/Users/GO2se/.atomcode/weixin/toast.ps1', '-Title', title, '-Body', body],
25+ { stdio: 'ignore', windowsHide: true });
26+ ps.unref();
27+ } catch (e) { console.error('toast 失败(忽略):', e.message); }
28+}
929 
1030// 把 /cd 的参数解析成绝对路径(纯函数,无 IO)。支持 `~`/`~/x` 展开、绝对路径、
1131// 以及相对当前目录 `base`。安全口径:只要目录存在即可切(边界靠 allowlist)。
@@ -19,6 +39,15 @@ export function resolveCdTarget(arg, base, home) {
1939 
2040export function makeBridge({ sessions, backend, ilink, allowlist, thinkingAck = false, defaultWorkingDir }) {
2141 assertBackend(backend);
42+ // per-user 串行队列:同一用户同一时刻只允许一个活跃 turn(daemon 对同 session 并发 /chat 返回 409)。
43+ // 审批挂起时新消息排队等待,不并发、不丢失,链路不被卡死。
44+ const queues = new Map(); // fromUserId -> Promise 链尾
45+ function enqueue(user, task) {
46+ const prev = queues.get(user) ?? Promise.resolve();
47+ const next = prev.then(task, task); // 前序失败不阻断后续
48+ queues.set(user, next.then(() => undefined, () => undefined));
49+ return next;
50+ }
2251 // 当前(per-user)工作目录:切换过的用 stored,否则回退默认(cfg.workingDir / sandbox)。
2352 const curDir = (user) => sessions.getWorkingDir(user) ?? defaultWorkingDir;
2453 
@@ -50,24 +79,125 @@ export function makeBridge({ sessions, backend, ilink, allowlist, thinkingAck =
5079 // 切目录 = 开新会话(daemon 的会话按目录存储,沿用旧 sessionId 在新目录里也找不到)。
5180 sessions.setWorkingDir(user, target);
5281 sessions.setSessionId(user, undefined);
82+ sessions.clearWhitelist(user); // 授权不跨会话:切会话清空工具白名单
atomgit-bot
atomgit-bot16 天前评论:

🟡 Medium Priority

建议:在 handleControlCommand 的无参 /cd 分支 sessions.setSessionId(user, undefined) 之后补上 sessions.clearWhitelist(user);,与有参分支保持一致。

likedislike
不准确?