Repository navigation
Description
Description *
摘要
对一个 33 块(5 active)、上下文 144,388 tok、cache 命中 100% 的健康长会话点「开启分支对话」,子会话首请求就零继承:outbound 从父的 5.3 MiB 变成 17.8 MiB(= inbound,一个块都没折),上下文 14.4 万 → 113 万 tok,超所有模型窗口,此后换任何模型都 400,会话永久不可用。
根因分两层:
- DSH 0.2.2-rc.1 的
fork()只写isSeeded: true,不写parentSession—— 而同一代码库的子代理路径(dsh-subagent)是写了的。bili 的forkChildVerdict因此返回parent-missing,fork 收养整条路径在入口就被跳过(日志#4215)。 - 退路
resume-inheritance走planForkAdoption,它的块门控是every()(全有或全无)。实测 5 个 active 块里 4 个只差几条消息就整块作废 —— 而这些消息恰好是 图片 part 和 reasoning,它们本来就不该参与「这个块是否完整复制」的判定。
净结果:33 块 → 1 块,丢掉 161,960 tok 的压缩成果。
复现
- 在 DSH 里开一个长会话,让它完成多次
compress(本例父会话 33 块 / 5 active /last context tokens: ~144388)。 - 确认它健康:
request: 3200 msgs, inbound=18.6MiB, outbound=5.3MiB, local=887ms、[acp-usage] input=144388 cached=144128 (cache hit 100%)。 - GUI 侧边栏「开启分支对话」。
- 子会话首请求即
outbound = inbound,随后所有请求 400。
日志证据
父会话最后一条健康请求 → 子会话首条请求(同一批内容)
16:41:52 request: 3200 msgs, inbound=18.6MiB, outbound=5.3MiB, local=887ms
16:42:20 [acp-usage] input=144388 cached=144128 (cache hit 100%)
--- fork 于 17:36:01 ---
17:36:34 [warn] [dsh-client] bili-native-dsh: fork adoption for session-d482576f-… skipped —
the session header says isSeeded but carries no usable parentSession;
the child starts without inherited compression state (#4215)
17:36:35 [resume-inheritance] 3192 msg(s) byte-exact prefix of session-9d5ebc9e-… — inheriting refs/blocks/lineage (#1486)
17:36:35 [resume-inheritance] session-d482576f-… resumed session-9d5ebc9e-…:
inherited 3419 ref(s) up to m04507, 1 block(s) (~29878 tokens)
17:36:35 strict-echo: injected blank reasoning_content on 1487 assistant tool-call message(s) (#762)
17:36:35 context 1133070 tokens reached preflight target 750000 (model window 750000,
model=deepseek-v4.1-flash; usage-baseline=none local-est=999535)
17:37:42 request: 3203 msgs, inbound=18.6MiB, local=67476ms
17:38:01 request: 3204 msgs, inbound=18.6MiB, outbound=17.8MiB, local=673ms
随后全部失败(换模型无效)
17:38:01 ← upstream 400: {"error":{"code":"invalid_parameter_error",
"message":"Range of input length should be [1, 1000000]"}}
17:38:01 ← upstream 400: code=bili_gate_oversize_refusal_unattributed (#4068)
17:40:42 ← upstream 400: "Range of input length should be [1, 1000000]"
17:42:12 ← upstream 400: "This model's maximum context length is 1048576 tokens.
However, you requested 1712327 tokens (1456327 in the messages, 256000 in the completion)"
「压缩上下文」也救不回来(缺口 383,070 tok / 25 轮预算 / 30 秒后浏览器断连)
17:36:36 [preflight] payload ~1158396 tok vs window 750000 (~1.5x) — raising summarization budget 16 -> 25
17:36:36 [preflight] round 1/25: payload ~1133070 tok vs window 750000 (deficit 383070), 51 viable range(s)
17:36:36 [preflight] fold order (largest first outside the recent zone, #2682):
m04172:m02871 (65690 tok), m03491:m03591 (48914 tok), m02839:m00844 (46912 tok), ... (+43 more)
17:37:12 [preflight] normalized reversed range m04172–m02871 → m02871–m04172
(non-monotonic refs after client history rewrite, #1001)
17:37:42 [preflight] still running after 30000ms grace — committed early SSE headers + keep-alive
17:37:42 client disconnected mid-stream after 52648ms
17:37:42 [preflight] summarization aborted: client disconnected
17:37:42 preflight aborted (the client disconnected during preflight compression); not forwarding
最好的一轮也只做到 compressed 2 range(s), ~115668 tokens saved (1133070 → 952766) in 66385ms,离 750k 还差 20 万。
根因 1:DSH 的 fork() 漏写 parentSession
dsh/node_modules/@deepseek-ai/dsh-api-session-controller/lib/index.js,async fork(request)(:756)里的建会话调用(:790):
await this.ctx.agents.create({
sessionId: childId,
seed,
inheritedEventCount: SessionLogOffset(boundary + 1),
meta: {
...source.header.cwd === void 0 ? {} : { cwd: source.header.cwd },
isSeeded: true, // ← :796 只有这个
...composition.agentPreset === void 0 ? {} : { agentPreset: composition.agentPreset }
},
agentOptions: { provider, model },
setup: composition.setup
});同样的形状在 dsh/node_modules/@deepseek-ai/dsh-session/lib/index.js 的 fork(source, boundary, childSessionId)(:2025,isSeeded: true 在 :2036)。
但同一个代码库的子代理路径是写了的 —— dsh/node_modules/@deepseek-ai/dsh-subagent/lib/index.js:353:
function childSessionMeta(parent, childDepth, isSeeded, selectedPreset) {
const parentHeader = parent.session.header;
const agentPreset = selectedPreset ?? parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
return {
...parentHeader.cwd !== void 0 ? { cwd: parentHeader.cwd } : {},
...agentPreset === void 0 ? {} : { agentPreset },
parentSession: parentHeader.id, // ← 子代理写了
isSeeded,
origin: "subagent",
delegationDepth: childDepth
};
}落盘证据(session.v4.jsonl.zstd 首帧):
// 本次子会话(2026-11-10 17:36:01)
{"type":"session","version":4,"id":"session-d482576f-…","createdAt":1794303361310,
"cwd":"…","isSeeded":true,"delegationDepth":0,"agentPreset":"standard"}
↑ 无 parentSession
// 同一台机器、更早版本做出来的同类会话(2026-10-27 21:27:48)
{"type":"session","version":4,"id":"session-309ae3d7-…","createdAt":1793107668223,
"cwd":"…","parentSession":"session-a2397714-…","isSeeded":true,
"delegationDepth":0,"agentPreset":"standard"}
↑ 有 parentSession扫本机 4 个 workspace 全部会话头,isSeeded:true 仅这 2 个。同一操作跨版本从有到无,疑似回归(但 309ae3d7 早于当前安装的 0.2.2-rc.1,也可能只是旧版行为,不敢断言)。
于是 bili 侧命中第一个分支(billion-context/dist/agent/dsh-native.js:161 的 forkChildVerdict):
const parent = session.header.parentSession;
if(session.header.isSeeded !== true) return { ok: false, reason: "not-seeded", sid };
if(typeof parent !== "string" || parent.length === 0) return { ok: false, reason: "parent-missing", sid };
if(parent === sid) return { ok: false, reason: "parent-is-self", sid };
return { ok: true, sid, parent };根因 2:every() 门控对图片 part 和 reasoning 零容忍
退路确实识别出了父子关系(3192 msg(s) byte-exact prefix),但只捞回 1 块。dist/index.js:1134 的 planForkAdoption:
if(includeBlocks){
for(const b2 of parent.state.blocks){
if(!b2.active) continue;
if(b2.effectiveMessageIds.length === 0) continue;
if(b2.effectiveMessageIds.every(id => incomingIds.has(id))) active.push(b2);
else straddled++; // ← 漏一个 id 就整块丢
}
}我把父会话 5 个 active 块逐块对子会话 seed 消息集做了覆盖率实测(bili 的 index.db,blocks.payload_json.effectiveMessageIds × 子会话 seed 的 msg_id 集合):
| 块 | 消息数 | 压缩后 tok | 在子会话 seed 中命中 | 覆盖率 | 结果 |
|---|---|---|---|---|---|
| b1 | 63 | 29,878 | 63 | 100.0% | 收养 |
| b24 | 2,114 | 77,704 | 2,076 | 98.2% | straddled |
| b31 | 1,015 | 72,551 | 991 | 97.6% | straddled |
| b32 | 74 | 9,443 | 65 | 87.8% | straddled |
| b33 | 17 | 2,262 | 14 | 82.4% | straddled |
只丢 161,960 tok 中的 1 块,其余 4 块覆盖率 82%–98% 却全部作废。
那 72 条漏掉的 id 是什么:
total missing ids: 72
by role/kind: user/text: 60, assistant/reasoning: 12
再细分 user/text:imagePart=true: 37, 普通文本: 23
也就是说:
- 37 条是带
"imagePart":true/rawOpenaiContent.image_url的图片消息。父会话共 145 条 imagePart 消息,子会话 seed 里只剩 67 条 —— 图片在 fork 时被重新编码/占位,identity hash 变了([image]占位与image_url原串之间切换)。 - 12 条是
assistant/reasoning。父会话有 22 条 reasoning,子会话 seed 里 0 条 —— DSH 的 fork seed 不带 reasoning 块,同时strict-echo: injected blank reasoning_content on 1487 assistant tool-call message(s) (#762)说明它在请求侧重写了这部分。 - 23 条普通 user/text(含
Image "image.png" (sha256:…)这类附件描述行)。
这些消息本来就不是「块有没有被完整复制」的可靠证据:图片会被 httpByteShed / BILI_IMAGE_* 改写,reasoning 会被 strict-echo 补空。用它们做 every() 的全等判定,等于让每个含图片或 reasoning 的块必然 straddled。
子会话这一侧同时留下了漂移痕迹:
[fold-reconcile] resent history drifted: 1 covered id(s) missing with no anchor match — mutation
[acp-drift] fold coverage mismatch: 497/498 covered message id(s) present in resent history
顺带一个可作旁证的落盘事实:父会话的 metadata.publicSnapshotTextComparable = false(publicSnapshotBytes: 33838036),说明父快照本来就不可按文本比较 —— 即使 parentSession 在,fork 收养走 snapshot 那条路也会撞 #4589/#3011 的 409。所以本 issue 与那些票是叠加关系,不是替代关系。
为什么 #4068 的 fail-fast 没能止血
子会话首请求触发了 oversize 拒绝,但走的是 _unattributed(不是 _bytes):
← upstream 400: {"error":{"type":"server_error","code":"bili_gate_oversize_refusal_unattributed",
"message":"billion-context refused this request locally: it is not attributed to any
bili-managed session, and its body provably exceeds even a 1,000,000-token window —
sending it directly to the provider can only be rejected (every retry re-sends the same
oversized history, #4068)."}}
紧接着 bili 自己打了这行:
← upstream 400 carries no parseable overflow marker (an intermediate gateway rewrote the
rejection body?) — bili's overflow self-heal (refold / emergency-shrink / window-learn)
CANNOT engage from this rejection; last settled input ~819231 vs effective window 1000000
它已经知道这个请求必死,但自愈链路被自己的错误码挡在外面,用户只能一轮一轮手点、一轮一轮看 400。
期望 vs 实际
| 期望 | 实际 | |
|---|---|---|
| 子会话首请求 outbound | ≈ 父的 5.3 MiB | 17.8 MiB(= inbound) |
| 继承块数 | 5 active | 1 / 33 |
| 上下文 | ≈ 144,388 tok | 1,133,070 tok(7.8×) |
| 可用性 | 正常对话 | 永久 400,换任何模型都无效 |
| 诊断 | 一行 degraded 说明原因 | 一行 skipped,无块级信息 |
建议
F1(DSH 侧,根因):SessionController.fork() 与 Session.fork() 的 meta 补 parentSession: source.header.id,参照同仓库 dsh-subagent/lib/index.js:353。如果分支在语义上不是「子会话」而只是「独立副本」,那 bili 侧就该换一个不依赖 parentSession 的识别方式 —— 但两边至少要对齐。
F2(bili 侧,every() 门控):把图片 part、reasoning、以及任何会被 httpByteShed / strict-echo / 图片改写碰到的消息从 effectiveMessageIds 的身份集合里排除,再判 every()。或退一步给覆盖率阈值(如 ≥ 90% 即收养,摘要照搬,未覆盖的原文保留)。实测数据:排除这 72 条后,b24/b31/b32/b33 全部 100% 覆盖,收养结果会从 1 块变成 5 块。
F3(bili 侧,防御):isSeeded === true 且 parentSession 缺失时不要直接跳过整条 fork 收养。bili 已经用前缀匹配证明了父子关系(3192 msg(s) byte-exact prefix),这条信息完全够用来做块收养;现在它只被用于 refs 继承。建议降级为「按前缀匹配到的父会话做块收养」并打 degraded (parent-missing → prefix-adopted)。
F4(bili 侧,可观测性):resume-inheritance 那行应该把 straddled 数量打出来。现在只有 1 block(s),看不出 33 块里 4 个 active 块是被丢掉的。
F5(bili 侧,#4068 覆盖面):bili_gate_oversize_refusal_unattributed 应该和 _bytes 一样进 overflow 识别,否则 bili 明知必死还要用户手点。
与既有 issue 的关系
- [Bug] Fork of DSH branch clone: inheritance permanently abandoned by 409 SNAPSHOT_UNAVAILABLE — zero-fold clone triggers context_length_exceeded 400 on first turn | [Bug] DSH 分支克隆的 fork 继承被 409 SNAPSHOT_UNAVAILABLE 永久放弃:克隆零折叠,首轮即 context_length_exceeded … #3011 / [bug] DSH forked child never inherits compression state: snapshot returns 409 SNAPSHOT_UNAVAILABLE and the client treats 409 as terminal | fork 子会话恒定不继承压缩状态:/__bili/plugin/snapshot 返回 409,客户端把 409 当作终态 #4589 / [Bug][dsh] Fork adoption returns 201 but child session has 0 collapsed blocks: all parent session compaction results voi | [Bug][dsh] fork 收养返回 201 但子会话 0 个折叠块:父会话压缩成果全部作废,原文重放 1,395,401 tok → preflight 299s 后被客户端断连 #5122 / Fork adoption fails on multimodal parents, then degrades into a raw-history rebuild that the provider rejects (400 InvalidArgument) — the forked session stays unusable #5595:都是 fork 收养失败,但入口不同 —— 那些是
snapshot 409 SNAPSHOT_UNAVAILABLE(父快照不可用 / 多模态不可比较)。本 issue 是收养路径在入口就被判定跳过(parent-missing),快照根本没被请求过;日志里没有degraded (snapshot …),只有skipped — … no usable parentSession。本机父快照publicSnapshotTextComparable = false说明两条链会叠加,但 F2 的every()缺陷与快照无关,修了它这一类会话也能救回大半。 - [Bug][fork] Fork child session zero block inheritance: all three adoption paths require "strictly longer", but fork is a | [Bug][fork] fork 子会话零 block 继承:三条收养路径都要求「严格更长」,而 fork 是父链的前缀副本 → 必然 miss;多模态父会话 50.7MiB 重放 → 摘要器空返回 → 413 #5738:纯代理车道(ZCode)的「三条收养路径都要求严格更长」。本机是插件车道,走
forkChildVerdict+maybeAdoptResume,入口不同;但 [Bug][fork] Fork child session zero block inheritance: all three adoption paths require "strictly longer", but fork is a | [Bug][fork] fork 子会话零 block 继承:三条收养路径都要求「严格更长」,而 fork 是父链的前缀副本 → 必然 miss;多模态父会话 50.7MiB 重放 → 摘要器空返回 → 413 #5738 的 F2「fork 需要的是复制语义,不是搬移语义」对本 issue 的 F3 同样成立,且 [Bug][fork] Fork child session zero block inheritance: all three adoption paths require "strictly longer", but fork is a | [Bug][fork] fork 子会话零 block 继承:三条收养路径都要求「严格更长」,而 fork 是父链的前缀副本 → 必然 miss;多模态父会话 50.7MiB 重放 → 摘要器空返回 → 413 #5738 里那个 37%–52% 覆盖率旁证与本 issue 实测的 82%–98% 是同一现象的不同刻度。 - [Bug][DSH] Fork loses fold inheritance without adoption diagnostics; interrupted preflight leaves child in repeated local 413 #4215(closed):症状相同(fork 丢继承 + preflight 卡死 + 反复 413),但当时没有
isSeeded/parentSession这层证据,结论停在「请调查收养入口静默跳过」。本 issue 把那个「静默跳过」定位到了parent-missing这个具体 reason,并给出了 DSH 侧的漏写行号,可作为 [Bug][DSH] Fork loses fold inheritance without adoption diagnostics; interrupted preflight leaves child in repeated local 413 #4215 的补充证据。 - Zero-Inheritance Session Cloning: Unified Fix for Eight Independent Defects in the Fork Adoption Chain (Next Release Tar | 会话克隆零继承:fork 收养链上八类独立缺陷统一修复(下版目标) #4266(closed,缺陷清单第 7 项):「DSH native 收养入口判定分裂」——本 issue 是这一项的具体实例:分裂的原因不是 attribution,而是 header 字段缺失。
额外观察
fork 之后 5 次 preflight 尝试里,fold order 报的第一个区间是 m04172:m02871,紧接着被 normalize 成 m02871–m04172(#1001)。#1001 在 #5885 里是 rewind 场景,这里是 fork 场景。如果 fork 的 seed 会让 ref 顺序整体反转,那每轮 fold 的收益都会被「先归一化再折叠」吃掉一部分 —— 这可能解释了为什么每轮只省 2–11 万 tok。
环境(对照 issue 模板)
- bili version:
0.1.213(npm latest,2026-11-07T23:32:24Z 发布,升级无解) - Usage mode:Plugin native(
bili-native-dsh,跑在宿主进程内,非 proxy 车道) - Client and version:DSH Desktop 0.2.2-rc.1(
@deepseek-ai/dsh-desktop/dsh-desktop-runtime/dsh-api-session-controller/dsh-session/dsh-subagent全部同版本) - Operating system:Windows 11 build 26200 x64(非 WSL)
- Upstream and model:
deepseek-v4.1-flash→deepseek-v4.1-flash-1017→deepseek-flash;窗口 1,000,000 / 1,048,576,headroom 后有效窗口 792,576(reserved 256000 for max output 256000, cap 0.25 via compress.outputHeadroomMaxPct) - Compression-related config:
C:\Users\<user>\.config\billion-context\billion-context.json仅{"providers": {}},compress 全默认;BILI_SILENT_DIRECT_MAX_MB未设 - 存储:
storage.unified = full,~/.local/share/billion-context/sessions/index.db
bili version *
0.1.213
Usage mode *
Plugin native (bili plugin install …, runs inside the host process)
Client and version *
DeepSeek Harness (DSH) Desktop 0.2.2-rc.1 — in-process native plugin lane (bili-native-dsh), not the proxy lane
Operating system *
Windows 11 build 26200 x64 (not WSL)
Upstream and model *
deepseek-v4.1-flash → deepseek-v4.1-flash-1017 → deepseek-flash · declared window 1,000,000 / 1,048,576 · effective 792,576 after headroom
Compression-related config (optional)
# ~/.config/billion-context/billion-context.json
{"providers": {}}
# compress: all defaults (no minCompressRange / maxSummaryLength / preserveRecentMessages overrides)
# env: BILI_SILENT_DIRECT_MAX_MB unset
# storage.unified: fullLog excerpt (strongly recommended)
=== 父会话最后一条健康请求 ===
2026-11-10T16:41:52.332 request: 3200 msgs, inbound=18.6MiB, outbound=5.3MiB, local=887ms
2026-11-10T16:42:20.297 [acp-usage] input=144388 cached=144128 (cache hit 100%)
=== fork(2026-11-10T17:36:01+08:00)===
2026-11-10T17:36:34.854 [warn] [dsh-client] bili-native-dsh: fork adoption for session-d482576f-3593-4751-8b02-915c75f2f140 skipped — the session header says isSeeded but carries no usable parentSession; the child starts without inherited compression state (#4215)
2026-11-10T17:36:35.247 [resume-inheritance] 3192 msg(s) byte-exact prefix of session-9d5ebc9e-6771-43c7-9dbd-7c5fa4e45bac — inheriting refs/blocks/lineage (#1486)
2026-11-10T17:36:35.448 [resume-inheritance] session-d482576f-… resumed session-9d5ebc9e-…: inherited 3419 ref(s) up to m04507, 1 block(s) (~29878 tokens); stale citations now resolve to their original messages (#1486)
2026-11-10T17:36:35.865 strict-echo: injected blank reasoning_content on 1487 assistant tool-call message(s) (#762)
2026-11-10T17:36:35.995 [warn] context 1133070 tokens reached preflight target 750000 (model window 750000, model=deepseek-v4.1-flash; usage-baseline=none local-est=999535); preflight compressing before forward
2026-11-10T17:36:36.002 [warn] [preflight] payload ~1158396 tok vs window 750000 (~1.5x) — raising summarization budget 16 -> 25, rounds 16 -> 25
2026-11-10T17:36:36.120 [preflight] round 1/25: payload ~1133070 tok vs window 750000 (deficit 383070), 51 viable range(s)
2026-11-10T17:36:36.123 [preflight] normalized reversed range m04172–m02871 → m02871–m04172 (non-monotonic refs after client history rewrite, #1001)
2026-11-10T17:37:42.023 [preflight] summarization aborted: client disconnected
2026-11-10T17:37:42.381 request: 3203 msgs, inbound=18.6MiB, local=67476ms
2026-11-10T17:37:42.381 [warn] preflight aborted (the client disconnected during preflight compression); not forwarding
2026-11-10T17:38:01.164 request: 3204 msgs, inbound=18.6MiB, outbound=17.8MiB, local=673ms
2026-11-10T17:38:01.620 [warn] ← upstream 400: {"error":{"code":"invalid_parameter_error","message":"Range of input length should be [1, 1000000]","type":"invalid_request_error"}}
2026-11-10T17:38:01.620 [warn] ← upstream 400: {"error":{"code":"bili_gate_oversize_refusal_unattributed","message":"billion-context refused this request locally: it is not attributed to any bili-managed session, and its body provably exceeds even a 1,000,000-token window — sending it directly to the provider can only be rejected (every retry re-sends the same oversized history, #4068)."}}
2026-11-10T17:38:01.620 [warn] ← upstream 400 carries no parseable overflow marker (an intermediate gateway rewrote the rejection body?) — bili's overflow self-heal (refold / emergency-shrink / window-learn) CANNOT engage from this rejection; last settled input ~819231 vs effective window 1000000
2026-11-10T17:42:12.839 [warn] ← upstream 400: "This model's maximum context length is 1048576 tokens. However, you requested 1712327 tokens (1456327 in the messages, 256000 in the completion)."
/acp or acp_status output (optional)
# bili sessions ls(三个会话均 active)
session-9d5ebc9e-… 10.7 MiB 父会话(健康,33 blocks / 5 active / ctx ~144388 tok)
session-d482576f-… 1.7 MiB 子会话(fork 出来,4 blocks / 4 active / ctx ~1133070 tok)
session-93c99c36-… 2.7 MiB 用户事后另开的干净会话(isSeeded:false,完全正常)
# index.db 实测:父会话 5 个 active 块在子会话 seed 里的覆盖率
b1 msgs= 63 tok= 29878 coverage=100.0% → 收养
b24 msgs= 2114 tok= 77704 coverage= 98.2% → straddled
b31 msgs= 1015 tok= 72551 coverage= 97.6% → straddled
b32 msgs= 74 tok= 9443 coverage= 87.8% → straddled
b33 msgs= 17 tok= 2262 coverage= 82.4% → straddled
# 丢失 = 161,960 tok
# 漏掉的 72 条 id 的构成
user/text: 60(其中 imagePart=true: 37,普通文本: 23)
assistant/reasoning: 12
# 父会话 reasoning 22 条,子会话 seed 中 0 条
# 父会话 imagePart 145 条,子会话 seed 中 67 条
# 父会话快照元数据(旁证:snapshot 那条路也不通)
publicSnapshotTextComparable: false
publicSnapshotBytes: 33838036
Activity
ranxianglei commentedon Nov 10, 2026
[bot] 🏷
(ai: vllm-qwen/qwen3.8-27b)
查重结论:不重复,但高度重叠于在途 PR #5962(作者 2772780896,分支
2026-11-08_dsh-fork-source-adoption,「fix(dsh): preserve verified seeded context inheritance」)。该 PR 直接命中本 issue 的核心机制(DSH fork 种子继承 +parentSession/forkSource裁决 + 服务端按原文身份选块),1740 行插入、三个测试文件大幅扩充,领先 master 4 个提交。其余开放 PR(#3024 快照可重试路径、#5901/#5873 快照表底座、#6123 dsh 原生压缩 feat)与本 issue 失败签名不同。故不开并列修复 PR,改为对 #5962 做本 issue 视角的审核。(注:镜像把该 PR 编号成 #5964,与 GitHub 真实号 #5962 冲突——已按权威 PR 列表校正,避免张冠李戴。)我查证了什么(当前 master @wt-6124,晚于报告的 0.1.213)
src/fork-adoption.ts:180if (b.effectiveMessageIds.every((id)=>incomingIds.has(id))) active.push(b); else straddled++;,所有块收养都经planForkAdoption;maybeAdoptResume(src/server/handle.ts:1944,blocksEnabled:true)走的就是它——正是日志里「inherited 1 block(s)」的来源。src/agent/dsh-native.ts:1379if(typeof parent!=='string'||parent.length===0) return {ok:false,reason:"parent-missing"}—— 只认parentSession,不认forkSource。maybeAdoptResume成功日志(src/fork-adoption.ts:451-454)只打 seededRefs/maxRef/adoptedActive/adoptedTokens,不打 straddled;对照maybeAdoptForkBlocks:384与reconcileDerivedLineage:597都打了。操作员看不到 resume 路径丢了几个 active 块。src/agent/native-intercept.ts:663unattributedOversizeRefusal(bili_gate_oversize_refusal_unattributed),未进入溢出自愈识别。一个关键层次修正(重要):issue 称「fork 收养整条路径在入口就被跳过」,overstates 了 RC1。日志同时有三条:
[dsh-client] …fork adoption … skipped — …no usable parentSession…(#4215)← 客户端插件 fork 协议被跳过(RC1);[resume-inheritance] 3192 msg(s) byte-exact prefix…inheriting refs/blocks/lineage(#1486);…resumed…: inherited 3419 ref(s)…1 block(s)(~29878 tokens)← 服务端 resume-inheritance 兜底确实跑了,继承了 refs + 1 块。也就是说:真正造成「33→1 块」的伤害是 RC2(every() 门把 resume 兜底限制到只剩 b1),不是「整条路径被跳过」。RC1 只是关掉了更强的客户端路径,让会话退回更弱的 resume 兜底;若 RC2 修好,现有兜底即便没有 parentSession 也能收养全部 5 块。
方案评估
src/fork-adoption.ts(resume 兜底的 every() 门 = F2 残留 + F4 straddled 日志都不在其 diff 内),也不动 native-intercept.ts(F5)。所以即便 [PR] fix(dsh): preserve verified seeded context inheritance #5962 合入,仍有残留缺口(见下)。下一步
forkSource;其 durable 选块对「被丢掉 37 个 imagePart」的处理是否覆盖)。先给到这里。我正在拉 #5962 全量 diff 做审核,稍后回报审核结论与残留缺口的处置建议。