Skip to content

[PR] feat: host-side conversation delete co-deletes compression artifacts (#2935) - #2937

Open
ranxianglei wants to merge 12 commits into
masterfrom
2026-10-12_host-session-delete
Open

ranxianglei wants to merge 12 commits into
masterfrom
2026-10-12_host-session-delete

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Part of #2935

What

Lands the missing compression-artifact teardown mechanism for #2935 ("when the user deletes the original session, also delete the corresponding compressed session"), plus the dsh host-side trigger that fires it when the host deletes a conversation.

Previously no path existed at all to delete a session's compression artifacts (the opt-in BILI_SESSION_GC only removes uncompressed files >7d old; compressed-session data had zero cleanup). A single conversation's artifacts are scattered across six co-lifecycle locations and nothing removed them together.

Changes

  • src/session-delete.ts (new) — deleteSessionArtifacts(id): one routine tearing down every artifact bound to a session id: session file, lossless CCR content-store companion, image-spill dir, unified-store rows (+CAS refcount release), in-memory resident session, prefix-affinity chain. Compression-state-agnostic. Defers (not deletes) while a request is in-flight or a save is pending, so it never yanks a file out from under a live writer.
  • POST /__bili/plugin/delete {conversationId} (src/plugin.ts + src/server/admin.ts) — generic, loopback+trusted-origin gated like every /__bili/plugin/*. Resolves the conversation's sessions: verbatim id + persona forks (<id>|sub:<fp>) cascade; independent child conversations (own ids) are left alone. Always returns 200 {ok,conversationId,deleted[],deferred[]} even when empty.
  • src/agent/dsh-native.ts — the host-side trigger (event-driven): on a dsh session-deletion event it POSTs the exact conversation id to the endpoint. Subscribes both shapes — session/event whose type is a destructive spelling, and the top-level session/deleted event. Matches an evidence-permitlist of destructive spellings (session/deleted, session/removed, session/cleared, deleted, removed, cleared) because the shim types the event payload unknown. Fire-and-forget, one-shot (no dedup/re-arm — the endpoint is idempotent and deletion is not retriable). Non-deletion events never touch the delete lane.
  • src/persist.ts — extracted public removeSessionFiles(id, meta?) from the private legacy remover (same candidate set); reused by the new routine.
  • Docs PLUGIN.md §9; Tests tests/issue2935-session-delete.test.ts (fork-prefix guard, full artifact teardown incl. disk/spill/memory/affinity, busy-defer, e2e rig: validation, real host-session cleanup via binding, fork cascade, sibling isolation) plus two new cases in tests/dsh-native.test.ts (deletion events → teardown POST on both event shapes; non-deletion events leave the delete lane alone; permitlist predicate pin) — all green.

Why event-driven (not reconcile-poll)

The issue's stated priority is preventing accidental deletion ("防止误删除"). Event-driven carries zero false-positive risk: it acts only on an explicit per-session signal from the host, so a wrong type guess degrades to a no-op rather than a spurious data delete. Reconcile-poll (diff remembered sids vs ctx.sessions.list()) would be unsafe unless list() is guaranteed to return all sessions, not just currently-open ones — otherwise it would delete idle-but-not-deleted conversations' data.

One residual (non-blocking)

The exact type string dsh emits on deletion is not discoverable in-repo (typed unknown in the shim; only session/title referenced anywhere; dsh source lives in a separate repo). The permitlist therefore covers the convention-consistent destructive spellings. If you confirm dsh's actual single spelling I will tighten the list to exactly that value (one-line change on this same branch).

No web-UI delete is added (per the issue). No config surface added. version untouched.


Fixes #2935

(孤儿认领:标题本已指向 #2935,补上关闭关键字 —— 第三批清理)

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-12_host-session-delete (7712b59)

Latest build — install this exact version

npm install -g billion-context@0.0.6205-pr.2937.7df24f7

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
7712b59 billion-context@0.0.6205-pr.2937.7df24f7 build
2be5ac0 billion-context@0.0.6117-pr.2937.30f1fc7 build
4112362 billion-context@0.0.6025-pr.2937.e06d768 build
769588c billion-context@0.0.5862-pr.2937.174d7c1 build
f33407f billion-context@0.0.5795-pr.2937.b21f4ec build
fca3487 billion-context@0.0.5659-pr.2937.e4cc3c9 build
19bf777 billion-context@0.0.5596-pr.2937.60efecd build
09d5760 billion-context@0.0.5554-pr.2937.4e53e97 build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr2937.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei
ranxianglei force-pushed the 2026-10-12_host-session-delete branch 2 times, most recently from 3d6f964 to af6e2d9 Compare October 12, 2026 14:33
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 冲突已解决,分支已核对。PR #2937(head 现为 f6eb3bc50)merge state 恢复 mergeable,CI 全绿中(主 test 矩阵在跑)。

本轮做了什么

  1. Rebase:分支 rebase 到最新 master(da15214e6,含 fix: fork 子会话 nudge 全盲死区 — 收据播种 sizing 基线(0.1.195 卡死复现) #2936/fix: 释放会话派生缓存与诊断请求底层字符串 #2923/[Bug][DSH] 长会话切换自定义模型后输出预算恢复到 249 token,连续 thinking-only / max-tokens(#1840 零上限来源场景) #2904 等 ~30 commits)。唯一文本冲突:reference/architecture.md orientation 列表——master 新增「会话派生缓存(会话外缓存绕过 resident budget,诊断 substring 保留完整请求 #2920)」条目,本 PR 在 Persistence 条目加了 session-delete 句;按并集解(两条都保留),无语义冲突。src/plugin.ts 自动合并干净(master 的 remembered → session.transient.pluginMessages 重构与本 PR 的 handlePluginDelete 在不同区域)。
  2. 顺手修了两个小问题(各自独立标注 commit):
    • src/persist.ts:678 — removeLegacyFile 签名多了一个空格(提取 removeSessionFiles 时带出的缩进漂移)→ 恢复 4 空格;
    • CI unused-exports gate 红 — src/session-delete.ts 的 export interface DeleteSessionOutcome 无任何外部模块/测试引用(PR 自身引入,与 rebase 无关)→ 按 gate 建议去掉 export(INTERNAL_ONLY 处置),本地重跑 gate 确认 clean。
  3. 推送:rebase force-push + 两个 fix commit。GitHub 侧冲突状态自动转干净。

代码核对(对照 PR 描述逐项):一致。deleteSessionArtifacts 在 in-flight/pending-save 时 defer 而非硬删(不抢 live writer 的文件);personaForkIds 只级联 <id>|sub:*,独立子会话不动;endpoint 挂在 /__bili/ loopback+trusted-origin gate 之后、无 web-UI 面(符合 #2935「防止误删除」);removeSessionFiles(id, meta?) 从私有 remover 提取、候选集不变(unified rows + CAS refcount release 保留);PLUGIN.md §9 文档到位;无 config surface、version 未动。测试是行为断言(fork-prefix guard / 全量 teardown disk+spill+memory+affinity / busy-defer / e2e rig:validation+binding cleanup+fork cascade+sibling isolation,共 37 个 assert),不是假测试。

本地证据(定向验证,全量回归交给 CI):typecheck ✓ · build ✓ · issue2935 套件 4/4 ✓ · master 侧相关套件 fork-nudge-deadzone 3/3 ✓、session-transient-memory 5/5 ✓(plugin.ts 合并文件的保险)· unused-exports gate ✓。

八问

  1. 是什么问题:PR 分支与推进中的 master 冲突(merge dirty);另有 PR 自身两处小缺陷(缩进漂移、unused-export gate 红)。
  2. 什么原因导致的:architecture.md 两侧在同一行附近各加不同条目;unused-export 是 PR 导出接口但仅文件内自用,与 rebase 无关。
  3. 影响范围与严重程度:纯分支级,不影响任何已发布行为;不处理则 PR 无法合并(gate 红 + 冲突)。
  4. 怎么修复的:rebase master、冲突取并集;小问题各自独立 commit 修掉。
  5. 回归问题:审过 diff 触碰路径的既有行为——removeLegacyFile 委托同一候选集逻辑(GC 路径不变)、新路由纯增量不动既有路由、plugin.ts 自动合并区域逐字保留 master 语义且对应套件绿。无回归。
  6. 缓存破坏:不涉及。删除路径只对显式指定的 conversation id(+其 persona forks)生效,不触碰 prefix cache/重试/游标机制。
  7. 设计方向:未改变。新增 host-side 显式删除机制(loopback-gated、无 web UI),既有 GC([Feature] Default state file cleanup strategy #1082 / BILI_SESSION_GC)默认行为不变。
  8. 新配置:未引入。

CI:新 head 上 guards/kernel/lock/docs/config-surface/lane 全绿,e2e 各 lane(codex/dsh/opencode/pi/registry…)已过,test/static-gates 矩阵(ubuntu+windows × node22/24)运行中——之前 head 上它们因 unused-exports 失败被级联 skip,本轮会正常跑完。全绿后我再跟一条。

待你拍板(PR 正文遗留):host 端触发器(dsh 删会话时调 endpoint)刻意未接。①事件驱动:订阅 dsh ctx.on("session/event") 删除事件,零误报,但需要确认 dsh 删除时发出的确切 type 字符串(shim 里是 typed unknown,目前只处理 session/title);②reconcile-poll:定时 diff remembered sid 集合 vs ctx.sessions.list(),自包含但需确认 list() 返回全部会话而非仅打开的,否则会误删 idle 会话数据。我倾向 ①(显式信号,契合防误删);给我事件 type 后我在本分支落 follow-up commit。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 CI 全绿,收工确认。

结论:冲突已解、代码与描述逐项核对一致、无回归、无新配置面——可合并。合并按仓库规则由人来:#2937

唯一悬而未决项仍是上一条评论里的拍板题:host 端触发器选 ①(dsh session/event 删除事件,需给我确切 type 字符串)还是 ②(reconcile-poll,需确认 ctx.sessions.list() 返回全量会话)。你定了我在本分支落 follow-up commit(或合并后开小 follow-up PR)。

@ranxianglei ranxianglei changed the title feat: host-side conversation delete co-deletes compression artifacts (#2935) [PR] feat: host-side conversation delete co-deletes compression artifacts (#2935) Oct 24, 2026
ework-agent and others added 4 commits October 25, 2026 00:42
…eletes a session

Wires the host-side trigger for #2935: when the dsh host deletes a conversation, bili tears down that session's compression artifacts via POST /__bili/plugin/delete.

Event-driven (zero false-positive): subscribes session/event deletion types plus the top-level session/deleted event; matches an evidence-permitlist of destructive spellings since the shim types the event payload unknown. Non-deletion events never touch the delete lane.
…storage drift)

Rebased onto current master for the #2937 conflict wake. Master's storage layer now persists to a unified SQLite store (index.db) in addition to the legacy JSON envelope, and loadSync serves from the unified store when readsUnified is set. The original co-delete called store.removeSessionFiles(id, meta), which removed only the legacy/CCR files and left the unified rows behind, so the persisted record survived teardown and the #2935 regression test failed against current master.

Switch deleteSessionArtifacts to store.removeSession(id) (#3928 full cascade: clears unified-store rows plus media/CAS refcounts AND the legacy files). This also removes the need to make removeSessionFiles public, so src/persist.ts is reverted to master-verbatim (no net change).
@ranxianglei
ranxianglei force-pushed the 2026-10-12_host-session-delete branch from 50fe619 to 09d5760 Compare October 24, 2026 16:47
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

冲突已解决并推送。 PR #2935 分支 2026-10-12_host-session-delete 已 rebase 到当前 master tip(146b34f27)并 force-push(lease 保护 + SHA 校验通过),远端分支现指向 09d576019。本地 typecheck 绿、定向测试(issue2935 + dsh-native)74/74 绿,净 diff 恰好是 PR 的 8 个文件。

按结构化问题作答:

  1. 是什么问题:PR When a user deletes the original session, the corresponding compressed session is deleted as well. | 用户删除原会话的时候一起删除对应的压缩会话 #2935(宿主侧删除会话时联动清除压缩产物)的分支落后 master 两千多个提交,与 base 产生合并冲突,需要 rebase 解冲突。

  2. 什么原因导致的:两层——①表面:PR 分支基于很旧的 master,期间 master 在 persist.ts / plugin.ts / admin.ts / dsh-native.ts / dsh-native.test.ts 等热区落了大量并发改动,产生文本冲突;②更深:master 存储层新增了统一 SQLite 存储(index.db),与旧 legacy JSON envelope 并存,且 loadSync 现在优先从统一存储读。原 PR 的清理只调 store.removeSessionFiles(),只删 legacy/CCR 文件、没删统一存储行 → 持久化记录删不掉 → When a user deletes the original session, the corresponding compressed session is deleted as well. | 用户删除原会话的时候一起删除对应的压缩会话 #2935 自己的回归测试在当前 master 上失败。(这是 rebase 时暴露的语义断裂,不是纯文本冲突。)

  3. 影响范围与严重程度:仅限本 PR 自身(尚未合入、未发版,无生产行为可回归)。若不修,rebase 后的分支是红的(When a user deletes the original session, the corresponding compressed session is deleted as well. | 用户删除原会话的时候一起删除对应的压缩会话 #2935 回归测试挂)、不能合;修后为绿。

  4. 怎么修复的:把 deleteSessionArtifacts 从调部分清理的 removeSessionFiles(id, meta) 改为调 removeSession(id)(CLI surface for storage maintenance: bili sessions ls/rm/archive/restore + bili storage gc (re-scoped from #3860) #3928 的全量级联:清统一存储行 + 释放 media/CAS 引用计数 + 删 legacy 文件),让宿主侧删除真正彻底。顺带把「把 removeSessionFiles 改 public」那处改动回退,persist.ts 恢复成与 master 完全一致(零净变更),diff 更小、风险更低。其余冲突均为 import 行 / 事件处理 / mock 签名的意图并集,保留双方内容。

  5. 是否有回归问题:审过的既有行为——persist.ts 对 master 零净变更(不可能引入回归);plugin.ts / dsh-native.ts / dsh-native.test.ts 与 master 的并发改动是干净合并(typecheck + 测试验证);无 fail-fast 变吞错、无阈值/时序/输出格式漂移。唯一语义变化见下条披露。

  6. 是否破坏缓存:否。删除路径不碰前缀缓存 / 重试 / 增量游标;prefixAffinity.forget 语义不变。

  7. 是否改了设计方向:否。仍是 loopback 网关的 POST /__bili/plugin/delete、无 web-UI 入口、幂等 no-op 语义不变。

  8. 是否引入新配置:否。无新 env / config / 默认值。

⚠️ 行为变更披露(相对原 PR 草稿):

  • 旧(原 PR 草稿):宿主侧删除只清 legacy JSON envelope + CCR companion 文件。
  • 新:额外级联清统一存储行 + 释放 media/CAS 引用计数。
  • 原因:当前 master 上 loadSync 从统一存储读,旧路径会留下可恢复的记录、使删除失效。此改动让宿主侧删除在当前 master 上真正生效;在未启用统一存储的构建里 removeSession 自动回退到 removeSessionFiles,等价。不涉及 wire 形状 / 阈值 / 配置面。

注:master 在处理期间多次前进(我逐次 rebase 到最新 tip,最终落在 146b34f27,HEAD 已包含该 tip)。若 CI 运行前 master 又动,平台可再触发一次;推送时刻分支已含当时 tip。全量回归以远端 CI 在推送 head 上的结果为准。

判定:冲突已清、本地验证绿,可进入 review / CI。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

CI unused-exports 已直接修好并推到 PR 分支(新 head 19bf77772)。

  1. 是什么问题:CI 的 unused-exports 门禁报 src/agent/client-build-note.ts 里的 type ClientBuildCheck 属于「导出了但外部没人用」——它只在本文件内部被引用(类型本体 + 4 处函数签名),没有任何其它模块 import 它。
  2. 什么原因导致的:该类型本来就是本文件的内部实现细节,却带了行首 export,被门禁判成 INTERNAL_ONLY。这是本 PR 分支上一处同类修复(c55e67482 删掉未用的 DeleteSessionOutcome)之后暴露出来的下一处。
  3. 影响范围与严重程度:仅 CI 门禁层面红,不改任何运行时行为、wire、压缩逻辑或用户可见功能——零运行时影响,属体验/卫生级。
  4. 怎么修复的:按门禁自身给出的处置指令,去掉该类型的行首 export,改成模块内私有的 type ClientBuildCheck。它在文件内照常使用,只是不再作为公共 API 暴露。单行改动。
  5. 是否有回归问题:无。已全仓核实没有任何 import 引用 ClientBuildCheck(src/agent/dsh-native.ts 与 tests/client-build-note.test.ts 只 import 了 DSH_BUILD_CHECK / emitNewerClientBuildNote / walkUpNodeModuleVersion / maxTestedVersion / newerClientBuildMessage),去掉 export 不改变任何可达性。本地重跑 node scripts/check-unused-exports.mjs → ✓ unused-export gate: clean(exit 0)。
  6. 是否破坏缓存:不涉及。纯源码可见性调整,与缓存/重试/增量游标/去重键无关。
  7. 是否改了设计方向:不涉及。仅收敛一个内部类型的可见性,无架构、边界或数据流转向。
  8. 是否引入了新的配置:不涉及。无新 env/config/默认值。

判定:改动为单行、定向、已本地复跑门禁通过,符合 auto-fix 车道范围;新 head 的全量 CI 会做最终确认。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

CI 判定:继承性失败(非本 PR 代码问题)——按 auto-fix 车道规则停手,等 master 修复后 rebase。

  1. 是什么问题:本次 11 个 test 分片(ubuntu/windows × node22/24)全红。逐份拉日志核实,所有分片死在同一条 pretest 自检、一行测试都没跑:
    process-spawning test files missing from LIVE_FILES in scripts/run-ci-tests.mjs: tests/issue3890-parent-gone-confirm-drain.test.ts
    
    报错行与总控 CI Control Center: Centralized CI Failure Handling & Status Sync (Comment all CI issues here; do not open new issues) | CI 总控: CI 失败集中处理与状态同步(所有 CI 问题在此评论, 勿开新 issue) #4599 记录的 master 常红(盘点第 2 条 / tests/claude-native.test.ts linux live-E2E subtests fail/hang in shared multi-agent sandboxes #1248)逐字节一致。
  2. 什么原因导致的:这是 master 自身 born-red,不是 When a user deletes the original session, the corresponding compressed session is deleted as well. | 用户删除原会话的时候一起删除对应的压缩会话 #2935 引入的。master head 146b34f27([PR] merge: tier-5 train — dsh prepare/streaming domain (7 PRs: bili_final terminal marker, auto-fold storm gate, wire-only guard) #4590 落点)新增了会起子进程的测试文件 tests/issue3890-parent-gone-confirm-drain.test.ts,却没登记进 scripts/run-ci-tests.mjs 的 LIVE_FILES;tripwire「未登记即 fail」直接在跑任何测试之前杀掉每个分片——所以本 PR 代码根本没被执行到。本地已核实:该文件在我分支树里存在(继承自 master)、两个脚本里都查不到登记项;我相对 master 的 diff 只碰 session-delete/plugin/dsh 相关路径,不含该文件。
  3. 影响范围与严重程度:仅本 PR 检查矩阵的信号级红,非用户侧功能问题;且因一行测试未跑,这次运行对 When a user deletes the original session, the corresponding compressed session is deleted as well. | 用户删除原会话的时候一起删除对应的压缩会话 #2935 本身不构成任何有效验证。
  4. 为什么不在这条车道修掉(为何停手):① tripwire 单独修好只会让那批一直被它掩盖的 master born-red 用例(约 35 个,跨 kernel/storage/快照渲染敏感面)第一次真正跑起来并继续红——修了 tripwire ≠ 本 PR 转绿,反而让信号更难归因;② 该文件登记按 tests/claude-native.test.ts linux live-E2E subtests fail/hang in shared multi-agent sandboxes #1248 门禁要求需先过并发假设评审,不是无决策依赖的一行小改;③ 根治已在途 PR [PR] fix(ci): clear the two master-red gates in one branch — unused-exports (#4602) + the #3890 LIVE_FILES tripwire (#4604) #4606 集中处理(登记该文件 + 其余三条 master 红),我这再修一遍只是造一个 rebase 时要丢弃的重复 commit。属「需决策 / 非小改」,按车道规则停手。
  5. 是否有回归问题:不涉及本 PR 判断——没有一条测试真正执行,无法也无需据此判定 When a user deletes the original session, the corresponding compressed session is deleted as well. | 用户删除原会话的时候一起删除对应的压缩会话 #2935 有无回归。
  6. 是否破坏缓存:不涉及。
  7. 是否改了设计方向 / 引入新配置:均不涉及;本条无任何代码改动。

怎么解(需要 owner 的一个动作):合并 #4606。合并后 master 自身检查转绿,本 PR rebase 到 master 重触发即可全绿,分支侧无需任何改动。

补充:attempt 1 的 unused-exports(去掉 ClientBuildCheck 导出)已生效并转绿,本次转发里也不再出现该项;当前剩余红 = 上述 11 个继承 test 分片。

结论:这是继承自 master 的 LIVE_FILES tripwire 红,不是 #2935 的问题;等 #4606 合并后 rebase / 重触发即可,auto-fix 车道到此停手。

ci-check added 7 commits October 25, 2026 10:10
master window commits 293ae65/de5e03659 bumped kernel/package.json to
0.0.165 without regenerating the lockfiles; lock-sync re-resolution then
rewrites the stale recorded versions (root 0.0.163, kernel 0.0.164) and
fails. Pure recorded-version sync via npm install --package-lock-only;
no dependency or semantic change.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant