Repository navigation
[PR] Unified Announcement Panel: Built-in Registry + Local JSON Files + Remote Dynamic Sources (Done in One Pass) | 统一公告面:内置注册表 + 本地 JSON 文件 + 远程动态源(一次做完) - #5575
Conversation
…er loop (#5548 P1) Every built-in web notice now lives as ONE entry in src/notices.ts (id, tone, i18n key set, server-side active predicate). The web client renders whatever the overview reports through a single generic loop over #notices-banner-region; the per-notice builders/banners/wiring added one by one in #5079, #5469 and the retired #5547 are gone. Adding a 4th notice is a table row + its i18n keys + its store allowlist id — no admin/client/page edits. - new src/notices.ts: NoticeEntry registry + never-throwing visibleNotices() - admin.ts: overview gains notices[] (replaces the two bespoke payload fields); new GET /__bili/notices serves the same array - client.ts: bili_pckNoticeBanner/bili_wmNoticeBanner collapse into one bili_noticeBanner({tone,on,desc,hint,dismiss}); one loop wires .notice-dismiss buttons against POST /__bili/notices/dismiss - page.ts: two static banner divs -> single #notices-banner-region - i18n: per-notice dismissed toasts merge into one notices.dismissed key (zh/en); web-i18n dead-key guard learns to scan src/notices.ts (registry strings count as references) - tests: new tests/notice-registry.test.ts (registry invariants, both locales, fresh-install visibility order, dismiss-hides-only-one) Behavior is deliberately unchanged (P1 of the approved #5548 design: P2 adds local JSON announcements + full schema, P3 remote GitHub raw + CI gate).
…#5548 P1, unused-exports gate)
… get a banner (#5548 P2) Owners can now post notices by writing one JSON file per announcement into ~/.config/billion-context/announcements/*.json (the dir follows BILI_CONFIG_FILE relocation). The web panel renders them through the same single banner loop as built-ins (P1 registry); no code change needed ever again for owner-side announcements. Schema (whitelist copy, extra fields dropped not fatal; caps title<=120 / desc<=600 / hint<=300, file<=64KB, bad file skips itself only): - id [a-z0-9-]{1,64} + kind 7-class (new-feature/upgrade-advisory/warning/ suggestion/incident/deprecation/other); tone auto-derived (ok/warn/info) or overridden; priority defaults per kind (incident 90 .. other 30) - versions: semver-ish AND list (exact, >=a <b, ^, ~, [lo hi] || [x y]); this build must match every element; unparseable build version hides versioned records (never over-nag) - percent 0-100 grey bucketing: sha256(id|install-id) last two bytes %100, salted per announcement so buckets do not correlate with feature rollouts - since/until ISO windows; publishedAt metadata - dismiss modes: persistent (store key id@sha256(canonical record)[:16] so edited content re-nags exactly once), instance (process memory only, restart re-shows), always (no close button rendered; store writes accepted but ignored — immune by construction) - ctaLabel+ctaUrl pair (http(s) only, <=200 chars), rendered as target=_blank rel=noopener anchor Merge/sort contract: local (B) shadows builtin (A) on equal id — tested that a shadowed id renders exactly once as source=local — then priority desc with stable order (builtins first among ties). mergedNotices() feeds both /__bili/notices and the overview field; the dismissal endpoint routes by the record's declared mode before touching persistent state. Store (src/pck-notice.ts): hashed keys id@16hex now admissible alongside the two builtin ids; write is bounded to 200 keys with builtin ids protected from trimming (regression-tested at 210+ hash keys). Client: bili_pickLoc picks zh-CN/en/any-value off the wire locale objects with the t() fallback chain; banner builder gains optional CTA anchor and hide-dismiss (all content through escapeHtml — zero new XSS surface). i18n: one shared notices.dismiss_btn label added (zh/en parity checked by suite). Evidence: typecheck clean, build clean, unused-exports gate clean; 51/51 targeted green (new tests/local-announcements.test.ts = 10 tests, every gate asserted both directions per #3617; plus notice-registry, wm-fastpath-intro-notice, issue5079-pck-default-off incl. live-proxy e2e, web-i18n dead-key guard, web-update-banner, web-relay-warn).
…S merge, docs and CI lint (#5548) Remote announcements: master announcements/remote.json polled on the shared ~3-minute watcher cadence (same throttle/dispatcher machinery as advisories), last-known-good snapshot survives restarts and outages, malformed pushes are rejected, installs behind firewalls override the URL via announcementsUrl / BILI_ANNOUNCEMENTS_URL. Merge order fixed so equal ids shadow builtin < remote < local (a test caught the missing local-over-remote leg). Docs: config reference seeds regenerated (en+zh+site), README en/zh sections, AGENTS KDD#16, plus a CI lint job guarding the official feed document.
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.8039-pr.5575.e1a309aThis exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr5575.tgz
npm install -g packageInstall instructions are refreshed on each push. |
…ile/feed notices (#5548) Live smoke caught it: the persistent path stored the bare id, which the store allowlist rejects ('unknown notice'), so every local/remote dismissal 400'd while built-ins kept working. Handler now resolves the visible record (local shadows remote) and stores id@contentHash16 — exactly the key the visibility gate filters on; built-in ids fall through unchanged. Unit pin: bare id rejected, hash key hides, edited content re-nags once.
…sUrl (#5548 review fix) Owner review: the bare 'announcements' prefix would collide with the future npm release-notes channel (releaseNotesUrl / notes prefix); scope it to the remote feed explicitly. Env var BILI_ANNOUNCEMENTS_URL unchanged. Also registers BILI_ANNOUNCEMENTS_URL in LIFECYCLE.md §2.7 — its absence failed lifecycle-ledger-guard on this branch.
The content-addressed dismiss fix (77c0e05) unit-pins the resolver helper and store directly, so a regression of the endpoint's own glue — passing the resolved value into dismissUpgradeNotice — would slip past that pin. Drive the real admin route (mock req/res, loopback-gated) across persistent LOCAL, built-in, unknown, and persistent REMOTE-feed cases; a revert to bare-id dismissal fails here instead of in production. Reviewer supplement for PR #5575.
Rename to follow the repo issue-numbered test convention (an internal mirror number slipped in by mistake); the test pins the #5548 unified- notice dismissal route. No behavior change.
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复。 本 PR 是 #5548(统一公告面)的唯一实现,已替代并关闭 #5554/#5570;检索 open/closed 无其它条目在做同一件事。评审期间该分支上有两个并发提交已先行落地(缺陷修复 结论先行: 这是功能 PR(内置注册表 + 本地 JSON 文件 + 远程动态源,一次做完)。核心实现正确、fail-open 链路干净、diff 整洁、config-surface 按规范申报到位。唯一实质缺陷是「持久型本地/远程公告点『关闭』一直失败」——我独立定位到根因后确认你已在 结构化八问:
行为变更披露(规范强制):
两个非阻塞小观察(可不改,留你定夺): 判定:可合并 —— 等推送头 |
…mespace "notices") (#5548)
…preloads legacy (#5548)
The validator imports parseRemoteDoc from src/remote-notices.ts, whose module graph reaches acp-kernel through src modules; without a built kernel/dist the file:./kernel link resolves to a missing index.js and the job dies with ERR_MODULE_NOT_FOUND before any linting happens. Same prerequisite as the main test matrix (ci.yml static/test legs).
…ref in test header, eviction comment accuracy
ranxianglei
left a comment
There was a problem hiding this comment.
(ai: vllm-qwen/qwen3.8-27b)
(GitHub rejects the APPROVE event on the author's own PR, so this carries as a COMMENT review — the verdict below stands.)
Verdict: APPROVE — full end-to-end read of all 28 files (+1746/−107) incl. the five new test files; CI green on head (mirror cache: 30 checks, 0 failed); no kernel/ touch, no wire/model-traffic impact.
结构化八问
- 是什么问题:三个公告面各自生长(内置引导提示、升级提示、#5548 设计的远程 feed),各有各的渲染/关闭逻辑;此前拆分期还留下「本地文件只压内置、不压同 id 远程条目」的 merge bug,且关闭回执在统一存储激活时仍写 JSON 状态文件。本 PR 把它们收敛成一个循环:一套 schema、一个渲染器、一份回执存储。
- 什么原因导致的:每个面各长了一条 fetch/dismiss/merge 路径,而不是共享「什么该显示」的单一投影——shadowing bug 和回执双写都是这个分裂的直接后果。
- 影响范围与严重程度:仅 web UI banner 区 + loopback-only
/__biliadmin 路由;零模型流量、零 wire、零 kernel。属 UX 功能 + 存储卫生,无可用性影响。 - 怎么修复的:schema 只定义一次(
src/announcements.ts)三源逐字共用;mergedNotices()做 local > remote > builtin 影子 + 稳定优先级排序;回执每进程一次性从统一存储(kv namespacenotices)水合,遗留文件键恰好迁移一次;远程拉取走 ~3 分钟 watcher 节奏、fail-open,last-known-good snapshot + throttle 文件兜底;渲染对每个动态值(含 CTA href 属性)走 house-standard escapeHtml,CTA 锚点target=_blank rel=noopener noreferrer。 - 回归审查(diff 触碰的既有行为逐项清点):
- overview payload:
pckNotice{active}/wmNotice{active}对象移除,改为notices[]数组——有意变更,#5548 线程已披露;唯一消费者是仓内 web client,同一 diff 内同步更新(grep 复核:这两个字段无其他读取者)。 POST /__bili/notices/dismiss:可接受 id 从内置 id 扩到 内置 ∪ 内容寻址id@hash——严格超集,旧调用形状照旧工作(新 endpoint e2e 钉住)。upgrade-notices.json:统一存储实例上降级为 pre-hydration 读 fallback + 一次性导入源(键恰好迁移一次,notice-unified-store.test.ts 钉住);legacy-only 实例仍是唯一载体,纯文件路径保留(内存缓存永不接管)。既有关闭数据两条路都保得住。- pck/wm 提示的可见性谓词不变(同样的 routing 门 + 同样的 dismiss 查询),只是背后的存储搬了家。
- i18n:删 2 个死键、加 2 个共享键;dead-key 扫描扩到
src/notices.ts。 package.jsonversion 未动;无 lockfile churn;diff 无跑题文件。
- overview payload:
- 是否破坏缓存:不涉及——模型流量零接触;唯一持久化状态是关闭回执本身。
- 是否改了设计方向:没有——既有面板内的 UI 面收敛。配置面纪律合规:新键在 config-surface 段声明,CONFIGURATION.md en+zh、updates.yaml seed、LIFECYCLE.md 全镜像。
- 新配置:
remoteAnnouncementsUrl(默认本仓 masterannouncements/remote.json)+ envBILI_ANNOUNCEMENTS_URL(env > file > default,同 advisoryUrl 模式);可选<config dir>/announcements/*.json无需任何配置。纯增量,无迁移。
测试有效性
真测试、断言有牙:#3617 双向门(matcher 矩阵含负例行;cap 测试垫到 210 条断言 ≤200 且内置受保护;failure ladder 模拟重启;endpoint 测试驱动真实路由处理器;unified-store 测试钉死 BILI_STORAGE_UNIFIED=full 并证明回执跨模拟重启存活)。hermetic 临时目录、listen(0) 端口(#360)、无环境运气依赖。
非阻塞观察(不要求动作)
- legacy-only 安装(无统一 sidecar)在文件超限淘汰时按字母序而非 FIFO——除非该类实例累积 >198 个非内置哈希键否则不可达;真 FIFO 需要文件格式加时间戳,超出本 PR 范围。
- 渲染循环不设设计稿里的 ≤5 条头部上限(溢出列表方案未落地)——实际暴露面很小:feed 由 owner 维护且过 CI lint,本地文件是用户自有的。
- PR 正文仍写着改名前的配置键名
announcementsUrl(2f91f63 已改名remoteAnnouncementsUrl)——纯措辞,reviewer 不动 PR 正文。
已顺手修(仅注释/文档,零行为变化)
- README.md:539 残留旧键名
"announcementsUrl"→"remoteAnnouncementsUrl" - tests/issue5548-notice-dismiss-endpoint.test.ts:9 头注释引用了 PR #5577(那是另一个已关闭的 PR)→ 更正为 #5575
- src/pck-notice.ts 淘汰注释声称 oldest-first,代码实为字母序淘汰(flat 文件没有时间戳)——注释改回与代码事实一致
commit 70dd6cf
结论:可合并。 CI 绿、发现项全部就地修掉或有意识留档、未发现回归。
(ai: vllm-qwen/qwen3.8-27b) PR #5575(统一公告面)review 完成:结论 APPROVE——全量通读 28 个文件(+1746/−107)含 5 个新测试文件,CI 绿(head 94d711b,镜像缓存 30 checks 0 failed),无 kernel/wire/模型流量影响。GitHub 禁止作者账号 approve 自己的 PR,故以 COMMENT review 形式发布同一结论:#5575 (review) 。已顺手修 3 处注释/文档(README 残留旧配置键名、测试头注释误引 #5577、淘汰策略注释与代码事实不符),commit 70dd6cf 已推 PR 分支,docs-only,CI 重跑中。 |
|
(ai: vllm-qwen/qwen3.8-27b) 新 head |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.1018-master.c29b118 # this exact build
npm install -g billion-context@master # always the newest merged build
|
把「web 面板上到底能不能弹公告」这件事一次做完:内置公告统一注册表 + 本地 JSON 文件自动渲染 + 远程动态公告源,外加完整文档和 CI 校验。这是之前 #5554(P1)和 #5570(P2)的合集——那两个已关闭,全部改动以线性历史收进本 PR(master d244fff 之上仅 4 个提交)。
用户看到什么
announcements/*.json(默认~/.config/billion-context/announcements/)即渲染成横幅。记录支持中英双语、7 类 kind→颜色推导、npm 风格版本定向(>=a.b.c <x.y.z/^/~/[lo hi]/||列表)、percent灰度分桶(按公告 id 加盐,与业务灰度互不相关)、时间窗、按类型优先级排序、三种关闭模式(persistent=按内容寻址、改内容再弹一次 / instance=仅本进程 / always=无关闭按钮)、CTA 按钮(仅 http(s))。坏文件只废掉它自己。announcements/remote.json(CI 校验的官方文档,当前空列表),约每 3 分钟拉取;上一份有效公告的快照跨重启/断网存活;坏推送被拒绝并继续显示旧值;防火墙环境用"announcementsUrl"/BILI_ANNOUNCEMENTS_URL指中继镜像。整条链路 fail-open:拉不到/坏了只影响公告面,绝不影响模型流量。config surface(新增面,按 AGENTS 要求显式申报)
announcementsUrl(string,默认缺省 = 官方 GitHub raw 源)+ 环境变量BILI_ANNOUNCEMENTS_URL(env > file > 默认三级,走 loadOptions 既有机制)。advisoryUrl/releaseNotesUrl就是这个形状(URL 覆盖键 + BILI_ 前缀 env),完全复用既有模式,没有发明新配置形态;本地/远程公告本身不是配置面(内容 ≠ 配置),不需要额外键。验证
announcements-lint.yml:PR/master 触碰官方文档或校验器时跑scripts/validate-remote-announcements.ts。Closes #5548 的实现部分(设计稿在 issue 内评论);替代并关闭 #5554、#5570。