Repository navigation
Conversation
d6ea911 to
0d6578a
Compare
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复。 open+closed 全量检索(按 标签:bug + dsh + scope/kernel + p2(缺陷类;dsh 车道;src 侧折叠保护判定属压缩决策面;条件触发、有绕行,p2)。 本条为外部贡献 PR(无 agent 标记),正在做完整审核:拉分支读 diff、核 CI、定向跑相关测试。结论稍后按八问体例补在这条线程。 |
(ai: vllm-qwen/qwen3.8-27b) 审核完成(外部贡献 PR,独立审过 diff、机制与 CI)。结论:可合。 ① 方向关:修对了层。缺陷的产地是分类器—— ② 回归审查(逐项过既有行为):
③ 代码审查(主关):关键机制我独立核过源码而非只信 PR 描述—— ④ 测试有效性:新增 e2e 折叠场景走完整管道( ⑤/⑤b diff 整洁度与代码质量(外部提交从严):3 个文件全部与目的相关,无空行/生成物/锁文件噪音,无 version 触碰;命名达意( 八问:
收工判定:代码与行为均可合入。本 PR 改变默认保护行为面(§7.4「fallback/default 变化属产品决策」),不进 auto-merge,请人工合并:#4922 。合并后 #4921 由 PR 的 |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) |
… 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
…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.
[bot] (ai: vllm-qwen/qwen3.8-27b) 冲突已解决,PR 恢复可合并状态。 做了什么:把 master 最新 tip(
验证:typecheck 干净; 当前状态:head |
这是啥问题
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.tsdshWorkspaceInstructionsPath:只匹配大写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(),把三种修订投射到独立 identitydurable/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"),注入没落地直接红。durable/agents-md;基线正文引用Instructions from:仍认作基线;修订正文引用它仍认作修订(顺序正确性)。compress让区间同时盖住两者 → 修订正文always run lint AND typecheck存活(它别处没有);基线同时存活(独立 identity,没被修订挤下去);区间里的填充消息确实被吃掉(证明折叠真发生了,不是保护把整个范围否决了)。src/durable-message-guards.ts重跑,issue2447的 5 个用例红 2 个(单测 + 新折叠场景);恢复后 5/5 绿。确认这两个测试真钉住了行为,不是无论怎么实现都会通过的摆设。issue24475/5;issue2446/issue2481/issue276619/19;durable-tool-identity9/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)