Repository navigation
feat(runtime): share native ACP processes and scope reverse interactions - #177
qiyuanhuakai wants to merge 6 commits into
Conversation
|
AI 辅助源码审查(dot) 审查 head: 以下为 24 个文件的 ACP 增量中发现的问题,不重复其他堆叠 PR 的发现。 [P2] 不同会话并发完成会竞争 process revision,遗留会话租约agent 声明并发支持、不同 session 的两个 prompt 同时成功完成时,两者可读到相同 process authority revision。第一个 terminal CAS 重写共享 process 行,第二个收到 entity_revision。此处没有重读重试,而是进入 reconciling 并留下 session lease,之后该会话的新操作被 session_busy 拒绝。 最小复现得到一个 native-executed、一个 entity_revision,失败者 lease 仍存在。已核对实际 SQLite worker 同值写入也递增 revision,因此并非替身特有行为。建议确认 owner/generation/lease 仍属原操作后,只重试数据库收尾 CAS,不重发 native 操作,并覆盖跨会话并发完成测试。 [P2] 权限答复的幂等摘要遗漏 interaction identityacpMutations.js L103–123、调用处 L88–104 同一 prompt 的两个不同权限请求,如果客户端复用答复 idempotency key 且 answer 相同,摘要因未纳入 input.interaction 而相同。第二次返回第一个请求的 already-completed,随后删除第二个 pending 项并发布 interaction.replied,却没有回复第二个 native RPC。 复现只回复 ID 7;ID 8 的 durable phase 仍为 pending,但可见 pending 列表为空,prompt 会继续等待。建议将 interaction identity 纳入摘要,这种跨请求复用应报 idempotency_mismatch,并补充专项测试。 [P2] 同一 prompt 内顺序复用 native request ID 被误判为重复权限请求答复后 RPC 层已释放 native ID,但 durable key 只由 scope、parent、native ID 组成。同一 prompt 后续复用该 ID,会命中旧记录并报 duplicate_native_request;内容变化还可能更早遇到 payload revision 冲突。 最小复现为答复 ID 7 后在同一 parent 接收新 ID 7,第二次失败。现有 QA 只覆盖不同 parent 的 ID 复用。建议每次接收生成独立 interaction identity,以 native ID 映射当前 outstanding 请求,同时保留对真正同时占用相同 ID 的拒绝。 [P2] 重试较早的配置操作会回滚当前缓存依次用 key A 将 model 设为 b、key B 设为 a,再重试 A。mutations.run 正确返回 A 的历史结果且不重新发送 native 操作,但此处无条件把历史 configOptions 注册为当前状态。 复现 native 实际值为 a,getSessionConfigOptions 却返回 b,只有两次 native 设置调用。建议 already-completed 路径仅返回历史结果,不覆盖最新状态,或用版本信息阻止旧快照覆盖,并测试延迟重复请求。 验证边界已读完整增量、调用链、存储 CAS 实现及新增测试/QA。四个最小复现使用该 head 原始模块与内存 CAS 替身执行;未独立运行完整项目测试、真实数据库 worker 或 installed OpenCode 场景,作者报告不计为独立复验。 该 head CI 已核实 success。上述专项边界仍需回归覆盖;本审查不保证无其他缺陷。 |
Four defects on the shared native ACP process, each reproduced by execution against the real runtime store before the fix. Cross-session concurrent completion competed on the shared process revision: the terminal CAS carried an authority guard read before the native enqueue and never re-read it, so the loser was rejected with entity_revision, left reconciling, and kept its session lease, after which every later operation on that session was refused with session_busy. The finishing CAS now re-reads its own authorities per attempt and retries only the database write; the native operation is never re-sent. Permission replies collided across two different requests on one prompt: the operation digest omitted input.interaction, so a reused idempotency key with the same answer made the second reply report the first request's result and drop its own pending interaction while the native RPC went unanswered. The digest now carries the interaction identity, so the cross-request reuse fails with idempotency_mismatch instead of silently succeeding. Sequential reuse of a native request ID inside one prompt was misjudged as a duplicate, because the durable interaction key was derived from an ID the RPC layer had already released. Identity is now minted per receipt and the native ID only guards the currently outstanding request through a generation-scoped occupancy row, so genuinely concurrent same-ID occupancy is still refused. Retrying an older config operation rolled back the current cache, since an already-completed replay registered its historical configOptions as live state without any native call. The register is now skipped on the already-completed path. Each fix has a named test that goes red then green, including one where the two permission callers are otherwise identical so only the digest can tell them apart, and one proving the native-ID guard still rejects concurrent occupancy.
|
AI 辅助增量复核(dot) 审查 head: 本次只复核新增修复提交的 4 个文件及其调用链;#175 → #176 的前置内容仍已包含在 base,不重复计为本次改动。对照上次评论,两项已修复,两项仍存在更窄的边界缺口。 [P2] 并发收尾固定重试四次,仍可留下已成功执行的会话租约重读 authority 解决了原先两个会话竞争的复现,但每次 terminal CAS 仍会重写共享 process 行,且最多尝试四次。agent 声明并发支持、多个已获得各自租约的 prompt 集中完成时,其他会话可以连续四次推进该 revision,使最后一个已完成的 native 操作仍落入 独立隔离验证先逐一让各会话进入 native 执行,再统一完成:4 个会话全部收尾;5 个时得到 4 个 建议消除同一 process 的无意义 revision 竞争,或串行化/可靠调度数据库收尾,在重新验证 generation、owner 和原租约后完成它;仅增加固定重试次数仍会留下同类窗口。补充至少五个已在途会话同时完成的回归。 [P2] native 答复已经发出后立即复用 ID,仍被误判为占用acpInteractions.js L133–145、拒绝分支 L40–44
使用该 head 的实际 process manager、RPC 模块及受控 stdio 子进程,暂缓首个回复的 terminal CAS,已观察到线序:ID 7 成功答复 → peer 再发 ID 7 → ID 7 错误答复;第二项没有进入可见 pending。无额外 CAS 暂停的 RPC 隔离用例也复现。新增顺序复用测试 L275–283先等待整个 建议把 occupancy 的生命周期与 native 答复发送完成对齐,或让新 receipt 等待上一条已发送回复的收尾;同时保留对真正同时 outstanding 的重复 ID 的拒绝。 已核实的修复
验证与生产边界执行前已核对所用源码与该 head 的 Git blob。独立验证使用原模块、保持事务/CAS/intent/异步交错语义的内存 SQLite 存储替身;stdio 复现使用实际 process manager/RPC 与受控 peer。真实 精确 head 的 CI已核实终态 |
…lign occupancy with reply completion The terminal CAS carried the shared acp-process row as a guard, rewriting a byte-identical value whose only effect was a revision bump. With more in-flight completions than the retry budget, a completed native operation stayed reconciling with its session lease held (5 sessions: 4 executed + 1 entity_revision; 8 sessions: 4 leases left, all later operations session_busy). That row is no longer part of the CAS: the generation is re-validated per attempt and the synchronous assertCurrent() sits immediately before the commit, and a completion only writes its own operation, lease and interaction rows. The occupancy row outlived the native reply, so a peer re-using a freed ID inside the same prompt was refused with duplicate_native_request. The reply now ends the occupancy's effective lifetime: respond() and settleParent() mark the key in the same block as rpc.reply(), and receive() only rejects a same-ID receipt whose request is still outstanding. Two regressions cover both timings, including the moment the peer can actually send. The retry budget is unchanged.
|
AI 辅助增量复核(dot) 审查 head: 本次仅复核新增提交的 3 个文件及相关调用链;base 未变,#175/#176 的继承内容不重复计入。对照上次补充评论:多会话收尾复现已转绿;权限 ID 即时复用的常规时序也已改善,但仍有下面这个跨 receipt 清理窗口。 [P2] 旧回复收尾会清除新一轮回复的 answered 标记,第三次复用 ID 仍失败acpInteractions.js L99–108、接收判定 L46–53
已通过该 head 的实际 process manager、RPC 模块与受控 stdio 子进程复现:两次 ID 7 成功答复,随后第三次 ID 7 收到上述错误;只产生两个 pending 事件。测试通过暂缓 A/B 的 terminal CAS 控制收尾顺序,peer 的三次请求在原生线路上均依次等待前一次答复,不是同时 outstanding 的重复请求。 建议让 answered 标记绑定具体 receipt/interactionId,例如保存 occupancy → 已答复 interactionId;接收时核对当前占用记录的 owner,旧 receipt 的清理只移除自己拥有的标记。对“不属于本 receipt”的 occupancy 不应删除后继 receipt 状态。补充上述 A/B 收尾重叠的三轮同 ID 回归;新增测试 L395–414先等 A 的 respond 完整结束才答复 B,因此没有覆盖这个交错。这属于上次 ID 复用问题剩余的更窄范围。 本次核实的修复
验证边界与 CI已校验所用 33 个源码文件与该 head 的 Git blob 一致。独立执行使用原模块和保留事务/CAS/intent/异步语义的内存 SQLite 存储替身;stdio 验证使用真实 process manager/RPC 与受控 peer。实际数据库 worker 在本环境仍受 tmpfs/overlayfs 文件系统白名单限制;未独立运行完整 Vitest、installed OpenCode 或 UI/supervisor 生产接线,作者和 CI 的测试不计为独立重跑。 当前 head 的 CI提交此评论时 Validate 已成功,完整 workflow 仍在运行。结论限于本次增量及已执行场景,不保证无其他缺陷。 |
…ion cannot clear it
|
针对 10-16 07:57 增量复核(head 跨 receipt 清理窗口(P2)—— 真实缺陷,已修复。 该轮已核实的修复(terminal CAS 不再重写共享 process 行、A 收尾未结束时 B 可进入 pending、幂等摘要纳入 interaction identity、配置重试不覆盖最新状态)在本机全量运行中保持通过: |
Array.isArray does not narrow away a readonly Json[] union member, so the false branch still admitted the array and value.phase failed to typecheck. Assert the shape through an explicit predicate, as the other Json consumers in this tree do.
|
第四轮复核(审查 head 修复就在当前 head。 独立 RED/GREEN(本机实测,未引用作者证据)。 在 head 工作区把修复前源码( 门禁(当前 head)。 未验证。 未启动真实 ACP CLI 与真实 SQLite worker 环境外的完整全量套件;本机存储替身/受控 stdio 之外的场景未覆盖。 结论:该 P2 在当前 head 关闭。 |
|
第五轮完整复核(审查 head 复核方式。 本轮自写独立探针( 第一轮 4 项
第二轮 2 项残留
第三轮 1 项残留
未过度收紧:真正并发占用同一 native ID 仍被拒绝 门禁(当前 head): 未验证:未启动真实 ACP CLI / installed OpenCode / live SSH/Slurm;未跑全仓 vitest(仅 app/runtime);未做 vite build 与浏览器 QA。已知非阻塞 flake(runtimeGit 200-workspace、kimiWebWsProxy 502)在 app/runtime 之外,本轮未触及。 结论:#177 全部七项 finding 在当前 head |
receive() cleared the answered mark for an occupancy key before the new receipt's claim was durable. When the previous receipt's release() landed inside that window it saw own === false and swept the very occupancy row the claim's CAS was preconditioned on, so a legitimate native-ID reuse was rejected with an internal entity_revision instead of being admitted. The occupancy emptied by that sweep is exactly the state in which re-use is allowed, so the claim is now retried (bounded, re-reading the occupancy and the process guard); a row still held by another receipt keeps rejecting as duplicate_native_request, and a genuinely concurrent same-ID claim is unchanged. Regression: a claim parked under the previous release sweep still admits the reused native ID. RED before the fix: [true, 'entity_revision', 1]; GREEN after: [true, undefined, 2].
|
AI 辅助增量静态复核(dot) 审查 head: 本次仅有一个新提交、两个变更文件;base 未变,继承的 #175/#176 内容及此前 ACP 修复不重复计入本次增量。 已核实的实质变更acpInteractions.js L49–98 为新 receipt 的 claim 增加了最多四次尝试:遇到 revision conflict 且重新读取的 occupancy 已空时,刷新 process authority 后重新读取并提交;仍被占用时返回 duplicate_native_request,耗尽尝试时返回 concurrent_update。 这针对旧 receipt 的 release 清空 occupancy、恰好使新 claim 的预期 revision 失效的窗口。旧实现对已空的 occupancy 直接重新抛出 entity_revision;当前源码已明确改为上述有界重试。对照了 release、authority 和 compareRecord 的调用契约,确认重试会重新取得 guard,而非复用失败的 revision。 新增回归 L632–739 将第二次 claim 暂停到第一次回复收尾完成之后,再放行并检查第二次 receive 成功且产生两个 pending 事件。测试源码覆盖意图与本次改动一致,建议保留该定点时序回归。 CI 与覆盖边界该 head 的 CI 39269775600 已 completed / success:Validate 的 lint/type-check、tests、web build 均成功,全部 Electron/Bridge 安装器任务及 Complete CI 成功;Publish draft release 为 skipped。 本次结论来自只读 diff、相关源码/测试对照及 GitHub CI 状态;没有独立执行权限/RPC 竞争复现、真实 SQLite/ACP CLI 或完整测试套件。此前受限的安全复现未重试,也没有借用作者评论中的 RED/GREEN 或“七项全部关闭”作为我的独立验证。本次未形成可独立核实的新缺陷,不将静态确认与 CI 成功扩大为旧问题全部关闭或完整安全通过的结论。 |
ACP observers need to share a native agent process without competing for its client connection or losing reverse-tool ownership when a UI disconnects. Add a runtime-owned ACP driver that keeps one client per configured agent instance, scopes observers and bounded replay to composite session identities, and keeps reverse file/terminal tools bound to the native process and trusted workspace.
Permissions and cancellation are durable controls tied to the original parent, lease, and process generation. Concurrent replies admit one winner; uncertain sends reconcile without automatic replay; replacing the native process invalidates old replies. Sequential native request-ID reuse stays parent-bound, while simultaneous outstanding-ID collisions close the source. Native capability mapping covers discovery, prompt/config/slash, fork/close, advertised concurrency, and managed authentication PTYs.
This atomic commit contains the 24 independently verified ACP files. It targets the fixed
feat/v090-acp-baseat98a4474b540c1258c3e485ec98e013eff3d2047b. The shared WS fixture and inventory-map prerequisites are already in that base through #175 and #176.The new independent whole-task gate is confirmed: 104 recorded surfaces and all nine applicable adversarial classes. Delivery rehashed 575 unique source/evidence files before and after commit, including 69 current sources, 66 original source bindings, the exact 24-file owned digest, and preserved historical/recursive proof.
Verification retained with exact source bindings:
node scripts/qa/runtime-v090.mjs --task 15 --case happy --out <artifact>and the matching--case failureinvocation: 34 happy and 50 failure assertions, installed OpenCode/controlled stdio traces, managed PTYs, and durable SQL observations.Limits: installed OpenCode 1.18.34 exposes 101 ACP sessions versus 102 native SQLite sessions, so its inventory remains explicitly partial; a conforming controlled ACP peer separately reaches complete exhaustion. Installed-native runs used private homes and an owned local model endpoint, with no paid inference or real-account login claim. UI/supervisor integration, actual oh-my-pi execution, installer/platform coverage, and true database/native atomicity are outside this task. The original uninstrumented full-suite WS failure's cause remains unproven.
Evidence:
.omo/evidence/vis-v090/task-15/inventory-refinal-001/adversarial-verify.json(SHA2561607a237507f16925f31180d88991ae147ba5f2486a0512e6ea96c058799821c) and.omo/evidence/vis-v090/task-15/delivery/delivery.json. Owned source digest:9a646606552b8afdcfc11b418becdb14bd600f4b5500171c5159ea006ce88da1. CI for the published commit is recorded separately.