feat(session): 收口跨进程会话库协议并补齐升级可见性 - #1342
Conversation
|
自动评审初步意见(首审,最终以维护者审阅为准)。整体方向与 fail-closed 语义都很扎实, 1.(建议修)无
|
|
补充两点(接上面的首审意见,仍为自动评审初步意见、最终以维护者为准): 4.(随 F1 一起)补「无
|
6bf84e2 to
a26249b
Compare
|
评审 5 条已逐条处理,提交 a26249b(已 rebase 到最新 master 9387fa1 并 force-push)。 1. 无 2. 3. 4. 补无 appId 枚举分支的回归测试 — 已补三条:非属主 daemon 回 404、目标行只在离线 bot 的 5. rebase 后复核: |
|
本机 dogfooding(
未覆盖:unmigrated / legacy_daemon 的拒绝文案只有单测证据,本机没有 <3.19 的 daemon 可对照。 |
|
dogfooding 追加发现并已修(e161e889):宿主 shell 跑 修法: |
- 基线切到 origin/master@0aba0fdd(含 Stage 3 #1308),版本时间线补齐 v3.18.0 / v3.18.12 / v3.19.0 三个分界 - 升级策略:不再等 fleet 自动重启,也不设复核日期;升级后用户手动 botmux restart,重启前新代码遇到旧 daemon 时明确失败并提示,据此把跨进程 JSON 读写、JSON 离线写、沙盒 JSON 授权、worker 的冻结 JSON 回落全部列为可删 - 心跳探针经复核不是兼容路径:displaced / claim 失败的 daemon 照常写行、supervisor 8s 强杀不释放租约,删掉它会开丢更新窗口。改为只拒绝、带原因(旧 daemon / 未持租 / 库不可读),删掉发布前的第二次判定 - descriptor 加 presence-based 能力位与仅用于文案的版本号,不做版本比较;文案按操作员 shell 与会话子进程分流,隔离判定前移 - 新增 daemon 侧「持有租约才写」的不变量、supervisor killTimeout 与关停预算对齐、宿主侧租约有效性两段规则 - 差距表补齐 owner 侧 138 处 updateSession、remote lineage 第二套事务、serializeByAnchor、bridgeMarkerCleanupFences、gate 的用户可见拒绝等未覆盖项,以及与升级窗口无关的现存缺陷 - §5 按 PR 粒度拆成收尾 PR / 前置 PR / 单独排期;§6 逐条列出可直接删除、改写为终态断言、只改名的测试用例与夹具缺陷
- 扁平 legacy store 只存在于 2026-03-11 至 03-22(首次发布到 per-bot 拆分),早于第一个发布 tag v2.16.1,没有任何发布版本写过它:无 larkAppId 的行直接放弃,扁平 store 支持点并入收尾 PR(A-8) - 只读库 / loadFailure 的 daemon 从降级运行改为有界重试后 fail-fast,写入 §3.7 - 一次性导入与中毒库恢复的删除条件定为 latest ≥ v3.19 满 90 天且线上 *.tmp* 孤儿核查为零(C-16),§5 C 重新编号 - 新增「阅读指引」:按要做的事读哪些节、已拍板的决策记录、收尾 PR 与前置 PR 的 grep 级验收标准与行为验收、施工约束
- 新增 §3.9:botmux send 与会话内定位底座改用 resolveSessionById(daemon 的 GET /api/sessions/:id 优先,已应答的 404 即权威,连接失败或读不到 secret 才读库),返回行须与 env 的 larkAppId 一致;daemon 侧补全每轮回复锚点与 codex 派发台账 - §3.2 中「旧 daemon 窗口里会话内 agent 回不了消息」的代价改为「降级但可达」,仅 macOS 凭证隔离 CLI 与 v2.x daemon 仍退回 unmigrated 提示 - §5 收尾 PR 增加 A-9;§3.8 常态可见性从单独排期提前为前置 PR B-11;C 项重新编号 - 阅读指引补两条决策记录、一条 grep 验收与一条行为验收(daemon 应答时 send 必须发出)
- §2.2 删除「没有命令边界就没有可入队单位」的根因表述:runSessionTurn 入队的是闭包,Stage 3 止于开场窗口是因为没有更多有复现的交错 - Stage 2 结束条件改为「session-store 里手写的行级事务只剩一个原语」,lineage 三函数与宿主路径共用同一个事务原语,不强求进命令联合;非 owner 只能施加 HostSessionCommand 这条边界已由 tsc 成立 - 删除 C-15「owner 侧 updateSession 命令化」,并在「不做」里写明理由;C 项重新编号 - Stage 3 残留里 admit / promote 改为复用 closeSession 的副本形态,不新增命令类型 - 决策记录与历史补 2026-09-09 这条
…前端死路径、staleHint 改版本真判定
升级窗口改为手动 botmux restart 后,跨进程读写只认 SQLite,心跳只拒绝并带原因;descriptor 广告能力位与版本号,操作员面按是否相等提示重启。扁平 store 删除另开独立 PR。 Co-authored-by: Cursor <cursoragent@cursor.com>
session-command-host 新增命名导入后,failclosed 测试的全量 vi.mock 会在 bun link 阶段直接失败。 Co-authored-by: Cursor <cursoragent@cursor.com>
- resolveSessionById:只有 BOTMUX_LARK_APP_ID 指向的属主 daemon 的 404 才是权威不存在;宿主 shell 无 appId 枚举所有在线 daemon 时,任一 daemon 的 404 只说明该 bot 的库里没有,循环后照常读库(目标 bot 已停、别的 bot 在线的场景此前会误报「未找到 session」) - 补三条无 appId 分支的回归测试:非属主 404 后仍从离线 bot 的 .db 读到、无在线 daemon 时读库、枚举到的属主 daemon 200 优先于库 - session-occupancy 测试里三处「已死 owner」改用 deadPid()(原 pid 99 / 4242 在宿主 pid namespace 上可能存活,会让「过期 + owner 已死 ⇒ 放行」的断言变红) - whiteboard-cli 测试 spawn 子 CLI 时显式删除 BOTMUX_SESSION_ID / BOTMUX_LARK_APP_ID 等会话环境变量(在 botmux 会话内跑测试时不再误触 larkAppId 一致性校验) - 删除已无引用的 SUPERVISOR_SHUTDOWN_PROTOCOL 常量与 SupervisorShutdownProtocol 类型,DAEMON_GRACEFUL_EXIT_CODE 保留
dogfooding 发现:本机 data/ 下有 4 份早已移出 bots.json 的 bot 留下的 sessions-<appId>.json,没有任何 daemon 会再导入它们,但 botmux list 每次都提示「会话库尚未迁移到 SQLite,请重启 daemon」,重启也不会消失。 - 新增 services/known-bot-app-ids.ts:bots.json 里配置的 + 当前有 descriptor 在线的 + 本进程所属的 appId,三者并集;每个来源都是 best-effort(沙盒读不到 bots.json 视为无) - loadAllSessionsSnapshot 增加 knownAppIds 选项,只把已知 bot 的残留 JSON 报为 unmigrated(fallbackAppId 始终视为已知) - cli.ts loadSessions、resolveSessionById 的库兜底、dashboard 删板解绑三处传入已知 bot 集合 - 测试:known-bot-app-ids 两条;snapshot 与 resolveSessionById 各补一条「已移除 bot 的残留不算 unmigrated、仍配置的算」 - 设计文档 §3.3 补这条判定规则
master 的 Codex 实例配置守卫仍按旧 resolveStoreFile.kind 读 JSON;rebase 后该字段已不存在,改为只读 .db,unmigrated 时 fail closed。 Co-authored-by: Cursor <cursoragent@cursor.com>
e161e88 to
ee25027
Compare
|
自动评审的初步意见(针对新 commit 的增量意见,以维护者审阅为准)。 先说结论: 一、新 commit 解决的问题(已核实)主干 #1333(Codex 会话级实例绑定)新增了 实证:在作者上一个 head 改法本身也核过,三点都对:
二、CI 红的根因(建议本 PR 内一并修)11 个失败全在 这同样是本 PR × #1333 的碰撞,两边单独都无害:
而这两个测试文件用 以前 四格交叉验证(都在本机实跑):
也就是说:这个红是主干上已经存在的隐性缺陷(矛盾的测试 mock),只是必须等本 PR 的 fail-closed 落地才会显形;不是 另外确认了不是生产缺陷:那条"中毒库"路径要求 三、建议改法(已在本地验证通过)这两个套件是 cost-calculator 的单元测试,其它协作者(codex-transcript / traex-transcript / pi-transcript / aiden-checkpoints / registry…)都已经 mock 掉了,唯独漏了会话库。补一个最小 stub 即可,两个文件各加一段: // resolveSessionTranscriptPath() 的 codex 分支会查会话库里的实例绑定。
// 本套件整体 mock 了 node:fs,真实 store 会读到合成文件系统;stub 掉它需要的这一个入口。
vi.mock('../src/services/session-store.js', () => ({
getSession: vi.fn(() => undefined),
}));验证结果:
(若更倾向于修 mock 本身,另一条路是给 四、其余部分除上述一处外, 补上第三节那两段 stub 后 CI 应当转绿,届时我再复验一次。 |
Summary
sessions-<appId>.json无.db时判定为unmigrated(只看文件是否存在,不解析内容),不再走 db-else-json,也不再为窗口保留读旧格式。lease/legacy_daemon/daemon_without_lease/store_unreadable)。descriptor 广告sessionStoreProtocol与仅用于文案的botmuxVersion;停写supervisorShutdownProtocol,删除 dashboardbootstrapRequired死分支及其 i18n、以及零引用的RestartLifecycleFlags。关停协议常量与状态机不动。GET /api/sessions/:id),已应答 404 即权威不存在;问不到再读库。隔离判定移到 occupancy 之前;白板解绑在 daemon 已应答任意状态码时不再回落宿主写。botmux statusVERSION 列、dashboard 版本卡、安装后提示,以及历史弹层staleHint的真判定(该 bot 的descriptor.botmuxVersion存在、非0.0.0、且与磁盘版本不相等才提示重启;只比是否相等)。扁平 store 删除见紧随其后的独立 PR(A-8),本 PR 的
sessionStore.init()无参调用允许非 0。影响面
共用路径:
session-store/session-command-host/cli.ts离线 close·delete·unbind、resolveSessionById(send / detectCurrent / whiteboard / dispatch / report)、dashboard 版本卡与历史弹层、descriptor 读写白名单、fleet supervisorkillTimeoutMs。会话类型:话题 / 群 / adopt / restore 共用同一套 occupancy 拒绝与 daemon-first 定位;Pty 与 Tmux 的 close/abandon 仍走同一 host apply。沙盒 / origin-channel 子进程只陈述状态,不含
botmux restart。owner:falseworker 在无.db且有 leftover JSON 时 fail closed,不建库、不解析 JSON。Test plan
验收 grep(相对本 PR):
kind: 'json'|loadFromFrozenJson|abortIf|legacyHeartbeatHeld在session-store.ts/session-command-host.ts:0sessions-${ctx.currentAppId}.json在fs-policy.ts:0sessions(-[^.]+)?在mojo-containment-command.ts:0withFileLockSync在session-store.ts:仅导入 + 中毒恢复两处sessions.get(sid)在cli.ts:0sessionStore.init()无参:本 PR 允许非 0(A-8 达标)sessionStoreProtocol|botmuxVersion:daemon.ts/daemon-discovery.ts/dashboard/registry.ts均命中PM2_DAEMON_KILL_TIMEOUT_MS在shutdown-budgets.ts:0(已改为FLEET_DAEMON_KILL_TIMEOUT_MS)未在本机做
switch:here/ daemon 重启(worktree 施工约束)。未决
init(appId)必填)另开 PR,与本 PR 同时开、后合。formatDaemonVersionRestartHint的中文与 dashboard i18n 中文模板对齐;英文走 i18n。判定函数daemonVersionDiffersFromDisk两边共用。评审跟进(2026-09-09)
bun run build通过;20 个单测文件 499 passed / 2 skipped;4 个 spawn CLI 的文件 34 passed。bun run switch:here && bun run daemon:restart后的观察见后续评论。