Repository navigation
fix: strip model-emitted bili internal artifacts from prose streams - #1645
ranxianglei wants to merge 2 commits into
Conversation
…1635) The kernel's chain checkpoint tag (<bili-chain .../>) and the internal summary artifact head lines ([Compressed conversation section] / [bili] context summary after compaction:) are injectable control data, but a model that saw them once could echo them back in its own output and nothing stripped them on egress. In long sessions this became an echo cascade: metadata flooded the client TUI mid-answer and the conversation stalled (#1635). - tag-echo-filter: new createBiliChainTagFilter (stateful, chunk-safe; over-long or unclosed openings released as prose per #644) and createInternalArtifactLineFilter (column-0 head lines only — indented or quoted occurrences are user content); composeStreamFilters is now variadic; stripAcpTags / containsMarkerLineText / mayStartMarkerLine cover the same shapes on the non-streaming and gate paths - all four loop adapters + plugin passthrough sites (chat/responses/arg-streams) compose the new filters with per-drop logging - codex-compact: export COMPACT_ECHO_PREFIX so the filter set derives from the single source of truth instead of a second literal - server: forward log line now carries [session-id]; both outbound append sites emit structured [chain] outbound-append audit lines (kind=chain-stamp debug per stamped egress; kind=compaction-summary-replace info), making every bili injection attributable to a session Tool-call arguments stay byte-exact verbatim (#1039 invariant); <summary>/<analysis> prose is preserved.
22 tests: real renderChainCheckpoint tags stripped non-streaming and char-by-char/chunked streaming; over-long unclosed openings released as prose; truncated echoes dropped at flush (lone '<' kept); column-0 head line swallowing vs indented/mid-line preservation; JSON-escaped gate detection; variadic composition pipeline-end stats + 2-filter backward compat; tool arguments untouched (#1039). Also re-pin three chain-checkpoint 'no [chain] log' assertions to expect exactly the new kind=chain-stamp egress audit line — their intent (no unexpected chain processing on plain/stale requests) is preserved.
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.1592-pr.1645.d6a5b36This exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr1645.tgz
npm install -g packageInstall instructions are refreshed on each push. |
[bot] 🏷 PR review — #1645 (fix #1635) ① 原 issue / 方向关 — 层次正确、范围正确。 ② 回归审查 — 触碰的既有行为清单及逐项结论:
③ 代码审查。 两个状态机均 chunk-safe:chain-tag 过滤器持有尾部有界(≤ CAP = 512 + open 长度,与 ④ 测试有效性。 断言有牙:用真实 ⑤ diff 整洁度。 11 文件 / +552 −35,两个聚焦 commit,无跑题文件、无锁文件/生成物噪音。 Pre-flight(本地在 head 结论:mergeable(待 ubuntu-22 job 重跑转绿)。合并操作按仓库规则留给人工。 一句话中文摘要:本 PR 修复模型把 bili 内部产物( |
Fixes #1635
Problem
Bili appends internal control data to outbound bodies (the chain checkpoint tag
<bili-chain .../>on every processed request; summary handoff lines on the Responses wire). Once any of those strings enter the model's context — by injection, by history replay, or even by the user quoting them while debugging — the model can echo them back in its own output, and nothing stripped them on egress. In long sessions this becomes a self-reinforcing cascade: metadata floods the client TUI mid-answer and the conversation stalls.Attribution (evidence-graded, per the thread agreement)
<bili-chain …/>stampOutbound, all wires; openai wire appends it as a trailing tag-only user message)[bili] context summary after compaction:replaceBiliCompactionItems(Responses wire); reporter's lane also has a second candidate origin — the model quoting the literal from dist bundle / debug report text it had read[Compressed conversation section]SUMMARY_HEADER, visible in rendered blocks / summary carrier → imitation<summary>/<analysis>proseMachine-wide log counts from the report: 2304
/v1/responsesrequests coexisting with ~9400/v1/chat/completionsacross two log files — multiple sessions share one log, and before this change no log line could attribute a forward to a session, so per-session attribution was impossible. That gap is closed below.Changes
Stripping (the fix):
src/loop/tag-echo-filter.ts: newcreateBiliChainTagFilter(stateful, chunk-safe; over-long or unclosed openings released as prose per the [bug] #361 residual, form 3: turn whose entire text is a render-tag echo strips to an EMPTY message — agent records a marker-only turn, stalls until manual nudge (v0.1.92, post-#365) #644 rule) andcreateInternalArtifactLineFilter(swallows column-0 head lines only — indented or quoted occurrences are user content).composeStreamFiltersis now variadic;stripAcpTags/containsMarkerLineText/mayStartMarkerLinecover the same shapes on the non-streaming and fast-skip gate paths.[bili-chain-echo]/[artifact-echo]).src/codex-compact.ts: exportsCOMPACT_ECHO_PREFIXso the filter set derives from the single source of truth instead of a second literal (guard and carrier can no longer drift apart).Auditability (requested in-thread):
forward GET → url [sid]— the missing correlation key the reporter needed to attribute forwards to sessions post-hoc.kind=chain-stamp wire=… role=user shape=… len=…(debug, per stamped egress) andkind=compaction-summary-replace … replaced=N dropped=N(info). Every injection bili makes is now attributable to a session.Invariants preserved: tool-call arguments stay byte-exact verbatim (#1039 — pinned by test);
<summary>/<analysis>prose is preserved (not bili-owned, would corrupt legitimate content); no request-body / wire-shape changes (response streams only); both compression modes covered (proxy mode via the adapters, plugin mode via the plugin-channel filters).Behavior changes (old → new)
<bili-chain .../>tags and column-0[Compressed conversation section]/[bili] context summary after compaction:lines in prose streams (content/reasoning/summary text, streaming + non-streaming, all four wires) are now removed before reaching the client/agent renderer. Previously they passed through verbatim. This is the intended fix for 【bug】压缩元数据被当作正文输出:模型复述摘要而非回答用户,长会话随之中断 #1635.forward GET → https://…→forward GET → https://… [session-id](non-session forwards log-). Log format change.[sid] replaced N echoed bili compaction item(s) with summary handoff message(s)(plus optional, dropped M legacy marker item(s)suffix) →[sid] [chain] outbound-append kind=compaction-summary-replace wire=responses role=user shape=message replaced=N dropped=M. Same level (info); structured format for grep-ability.[sid] [chain] outbound-append kind=chain-stamp wire=<wire> role=user shape=message|merged-part len=N. New output at debug level (this is why three chain-checkpoint tests' exact-count assertions were re-pinned — see Tests).config surface
None changed. No new config fields, env vars, or CLI flags.
Tests
tests/issue1635-bili-artifact-echo.test.ts: 22 regression tests — realrenderChainCheckpointtags stripped non-streaming and char-by-char/chunked streaming; over-long/unclosed openings released as prose; truncated echoes dropped at flush while a lone<survives; column-0 vs indented/mid-line head handling; JSON-escaped\u003cbili-chaingate detection; variadic composition stats + 2-filter backward compat; tool arguments untouched ([bug] 插件透传会剥掉工具调用参数里的 render tag(arguments 流挂了 tag-echo 过滤器) #1039).[chain]log lines exactly: plain-request tests now expect exactly the new debugkind=chain-stampline; stale-unmatched expects verdict line + stamp line. Protective intent unchanged (any unexpected chain processing still fails the count).Related: #1641 (lenient
isCodexClientUA substring fallback — separate trade-off-narrowing item, tracked there, not included here).