Skip to content

[PR] fix(#4921): protect workspace-instruction revisions, not just the baseline - #4922

Open
IcyDate wants to merge 355 commits into
ranxianglei:masterfrom
IcyDate:2026-10-27_durable-agents-md-update-carriers
Open

IcyDate wants to merge 355 commits into
ranxianglei:masterfrom
IcyDate:2026-10-27_durable-agents-md-update-carriers

Conversation

@IcyDate

@IcyDate IcyDate commented Oct 27, 2026 •

Copy link
Copy Markdown

这是啥问题

dsh 的 workspace instructions 是 durable 载体(#2447),而它有两种形态;守卫只覆盖了第一种。

基线是 Instructions from: <path>(大写 I),已被 durable/agents-md 保护住。但会话中途改了文件之后,@deepseek-ai/dsh-agent-instructions 发的是核对消息:

文案 触发
Updated instructions from: <path> 文件内容被修改
Additional instructions from: <path> 后来才发现该文件(基线里根本没带它)
Instructions removed: <path> 该文件不再适用

三者全是小写 instructions(或干脆换了动词),而 dshWorkspaceInstructionsPath 用的是 msg.text.includes("Instructions from:") ——大小写敏感精确匹配,一条都没被保护。

后果不是"少了一条历史"。baselineIdentity(dsh-agent-instructions/lib/index.js:39)只含 projectRoot / projectRootMarkers / maxBytes / maxSourceBytes / instructionFileCandidates / localInstructionFileCandidates,不含文件内容——内容改了 identity 不变,宿主不重发基线。所以那条修订是本次变更唯一的载体。折掉它之后,上下文里留下的是「一条被钉死的旧基线 + 一个不含新规则正文的摘要」:模型继续按用户已经改掉的规则干活,并且无从察觉自己丢了更新。这比两条都折更糟——两条都折,模型至少知道自己手上没有规则。

实测(一个 2907 条消息 / 23 个 block 的真实会话):11 条 Updated instructions from: 载体,9 条被折,其中一次 compress 一口气吞掉 4 条。另一会话里 Additional instructions from: neighbor-help-frontend/AGENTS.md(6710 字符)被折进 b1,此后整个会话再无项目指令。

根因(代码坐标)

  • src/durable-message-guards.ts dshWorkspaceInstructionsPath:只匹配大写 Instructions from:。原注释写着"大小写敏感是故意的——核对消息属于普通可消费历史",本仓 tests/issue2447-dsh-workspace-instructions.test.ts:138 也把这一点钉成了预期。
  • 这条预期不成立:dsh-agent-instructions/lib/index.js:39 的 baselineIdentity 不含文件内容,而 :262 / :283 / :285 三种修订文案全是小写(或换动词)——于是"可消费"的那条恰好是唯一带新内容的那条。
  • 也不能简单加 toLowerCase():默认策略是 protectedLatestTools: ["durable/*"] 的 latest-per-identity(collectLatestProtected 按 virtualPath 分组、每组只留最新一条)。基线与修订共用 durable/agents-md 时,最新修订会把多文件基线挤成可折。实测那对 base + revision:基线带 2 个文件(全局 + 项目根,5311 字符)、Additional 只带 1 个(6710 字符),共用 identity 就是从"保 2 丢 1"变成"保 1 丢 2",净亏。

改动

  • src/durable-message-guards.ts:新增 agentsMdRevisionPath(),把三种修订投射到独立 identity durable/agents-md-update/<path>;基线保留 durable/agents-md。durable/* 仍匹配带后缀的路径(matchToolPath 对 <prefix>/* 只看第一段)。
  • dshWorkspaceInstructionsPath 改为先认修订、再认基线。这不是形式上的整齐:修订正文完全可能引用大写 Instructions from: 字面量(用户在自己 AGENTS.md 里说明这个匹配串),按旧顺序会被误判成基线、拿到和真基线相同的 identity,再被 latest-per-identity 挤掉。回放真实会话日志时,就有一条 Updated instructions from: ~/.dsh/AGENTS.md,其正文同时含大写 Instructions from:。
  • src/config.ts:补文档——修订有独立 identity,所以 "durable/agents-md" 这个精确 takeover 不再覆盖修订;要收全家桶写 "durable/*",或再加上 "durable/agents-md-update/*"。
  • tests/issue2447-dsh-workspace-instructions.test.ts:翻转原第 138 行断言(原值 undefined,旁边注明 "lowercase reconciliation variants stay consumable history"),并在旁边写清为什么翻转;补三种修订形态、多文件基线、正文引用大写标记的用例;新增端到端折叠场景。

验证

  • 真实日志回放:解压两个真实会话日志(session-b5ad5365-… 6879 条事件、session-d979d0c3-… 3277 条),把新正则直接跑在真实注入消息上:Additional instructions from: neighbor-help-frontend/AGENTS.md → durable/agents-md-update/neighbor-help-frontend/AGENTS.md;同时确认所有真实基线消息都不被修订正则误命中。上面那条"正文引用大写标记"的用例就是从这里来的,不是编的。
  • 防静默失效:正文引用大写标记 那条用例靠 INSTR_MSG.replace(...) 注入,一旦 fixture 变动会静默不生效、断言照样通过。已加 assert.notEqual(注入后, INSTR_MSG, "fixture injection must actually land"),注入没落地直接红。
  • 单测:三种修订形态各自的 identity;多文件基线仍归 durable/agents-md;基线正文引用 Instructions from: 仍认作基线;修订正文引用它仍认作修订(顺序正确性)。
  • 端到端折叠场景(新增):基线居中、修订紧随其后,执行一次真实 compress 让区间同时盖住两者 → 修订正文 always run lint AND typecheck 存活(它别处没有);基线同时存活(独立 identity,没被修订挤下去);区间里的填充消息确实被吃掉(证明折叠真发生了,不是保护把整个范围否决了)。
  • stash 复核:仅 stash src/durable-message-guards.ts 重跑,issue2447 的 5 个用例红 2 个(单测 + 新折叠场景);恢复后 5/5 绿。确认这两个测试真钉住了行为,不是无论怎么实现都会通过的摆设。
  • 回归面:issue2447 5/5;issue2446 / issue2481 / issue2766 19/19;durable-tool-identity 9/9;issue2419 + protected-* 系列 17/17(2 个 verif 用例无求解器环境自动 skip);wire-contract-golden / config-surface-guard / marker-lane-coverage / golden-kernel-dist-guard 全绿;npm run typecheck 干净;unused-export / test-import-graph 两个 gate 干净。

关联

Closes #4921

#2447(本条 durable 车道)· #2428(message-level 保护钩子)· #2419 / #2446 / #2481(同族 carrier)

@IcyDate
IcyDate force-pushed the 2026-10-27_durable-agents-md-update-carriers branch from d6ea911 to 0d6578a Compare October 27, 2026 05:32
@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

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

查重:不重复。 open+closed 全量检索(按 #4921 引用与 durable/agents-md 失败签名):引用 #4921 的 PR 只有本 PR,无并行修复在途;相关的 #2447/#2428/#2419/#2446/#2481 是上游同族 durable 车道条目(已合入/已关闭),#4495 是同域但不同机制(mnemon runtime memory snapshot 缺 classifier),不构成重复。

标签:bug + dsh + scope/kernel + p2(缺陷类;dsh 车道;src 侧折叠保护判定属压缩决策面;条件触发、有绕行,p2)。

本条为外部贡献 PR(无 agent 标记),正在做完整审核:拉分支读 diff、核 CI、定向跑相关测试。结论稍后按八问体例补在这条线程。

@ranxianglei ranxianglei added bug Something isn't working dsh p2 scope/kernel labels Oct 27, 2026
@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

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

审核完成(外部贡献 PR,独立审过 diff、机制与 CI)。结论:可合。

① 方向关:修对了层。缺陷的产地是分类器——dshWorkspaceInstructionsPath 把三种修订文案判成「普通可消费历史」,PR 在分类器层给修订投射独立 identity,不是事后在折叠路径上拦症状。根因链也成立:宿主 baselineIdentity 不含文件内容(dsh-agent-instructions/lib/index.js:39),内容变更不触发基线重发,修订是变更的唯一载体——原注释「lowercase variants stay consumable history」的前提(修订是冗余更新)不成立。

② 回归审查(逐项过既有行为):

③ 代码审查(主关):关键机制我独立核过源码而非只信 PR 描述——matchToolPath 对两段式 prefix/* 模式只比第一段(kernel/src/protected.ts:267-269),故默认 durable/* 覆盖带后缀的新 identity;collectLatestProtected 对含 / 的模式按 virtualPath 分组、每组留最新(kernel/src/protected.ts:446-458),故基线与每个文件的最新修订各自存活。PR 声称的「共用 identity 会让最新修订挤掉多文件基线」的推演与这两段代码一致,per-path 设计是对的。修订先于基线分类的顺序论证(修订正文可能引用大写标记,真实日志有实例)也成立且有用例钉住。正则锚定消息头 + 要求 <system-reminder> 包裹:失配时退化为旧行为(不保护),不会误伤——fail-safe 方向。残留边缘:若未来 dsh 在包裹标签前加前导内容会静默失去保护,当前真实流量已验证锚定成立,不构成拦截理由。

④ 测试有效性:新增 e2e 折叠场景走完整管道(x-bili-plugin: dsh 头激活车道分类器 → 真实 compress 经 /__bili/plugin/tool 执行 → 下一轮出站 body 断言),三条断言分别钉住「折叠真发生了(填充消息被吃掉)」「修订规则正文存活」「基线并存未被挤掉」——有牙,不是恒真。单测断言直接等值比对投影结果;作者 stash 复核(仅撤 guards 改动 → 5 用例红 2)证明测试真绑定行为。防 fixture 静默失效的 assert.notEqual(注入后, 原文) 是好卫生。CI 已在 head sha 0d6578a3c 上全量跑绿(10 个 workflow run 全 success,含 ubuntu/windows × node 22/24 测试矩阵与全部 e2e 车道),本地不必重复。

⑤/⑤b diff 整洁度与代码质量(外部提交从严):3 个文件全部与目的相关,无空行/生成物/锁文件噪音,无 version 触碰;命名达意(agentsMdRevisionPath)、注释讲为什么且与本文件既有风格一致、hex 转义合规(KDD #2)、无死代码/重复/新依赖、函数单一职责。全项达标,不需要对齐重构。

八问:

  1. 是什么问题:dsh 会话中途改 AGENTS.md 后只发一条小写确认消息携带新规则全文,bili 的守卫只认大写基线——这条唯一载体被折叠后,模型按用户已经改掉的旧规则继续干活且无从察觉。
  2. 什么原因导致的:分类器的大小写敏感精确匹配基于错误前提(把修订当冗余历史),而宿主基线 identity 不含文件内容导致修订实为唯一载体;原注释把这个错误前提钉进了预期和测试。
  3. 影响范围与严重程度:仅 dsh 车道;触发条件 = 会话中修改/删除/新发现指令文件 + 后续压缩恰好盖住该消息。后果为静默过期规则执行(比两条都折更糟)。实测两会话:11 条 Updated 载体折掉 9 条、一次压缩吞 4 条。p2(有条件触发、可绕行)。
  4. 怎么修复的:分类器层把三种修订投射到独立 per-path identity durable/agents-md-update/<path>,基线保持 durable/agents-md;修订先判(防正文引用大写标记的误判);复用现有 durable/* latest-per-identity 机制,内核零改动。
  5. 是否有回归问题:无。审过的既有行为(基线判定、exact-takeover 配置、同族其余载体、非 dsh 车道、fail-fast/阈值/输出格式)逐项保持;唯一行为变化即修复目标本身,已披露。
  6. 是否破坏缓存:不涉及。保护只决定哪些消息不进折叠区间,不改写任何线上已有字节,前缀缓存语义不变。
  7. 是否改了设计方向:不涉及。完全复用 feat: message-level protection hook + dsh durable-state guards (skill catalog #2419, workspace instructions #2447) #2428/feat(#2446): durable-state carriers become virtual tool paths (Config.messagePathOf) #2736 的虚拟工具路径 + 受保护模式机制,无新架构。
  8. 是否引入新配置:无新字段/env/默认值;仅文档补充(exact takeover 用户需显式加 durable/agents-md-update/* 或改用 durable/* 才能覆盖修订)。

收工判定:代码与行为均可合入。本 PR 改变默认保护行为面(§7.4「fallback/default 变化属产品决策」),不进 auto-merge,请人工合并:#4922 。合并后 #4921 由 PR 的 Closes 自动关闭,我不代关。

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)
Synced to the latest master (dcf760753 → 44067a5bc, 14 new commits around fork/snapshot recon gates #5188/#5140): clean merge, zero conflicts — none of the PR's three files were touched by those commits. Head is now 2abb52ea6; merge state moved behind → blocked (current with master and conflict-free, awaiting the fresh CI run on the new head).

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)
Re-verified after this guard fire: master tip is unchanged (44067a5bc) and the branch (2abb52ea6, synced last round) already contains it — nothing new to sync or push. State remains blocked: current with master and conflict-free, held only by the CI run on the new head.

ci-check and others added 19 commits November 6, 2026 10:16
… issue number

Repo convention names regression files after the GitHub issue (issue2915-/issue3236-/...); GitHub ranxianglei#5767 is an unrelated banner issue, so the mirror number in these filenames pointed at the wrong thread.
…IdArg

The ad-hoc id normalization kept original case and skipped zero-pad forms, so B5/005-style ids undercounted the freed-chars receipt; parseBlockIdArg is the kernel's canonical normalizer and unknown ids are skipped honestly instead of matched against a mangled key.
acp_retire/acp_restore/acp_rewrite fell through toolChipCls to the unstyled default; they sit in the same fold-family chip class as compress/acp_status/acp_cache.
…#5765 review)

acp_retire counted freed summary chars over the whole input list before the
flip — already-retired (noop) and rejected ids inflated the receipt. Count
outcome.applied after the flip instead (summary text survives the flag
change). acp_rewrite silently dropped malformed items[] entries — the model
could not tell which item did not land; report a skip count. Drop the
dead failedCount counter.
…cks (ranxianglei#5765 review)

Math.min(...nums)/Math.max(...nums) over effectiveMessageIds can exceed
V8's argument-spread limit for tier-3 blocks of very long sessions; compute
min/max with a loop. Reuse the already-computed active list in renderHandoff
instead of re-filtering s.state.blocks.
…eview)

The PR's snapshot-render change (retired blocks contribute no synthetic
anchor while covered rows stay suppressed) had no test anywhere; legacy
prune and D-lane behave identically for this shape, so add a hermetic
D-on test with assertDActive to prove the lane rendered.
ranxianglei#5481)

The merge of origin/master into the branch (4954a12) resolved the
CHANGELOG conflict with the pre-review wording of the ranxianglei#5481 entry,
silently dropping the correction from fe4ffdb: the codex local-forge
path logs its cost line WITHOUT an outbound field now (nothing is
handed to fetch there), so '本地 forge ... 既有记账边界不变' was
inaccurate for that path. Re-applies the corrected sentence verbatim.
darwin's 'security find-certificate -a -p' keychain export includes
non-CA leaf identities (e.g. com.apple.kerberos.kdc, which also carries
a duplicate EKU extension in violation of RFC 5280 4.2). Strict
replace-semantics clients such as codex's rustls HTTP stack parse every
entry of SSL_CERT_FILE and refuse to build their client when any entry
fails, so a single such cert broke every connection through bili codex.

writeCombinedBundle now keeps only entries that parse as X509
certificates AND carry basicConstraints CA:TRUE or the keyCertSign
key-usage bit (legacy pre-basicConstraints anchors), applied to ALL
sources (system bundle, OS store, Mozilla roots, MITM root). The bundle
gains a leading marker comment; a fresh file missing it (pre-upgrade
bundle) is rebuilt once so the fix takes effect immediately instead of
waiting out the 24 h freshness gate. If the filter ever drops everything
(broken certificate parsing), it falls back to the unfiltered bundle
with a loud error log rather than shipping an empty trust store.
…L minting arm

Owner ruling (ranxianglei#5608 decision thread follow-up): the three one-shot
full-prefix-invalidation producers get fold-boundary discipline, closed
inline on PR ranxianglei#5609 per 「不要立issue,原来的pr改」.

C — timestampsHighWaterRefSeq: a rendered ACP tag gains its time=
attribute only when its ref index exceeds the mark recorded pre-pass.
The mark freezes from state across sync/fold/hydration and ledger
alignment raises it to the aligned max — so flipping ranxianglei#4313 on
mid-session (incl. the v0.1.209→v0.1.210 default off→on upgrade)
changes ZERO prefix bytes until a tag's own content edit/fold remints
it onto a higher seat. Brand-new sessions stay fully functional.

B — suppressMintingOnce: the stamp node records ids without fabricating
Date.now() while armed, instead of seeding false timestamps that would
ride into future lineage via seedTimestampsFromParent. Host arms it on
bulk-old-history paths (miss-adoption fallbacks, weak adoption, forks);
strong byte-exact resume stays un-armed (uncovered tail provably new).
Self-clearing; deliberately not hydrated after restart.

kernel/package.json 0.0.180 → 0.0.181. New pins:
kernel/tests/issue5608-timestamp-tails.test.ts (4).
…the next post-fold save

B/C/D ruling 「不能留尾巴」(D half): a restarted proxy must serve bytes
identical to what the provider cached — renumbering every historical ref
at cold load invalidated whole prefixes on every restart. Restamp now
fires once from the persistence path after a fold (pendingLedgerAlign
marker set at the stream.ts fold commit, consumed by scheduleSave /
flushSync / writeNow before content-store serialization so CCR
re-keying (ranxianglei#4733) rides the same record). Sessions that never fold keep
legacy counter refs forever at zero cost; stale-citation refusal
(ranxianglei#4599) covers the interval either way.

Also arms honest-NULL (kernel ranxianglei#5608-B) on the miss/fork boundary paths
that bulk-seed OLD history into fresh child state (handle.ts fallbacks,
weak adoption, derived/manual forks); strong byte-exact resume keeps
honest now-mints for its provably-new tail. Re-pins S15, the HTTP fork
survival test (cold load is now a byte-exact no-op) and the storage
digest pins; the miss-seed e2e pins replace fabricated-now assertions
with the honest-NULL contract.
…glei#5609)

Only conflict: kernel/package.json version line — both sides bumped after
base 0.0.180 (ours 0.0.181 via ranxianglei#5608-B/C time-attr work, master 0.0.182 via
ranxianglei#5758/ranxianglei#5759). The merged tree carries BOTH unreleased change sets, which no
single prior version denotes, so reconcile to 0.0.183 (max+1).
…#5802)

Review finding on PR ranxianglei#5809: the raw-DER KeyUsage scan expected the
extnValue OCTET STRING immediately after OID 2.5.29.15, but per
RFC 5280 s4.1 a critical-marked extension carries a BOOLEAN between
the two. KeyUsage is RFC-recommended critical on CA certificates, so
exactly the legacy pre-basicConstraints anchors the fallback branch
exists for were silently dropped whenever they carried the marking.
Empirically verified: a forged keyCertSign-only cert with the
critical flag parsed isCaLike=false before this change, true after.
Adds the matching regression row with ground-truth assertions.
… states

Conditional spreads in the two kernel clone paths (sync.ts syncBlocks
clone, compress.ts fold clone) plus delete-style flag clearing in the
stamp node: states created before this feature must not acquire the
`timestampsHighWaterRefSeq` / `suppressMintingOnce` KEYS with
undefined values — key presence itself moved golden stateKeyDigests
and would have flapped parity digests turn-to-turn (golden-storage-render
suite red on 3edf28b). The B arm still clears the key (not
`undefined`) so a consumed flag leaves no trace; C writes its mark as a
plain number after first init. Version 0.0.183 -> 0.0.184.
ranxianglei#554 state poisoning)

The first cut armed suppressMintingOnce on every "starting fresh"
fallback of the resume-inheritance gate. That is the same false-positive
zone v3's gate already rejects adoption for: cross-lane boilerplate
prefixes earn positive overlap against genuinely NEW conversations too,
and lanes that never run a kernel pass (side requests are pure
passthrough, ranxianglei#388) can never self-clear the flag — the arm leaked into
persisted kernel state and broke the ranxianglei#554 side-request state deep-equals
in CI. Decision: unproven identity keeps brand-new semantics (its ids
mint normally); arms now live only on proven-identity paths — weak/strong
resume adoptions (maybeAdoptResume, armBoundaryNull) and
derived-fork/manual-fork sites. Below-threshold restarts therefore mint
"today" display values on uncovered lines again: display-only cost,
accepted; billing behavior is unchanged either way.

Re-pins:
- tests/side-request-isolation.test.ts green again (no fixture change)
- tests/resume-miss-timestamp-seed.test.ts gate-negative e2e: FRESH
  fallbacks must MINT their own fresh ledger (was: empty/honest-NULL)
- tests/storage-orphan-recovery.test.ts: a bare cold load arms nothing
  (marker is fold-commit-only, ranxianglei#5608-D)
- src/fork-adoption.ts / src/plugin.ts / src/state-hydration.ts:
  guard the timestampsHighWaterRefSeq inherit/carry so a markless parent
  cannot materialize the key on child/persisted state
ranxianglei and others added 29 commits November 10, 2026 21:31
…ndle-ca-only-filter

[PR] fix: keep only CA certificates in combined-ca.pem (ranxianglei#5802)
…route-env-on-unload

[PR] fix(hermes-plugin): restore routed process env on unload and watchdog exit (ranxianglei#4644 follow-up)
kernel version: 0.0.183 (PR) vs 0.0.192 (master) -> 0.0.193
…anxianglei#6132)

Owner-directed full removal of the experimental compressAtFoldPoint /
compressAtFoldPointClearThinking feature (v1 ranxianglei#5471 + unreleased v2 ranxianglei#5949):
two distinct live pathologies in 9 days on its own target model family —
2026-11-08 voluntary re-compress loops (v1, ranxianglei#5949 field report) and
2026-11-10 authorship misattribution (v1 in v0.1.213 production: the pair's
relocation left the turn's own answer text bare at the transcript tail, and
a 27B model read its own last utterance as a user message, m00376 chain).

- delete kernel/src/relocate-compress.ts + kernel/tests/relocate-compress.test.ts
- drop the relocate-compress-carriers pipeline node (after hide-compress-calls,
  before view-latch) and its registration
- drop Config.compressAtFoldPoint / .compressAtFoldPointClearThinking /
  .reasoningDroppable and the public relocateCompressCarriers/plan exports
- drop the already-folded-echo parse kind + v2 pointer marker
  (parse-compress-input.ts) and the two echo tests
- kernel 0.0.192 -> 0.0.193 (both package-locks)

kernel-change: removed relocate-compress.ts, the pipeline node, 3 Config
fields and the public exports (full feature removal, owner decision after the
2026-11-10 incident, ranxianglei#6132); with the switch off the transform was a
byte-identical no-op, so removal changes nothing for the default layout.
kernel 0.0.192 -> 0.0.193.

Closes ranxianglei#6132 (kernel side; host/web/docs follow in the next commit of this PR)
…#6132)

Companion to the kernel removal: strip the experimental fold-point
relocation feature (v1 ranxianglei#5471 + unreleased v2 ranxianglei#5949) from everything else.

- env knobs BILI_COMPRESS_AT_FOLD_POINT(_CLEAR_THINKING) + resolvers
  (knobs.ts), file keys compress.compressAtFoldPoint(_ClearThinking)
  (config.ts: CompressSettings fields, COMPRESS_SETTING_FIELDS, parser
  loop), compress-settings.ts cascade/pass-through/reachability,
  relay.ts round-2 mirror + drop-predicate install, the four prepare*
  install sites, plugin-render.ts S4 mirror, makeFoldPointReasoningDroppable
  (server.ts), the v2 already-folded-echo ack in stream.ts
- stale configs: one-shot INERT deprecation warning extended to the two
  removed keys (every level incl. top-level), values ignored
- web config row + i18n strings (en/zh), config-reference yaml, site docs,
  site i18n strings, blog post + article entries deleted (owner instruction)
- docs configuration en/zh + lifecycle ledger (rows removed, §5 note),
  CHANGELOG entry
- tests: delete tests/compress-at-fold-point.test.ts, drop the echo test
  from compress-reject-reasons.test.ts, extend removed-compress-keys-inert
  with the removed fold-point keys

Closes ranxianglei#6132
…5765 tool surface

The acp_retire/acp_restore/acp_rewrite schemas are always-on in every
plugin manifest, so the pinned manifest/fork/launcher tool lists, the
insertion-order lists, and the codex-official prompt regex ('four' ->
'seven' context-management tools) all needed the three new names.

Fixes the 9 stale-assertion failures across plugin-protocol, plugin-fork,
launcher-plugin-mode and codex-official (deterministic on PR head, green
on master).
… surface

The four committed storage-render snapshots are byte digests of the
forwarded wire bodies; the always-on retire/restore/rewrite tool
schemas change every body (+2685B on the chat wire per turn), so the
digest/bytes fields were refreshed via
scripts/update-storage-render-goldens.ts. Structural fields (turns,
summary placement) are unchanged. Fixes golden-storage-render +
storage-engine-proofs (8 tests, 4 wires).
…y growth

The always-on retire/restore/rewrite tool schemas add ~0.66-0.7k
est-tokens (~2.6KB) to every forwarded body. Several e2e scenarios are
deliberately calibrated a few hundred tokens under their trigger
boundaries (the repo's established pattern when the wire surface
shifts: ranxianglei#2750 wording change, ranxianglei#3298 fold-boundary retune, ranxianglei#1933
bill-rate retune), and the constant body growth pushed them over:

- e2e-relay-5xx-selfheal: tool_result 12000 -> 11700 chars keeps the
  payload at the intended ~97% of the 32k window.
- issue1933 F2: 1.4x/15x4000 -> 1.5x/14x4000 keeps the billed total at
  exactly 21000 (the scenario's +1k-above-target) while the estimate
  lands ~1.5k under the 20k trigger.
- ranxianglei#663: repeat(235) -> repeat(220) keeps the post-fold forward at 4
  messages (the mock only misclassifies exactly-1-message bodies).
- e2e-google-compress: clamp the mock's fold endId to refIds[max(len-6,4)]
  — with the +2.6KB body the 24KB demand gate now opens at 12 refs, and
  the len-10 tail then degenerates to a single-message range
  (463 chars < minCompressRange, hard refusal). The len-6 clamp also
  fixes a steady-state ratchet each fold out-earned by ~500B, so the
  upstream bodies stay bounded (client 65.7KB vs peak 31.5KB, ratio
  2.09, assertion requires >2).
- ranxianglei#280: repeat(235) -> repeat(155) — the overflow-relaxed preflight fold
  now eats 3 more messages before the hard protection floor; 155 (the
  measured pass window is 100..215) keeps MARKER_8..11 in the forward.
- ranxianglei#1843: mock bill 8000/6617 -> 8657/7274 (a real encoder bills the
  +657-tok schema text too) so the learned per-image cost lands at
  1813.5, inside the (1500, 5000) sanity bound.
- preflight-hold ranxianglei#568: repeat(235) -> repeat(220) (same family as ranxianglei#663).
- issue2672: idx8 2562 -> 207 chars (it is BLOCKED — no compressible
  slot — so its size never touches the walk geometry) and tail 358 ->
  294 chars (74 tok each keeps the 1000-tok recent-zone walk stopped at
  idx9 exactly as on master: 5x74+5+675 = 1050 >= 1000). Restores the
  pre-ranxianglei#5765 post-fold payload (~7.6k tok vs the 8k window).
- cache-proof host-compaction: seed FILLER 6 -> 5.5KB/turn — the
  +2.6KB schemas pushed the second-phase seed body one body earlier past
  the 64KB demand threshold (66.9KB vs 65.5KB at body ranxianglei#11), landing the
  seed fold one turn ahead of the ranxianglei#2695 geometry the zombie-settle
  bound (9) and the rearm-shrink pin were measured against; 5.5KB
  restores the master-aligned fold turns (verified via PROOF_DUMP:
  seed folds at 8->9 and 12->13 on both, rearm at 21->22).

All 15 affected files green (156/156), matching the 27 deterministic
CI failures 1:1; the identical suite is 155/0 on master.
…nxianglei#5765)

The ranxianglei#5924 architecture-docs guard (landed on master via the branch sync)
requires every tracked src/ file in reference/architecture.md; the new block-ops
host executor module was added by this PR without its map row, so the guard
failed on the merged head. One-line registration next to the acp-* cluster;
guard passes locally (320 src files covered, no ghosts).
…+ tag-stripped body (ranxianglei#6160)

The scripted queue key was conv|sourceText.slice(0,64) over the RAW last-user
text, whose ACP render tag is volatile by design: the post-fold ledger
alignment (ranxianglei#5608-D defers restampRefsToLedger to the next successful fold)
renumbers old refs (m00001 -> m00015, refs-never-reused) and raises the
ranxianglei#5608-C time-attribute high-water mark, so the very next request re-renders
the same message with time= dropped. The 64-char key slice contains the tag,
so one scripted turn split into two queues the instant a fold landed and the
directive queue replayed from scratch — the ranxianglei#2791 tests' deterministic
"compress must be CALLED exactly once (got 2)" on local node-25 and the
intermittent red on CI (same SHA red and green across runs; the alignment
no-ops when the unified store has no visible rows yet at the post-fold
scheduleSave).

The key is now conv|<source ref>|<stripAcps(source).slice(0,64)>: the ref
names the message occurrence (stable for the turn; a remint voids the script
anyway) and keeps byte-identical prompts distinct (switch/back both send
请调用probe_tool); stripAcps (existing helper) drops the volatile wrapper.
One site (answerFor) covers all six suites sharing the fake.

Verified locally (pi-stable 0.83.6): both ranxianglei#2791 tests green — the fold run's
queue continues through the fold (probe -> compress -> acp_status, one call);
full pass over native-pi/dsh/opencode/delegate-notify/subagent-sessions
(the two remaining local failures reproduce identically on the unmodified
fake: pre-existing local-environment artifacts).

Closes ranxianglei#6160
…e-compress-at-fold-point

[PR] remove: the experimental compressAtFoldPoint feature — fold-point relocation (v1 ranxianglei#5471 + unreleased v2 ranxianglei#5949), ranxianglei#6132
…ght + role cards)

The sessions' handoff (交接文档) tab rendered every message body as
escaped plain text: markdown in assistant replies stayed literal, code
fences showed raw backticks, and tool input/output was one giant
unhighlighted pre with no copy button.

- new src/web/mdrender.ts: single escape-first Markdown + hand-rolled
  syntax-highlight core (json/diff/js/ts/py/sh lexers, GitHub light+dark
  palettes, unknown languages degrade to escaped text, zero runtime
  deps). Inlined into the browser IIFE via .toString() exactly like the
  JSONC helpers; tests/mdrender.test.ts pins behavior plus the
  self-contained inlining contract (esbuild-minified .toString() must
  run in a bare scope, mirroring the tsup pipeline).
- web/markdown.ts becomes a thin wrapper over the same core, so the
  server-rendered handoff fallback and blog articles gain highlighting
  too.
- client.ts renderHandoffMd: role cards (user/assistant/tool left border
  + badge), markdown-rendered bodies incl. thinking chains (collapsed by
  default), structured tool cards (input/output label, language detection
  json/diff, per-block copy button, JSON pretty-print, open when <= 20
  lines), document meta (title/session id/token stats) rendered as a
  key/value box instead of being skipped.
- styles.ts: card/token CSS vars in light + both dark palettes (ranxianglei#2559
  verbatim-equality kept); i18n: det.io_in/det.io_out.
- tests/web-handoff-roles.test.ts adapted to the new markup; the ranxianglei#2065
  invariant (in-body headings never become role dividers) is preserved.

CHANGELOG + docs/architecture/webui(.zh-CN).md + reference/architecture.md
file map updated (architecture-docs lane green).
…ei#5984)

dsh-live-compact is a competing compaction engine that takes over dsh's
stock compaction row (its bundled cordis.patch.yml disables
compaction-basic and registers its own engine row; its preset takeover
renames the preset's compaction row to itself), so bili's load-time
compaction-disable overlay (ranxianglei#3354), which recognizes only the stock row
names, is structurally blind to it. Its checkpoints can land outside
bili's knowledge and destroy the compression substrate - the ranxianglei#5984
session.

- scanDsh: dsh-live-compact entries (bare name / npm spec / bounded path
  segment, isDshLiveCompactEntry mirroring isOpencodeAcpEntry's
  discipline) are now a deterministic "known" finding (knownId
  "dsh-live-compact") instead of a keyword [suspected] one, so the
  conflict ledger / acp_status / web banner surface it as a confirmed
  conflict; lookalikes stay at most keyword-suspected
- announcements/remote.json: web notice (ranxianglei#5548) for dsh profiles with
  the plugin, with the removal recipe (dsh plugin remove
  dsh-live-compact) and a CTA to ranxianglei#5984
- docs: known-conflicts list updated (configuration.md / zh-CN)
- tests: 3 new cases + detail-shape / not-benign / not-sibling /
  not-display-only assertions
…ts own section

The client renderer only recognized ### user/assistant/tool as block
dividers (ranxianglei#2065), so the export.ts ranxianglei#845 appendix ('## Compressed block
summaries' + one '### Block bN' entry per active block) landed inside
the last assistant card — on a session with 72 active blocks that was
~700 lines swallowed into one card. '## ' headings after the
Conversation heading now close the role cards and open a section;
'### Block bN — topic' entries render as small cards (mono block id +
topic + tier/token meta line + Markdown body). ranxianglei#2065 invariant intact:
in-body '### 7.1 …' lines are still content.
….blockOps (default off)

Owner ruling (review of this PR): acp_retire / acp_restore / acp_rewrite
must sit behind ONE switch, default OFF, injected only when ON — the
always-on surface costs ~0.7k estimated tokens per request on every
session of an enabled proxy.

Follows the absorb opt-in pattern end to end:
- kernel: definitions + name consts stay exported, but the 3 tools leave
  the default arrays (ACP_TOOLS_{OPENAI,ANTHROPIC,RESPONSES,GOOGLE},
  ACP_READONLY_TOOLS_RESPONSES, ACP_TOOL_NAMES); they stay in
  ACP_MUTATING_TOOLS (dispatch must drive the compress loop when enabled).
  New BLOCK_OP_TOOL_NAMES set + buildBlockOpsSystemPrompt(lane) section
  builder; the default prompts are back to "four context-management
  tools". New Config.blockOps { enabled } (off by default); absorb/ccr
  tool-name predicates include the block ops.
- host: compress.blockOps config (3-level merge like absorb);
  blockOpsEnabled / effectiveBlockOpsConfig / storeEffectiveBlockOps
  (per-session, plugin tool API parity); isProxyToolFor, the proxy-lane
  inject* extras, the responses hybrid lane, the round-2 cache seam, the
  plugin manifest + tool API, the MCP dialect note and the system prompt
  all inject/advertise the 3 only while enabled; the MCP note, prompt
  and per-session metadata stay byte-identical to pre-ranxianglei#5765 while off
  (zero footprint: no metadata key written while off — the storage-render
  goldens match master byte-for-byte).
- dispatch: enabled calls run the kernel ops exactly as before; a stray
  call while disabled gets a model-facing "not enabled on this bili proxy
  (compress.blockOps.enabled is not true)" receipt and never touches state.
- wire-contract goldens revert to the master shapes (the default surface
  is unchanged), and this commit supersedes the three earlier fix commits
  on this branch (test-pin updates + storage-render regen + payload
  retunes): with the default surface back to the master shape, those
  test files return to the master versions and the 27 red CI tests are
  green again without any calibration changes.
- docs: compress.blockOps in configuration.md/.zh-CN.md + website seed;
  kernel 0.0.194.

Verified: kernel 1656/1656; host targeted 201/201 (the 15 previously
red CI files + block-ops/snapshot/wire-contract), 348/348 + 63/63
surface/config invariant files; typecheck + build green.
…el 0.0.195)

Conflicts: kernel version files (master 0.0.193 from the ranxianglei#6132 fold-point
removal + this branch's 0.0.194 -> 0.0.195) and one relay.ts import line
(master dropped relocateCompressCarriers; this branch adds
buildBlockOpsSystemPrompt — the resolved import carries only the latter;
no fold-point call sites survive in the merged relay).

Re-verified on the merged tree: kernel 1631/1631, host targeted 201/201 +
surface/config 374/374, typecheck + build green.
… mcp-note split

unused-exports guard: MCP_DIALECT_NOTE_BLOCK_OPS and mcpDialectNoteText
are consumed only inside compress-tool.ts (withMcpDialectNote) — make them
module-local.
… gate

- lock-sync: the second master merge auto-merged the root package-lock
  with the pre-merge kernel version (0.0.193); regenerate it for 0.0.195
  (npm install --package-lock-only).
- lane-guard: src/server/side-request.ts (BILI_TOOL_NAMES += the kernel-
  fixed block-op names) is side-lane logic — pin it in the dedicated
  side-lane suite: tests/side-request-isolation.test.ts now asserts the
  bili-owned set covers ACP_TOOL_NAMES + block ops + absorb/rule/retrieve.
…ive-compact-known-conflict

[PR] thirdparty-scan: flag dsh-live-compact as a known conflict (ranxianglei#5984)
…for pruned leaf messages

Two fixes for the session-detail 压缩结构 tab (ranxianglei#3917 tree):

1. Node titles flickered away on select/poll redraw because they were
   native SVG <title> elements. Replaced with a persistent in-page
   floating tip (.g-tip): follows the pointer, shows block id/tier/
   message span/full topic/tokens/message count, and survives node
   selection and the 5s live-tree redraws (re-anchored after redraw).
   The <g> keeps an aria-label for accessibility.

2. Clicking a leaf block listed its message refs (mNNNNN) but every
   row said 'no longer in the session' — host native compaction prunes
   the messages from context, so the bytes were nowhere to look. They
   ARE on disk though: collectBlockContent froze each original message
   ([role] / [role • tool] header + text, pi-host user messages carry
   their ACP ref marker) into the block's full snapshot. New
   frozen-snapshot hydration: for a LEAF block whose full snapshot
   parsed fragment count equals its direct-message ref count — with
   every ACP marker cross-checking its own ledger position — the rows
   re-align by position (preview + click-to-open via /message?ref=).
   Count mismatch, marker conflict, or non-leaf spans (children mixed
   into the snapshot, position meaningless) abort the whole block and
   keep the honest 'missing' marker — never serve shifted bytes.

Measured on a 253-block/9771-ref session: 5072 (52%) leaf messages
recovered their real previews and text; the rest stay honest misses.

Regressions: tests/web-blocks-tree.test.ts (2 new cases: positional
hydration + the three refusal arms), web-sessions/web-handoff-roles/
mdrender and the web sandbox suites stay green; typecheck +
check-dist-external (zero runtime deps) pass.
…emon

[PR] feat: compress-block retirement (acp_retire/acp_restore) + external summary rewrite (acp_rewrite) + export ledger
…snapshot hydration

- compression tree: hovering a node now widens its topic line in place
  (~20 chars) and the .g-tip overlay follows the pointer; a 5s live-tree
  tick previously redrew the graph with lean nodes and degraded every
  topic back to a bare block id — ticks now merge onto the rich detail
  blocks by blockId before redrawing (structure/active/spans win)
- snapshot hydration gains a restamp-proof ARITHMETIC anchor: the
  marker's frozen number minus the block's frozen span start equals the
  snapshot position. Cold-load restamping (ranxianglei#5196 parked bands take fresh
  numbers) renumbers whole blocks, which invalidated the identity proof
  (marker number no longer in the ref list) and left entire renumbered
  blocks (b27: 127/127 missing) at honest-misses although their frozen
  snapshot held every byte. Identity still wins where the marker number
  is current; a marker proving neither aborts the block; unbalanced
  segments stay missing. Measured on the 253-block/9771-ref session:
  7789 refs (80%) now hydrate (was 5072/52%), b27 fully recovered
- tests/web-blocks-tree.test.ts: +3 hydration cases (restamped block
  recovered / arithmetic beats a coincidental ref collision /
  unanchorable marker rejects the whole block incl. click-to-open);
  marker-shift fixture span pinned so the case stays legacy-anchored
…-write-lifecycle

[PR] fix: await unified snapshot writes and reset each debounce batch
…opic-trailing-system-tails

[PR] fix: insert Anthropic ephemeral tails before trailing client systems (ranxianglei#5432)
…work

[PR] Web Panel: Handoff Documentation and Collapsed Tree Diagram Rendering Upgrade (eWork-Level Rendering, Appendix Sectionin | Web 面板:交接文档与压缩树图渲染升级(eWork 级渲染、附录分节、冻结快照水合)
…able-message-guards.ts and issue5142-expected-rebase.test.ts)

The two conflicts were in doc comments and a test assertion that both
sides had updated independently:
- master added image-handle/image-offload classifiers (12 total, 14 markers)
- PR added workspace-instructions revision markers (4 total for that family)
- merged: 12 classifiers, 17 markers

Closes none — this is a CI sync commit for PR ranxianglei#4922.
@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

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

冲突已解决,PR 恢复可合并状态。

做了什么:把 master 最新 tip(c26c31ace4)合入 PR 分支,解决了 2 个文件的内容冲突:

  1. src/durable-message-guards.ts — dshCarrierRawMarkers 的 doc comment。master 侧加了 image-handle / image-offload 两个分类器(12 分类器 / 14 markers),PR 侧加了 workspace-instructions 三种修订 marker(4 markers for that family)。合并后:12 分类器 / 17 markers,comment 已更新为准确计数。
  2. tests/issue5142-expected-rebase.test.ts — lockstep 断言的 lower bound 从 14/15 统一为 17(与合并后的实际 marker 数一致)。

验证:typecheck 干净;issue2447 + issue5142 共 18 个测试全绿。

当前状态:head 1911566bce,mergeable=true,merge_state=blocked(等 CI),CI 已跑完:17 success + 13 skipped,0 红。PR 现在干净可合,等人工合并。

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

bug Something isn't working dsh p2 scope/kernel

Projects

None yet