feat(session): occupancy 写入 SQLite,所有权不再读心跳文件 - #1202
Conversation
bd1e669 to
cd9706f
Compare
dfd503f to
996920e
Compare
996920e to
966ef8b
Compare
|
感谢这个 PR —— 把所有权从旁路心跳文件搬进会话库的同一个 结论:0 阻断,1 条非阻断建议已同步到最新 我们对几处承重逻辑做了变异测试,确认断言都有牙:把「租约有效」改成恒假、摘掉进程存活检查、把「抢占仍存活的前任租约」这个危险方向放开、砍掉心跳回落腿、把 CLI 侧退回改动前的行为 —— 五种变异全部触发红灯。有条件接管(前任还活着就不抢、只记 warn 并在心跳里重试)和关停期「租约持有到最后一刻」这两个设计尤其稳妥。 🟠 非阻断建议:
|
966ef8b to
69fd203
Compare
Co-authored-by: Cursor <cursoragent@cursor.com> #1051 之后 daemon 只写会话行,但所有权仍在 dashboard-daemons 心跳上,探测与落盘不在同一事务。本变更把 per-bot 租约放进同库 occupancy 表,在首次 load 与离线写的 BEGIN IMMEDIATE 里判定,findOnlineDaemon 只保留 IPC 发现。
review 后续。修正租约语义: - 领取有条件:别的 boot 的租约只有过期、或其 owner_pid 已不存在时才被接管;存活的前任保留所有权,后任记 warn 并在心跳里重试。领取与续期合并为 claimOccupancyLease(held / displaced / unavailable),丢失租约的进程会在对方释放或过期后自动拿回,不再终生无租约运行 - 所有权谓词统一为「有效租约 || 心跳新鲜」:离线写事务内与 CLI 的 isOccupancyHeld 一致,回滚到不写 occupancy 的旧构建时不再被绕过;isOccupancyHeld 永不抛错,库不可读时由心跳决定 - 已应答的 daemon(任何 HTTP 状态)的拒绝恢复为终态;只有连接失败时才用租约区分「daemon 在但不可达」与「descriptor 残留」 - 优雅关停期间租约持有到 process.exit 前才释放,关停开始先续一次;exit handler 兜底。首次 load 之后立即 claim 一次,覆盖 reconcile 提前触发 load 的路径 - load 事务 COMMIT 失败不再被吞;领取失败(如只读库)只记 error、快照照常加载;attachOwnStore 的锁竞争(升级后首次 CREATE TABLE occupancy)透传重试而非钉成 loadFailure - removeDaemonDescriptor 先比对 bootInstanceId,重叠的旧 daemon 不再删掉新 daemon 的 descriptor - 删除无调用者的 setOccupancyHolder 与未用的 scope 参数;离线写事务内只读一次租约行;90s 常量收敛到 utils/daemon-heartbeat.ts 供 discovery / registry / session-store 共用 - mojo-isolation-inventory-failclosed 的枚举式 mock 补上 occupancyLeaseIsActive / readOccupancyLease:bun 在链接期校验整张图的具名导出,缺一个整文件 0 执行,这是 CI bun-test 腿红的原因 影响面:session-store(load / 离线写 / 租约)、session-offline-write、cli delete/prune/whiteboard 的 daemon 优先路径、daemon 心跳与关停顺序、descriptor 删除。不涉及 worker、适配器或 IM 路由。 验证:tsc --noEmit 干净;session-occupancy 17/17(新增接管 / 顶替 / 领取失败 / DDL 锁竞争);session-delete-cli 11/11(新增过期租约仍终态、活租约拒绝离线、过期且无心跳才离线);相关 16 个套件 722/722;bun test 该 mock 文件 2 pass。switch:here 后 daemon:restart 实测:新 daemon 持有租约并随 30s 心跳续期。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
本地 macOS 全量单测长期红的几处,CI(ubuntu)看不到:
- brand-template {cwd}:macOS 临时 home 路径含下划线,被 lark_md 转义成 \_;期望值按同一规则转义
- claim-botmux-bin-binary:/var 是 /private/var 的软链,wrapper 存的是解析后路径;期望值改用 realpathSync
- sandbox-shim-compiled-form:prepareDirectSandbox 在非 Linux 直接返回 null,整组 describe 本质 Linux-only,按仓库惯例 skipIf
- mojo 四个文件:等待 fake mojo 首行输出的 vi.waitFor 用默认 1s,单独跑全绿、全量并行下超时;14 处给 15s 预算
验证:8 个文件 vitest 95 通过 / 11 跳过;同批在 bun test 下 0 fail;全量 vitest --project unit 从 12 失败收敛到 1(npm pack 解析,#1207 修)。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ization 告警 上一提交在测试里手写了一遍 lark_md 转义正则来生成期望值,CodeQL 的 js/incomplete-sanitization 规则把它当成缺少反斜杠处理的清洗代码报了 high。 测试本就不该复刻被测模块的转义逻辑:改为断言 `~/foo` 的渲染结果与展开后 绝对路径的渲染结果一致,且不以字面量 ~ 开头。用例意图不变,macOS 上依旧绿。 验证:vitest 21/21;bun test 21 pass。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
openDbForOwnStore 与 BEGIN IMMEDIATE 拿不到写锁时,与租约被持有同语义返回 undefined,CLI 不再抛 database is locked。缺引擎 / 坏库仍表面。 Co-authored-by: Cursor <cursoragent@cursor.com>
worker 在 argv-baked 首轮会合法地依次发出 working → idle → working → idle: 中间那个 idle 是启动窗口内的假 idle,worker 自己判定后 re-arm 回 working, 最后才真正落到 idle。 原用例用 waitForScreenUpdates 等到「出现过 idle」就返回,却断言「最后一条 screen_update 是 idle」。两者只有在尾部三条 update 挤进同一个 IPC 投递批次 时才等价:本地这三条落在同一毫秒,25ms 轮询每次都看到完整序列,所以恒过; CI 上满载、该文件排在最后一批,批次被打散,轮询正好落在 re-arm 的 working 与最后那个 idle 之间,尾部断言就读到 working。 新增 waitForSettledIdle:等「最新一条是 idle 且在静默窗口内保持 idle」,不 再依赖投递批次,且比原来更严格——瞬时 idle 后紧跟的 re-arm working 落在静 默窗口内,无法再结束等待。 覆盖两个同缺陷调用点(都丢弃 helper 返回值后重新全量过滤 messages);另两 处用的是 helper 返回的 idle-only 数组,.at(-1) 恒为 idle,本就无竞态,未改。 不涉及产品代码,worker.ts 与 idle 检测一行未动。 验证:bun test 与 vitest 两条腿各跑一次,均 13/13 通过,expect 数量不变。 Claude-Session: https://claude.ai/code/session_017pqAYFy3pdKDL4fzGwyBge
69fd203 to
a4ff3ca
Compare
Summary
sessions.db新增occupancy表(scope/owner_pid/boot_id/lease_until)。daemon 在首次load()的BEGIN IMMEDIATE里写入租约,30s 续期,shutdown /exit按boot_id释放。findOnlineDaemon不再作为默认所有权,只保留 IPC 发现与 dashboard 展示。abortIf。删除该回落的条件与 Stage 0 JSON 读路径相同。本 PR 不做 Stage 2(单一 apply)也不删 JSON 读路径。影响面
session-store、session-offline-write、daemon 启停心跳、CLI close / abandon / prune、whiteboard 离线解绑。<dataDir>/heartbeats/busy-count,不把旁路文件并进会话库。Test plan
tsc --noEmit干净bun run build通过bunx vitest run test/session-occupancy.test.ts test/session-store-sqlite.test.ts test/session-store.test.ts test/whiteboard-unbind-session.test.ts test/session-delete-cli.test.ts test/daemon-discovery.test.ts(157 条通过)occupancy行;旧 daemon 未重启时 close / abandon 仍走缺行回落;租约过期后即使心跳文件仍在,离线写应能落盘Made with Cursor