Skip to content

Visibility barrier for sidebar badge freshness (P7) #581

Description

@lis186

摘要

Sidebar badge 的更新依賴 herdr 事件觸發讀取 index.ndjson,但 ccxray 的 appendIndex 是非同步的——broadcast SSE 和 herdr 的 idle 偵測都可能在 index commit 之前發生。目前用 250ms 固定 delay bridge,但這是時間猜測而非因果保證。

根因(三方獨立分析收斂)

Fable(對抗式)、Codex GPT 5.6 Sol(獨立提案)、Sonnet(實作式)各自讀了 codebase 後收斂在同一個診斷:

這是一個因果可見性問題(causal visibility problem),不是延遲選擇問題。

forward.js:780 的順序是:結束 client response → broadcast SSE → 非同步 appendIndex()。herdr 偵測到 idle 轉換時 appendIndex 可能還沒完成。badge 讀到的是上一個 turn 的 index snapshot。

Codex 額外發現:herdr 每個 prompt 觸發兩次事件(idle→working + working→idle),spawn 兩個獨立的 refresh-badges.js。增加 delay/retry 可能讓舊 process 在新的之後完成並覆寫更新的 badge。

設計(P7 visibility barrier + per-pane cursor)

Producer(server 端):

async function persistVisible(entry, canonical = entry) {
  await config.storage.appendIndex(buildIndexLine(entry) + '\n');
  sessionIdx.updateFromEntry(canonical);
  visible.advance({ sessionId: entry.sessionId, agentId: entry.agentId });
}

Consumer(plugin 端):

GET /_api/telemetry/visible?sessionId=X&agentIds=Y&after=<cursor>&timeoutMs=1500

Plugin 只在 terminal 事件(idle/done/blocked)才 long-poll,non-terminal(working/detected)直接讀現有資料。每個 pane 記住上次成功寫入的 cursor,超時保留舊 badge 不覆寫。

影響範圍

改:

  • server/forward.js ×3 push sites(appendIndex → persistVisible)
  • server/ws-proxy.js ×1 push site
  • server/importer.js ×1 push site
  • server/routes/api.js +1 new endpoint
  • plugins/herdr/bin/refresh-badges.js(long-poll 取代 waitForTelemetry)

不改(零影響):

  • server/sse-broadcast.js — broadcast 順序不變
  • public/*.js — dashboard 完全不碰
  • server/restore.js — 啟動重建路徑不變
  • 既有的 cold-load REST API 不變

估計 ~80-100 行改動。

被排除的方案

方案 排除理由
SSE daemon(P2) herdr plugin 無 persistent process 原語;SSE broadcast 在 appendIndex 之前所以重現 race
Proxy push metadata(P1) proxy 不持有 pane ID(只有 launch token);badge 需要 session grouping/dedup/confidence fold,不是單一 entry 能算的
Custom herdr event(P3) herdr 無此 API
Adaptive delay(P6) proxy 存在 ≠ 這個 pane 用了它 ≠ 這個 request 完成了 ≠ append 完成了

Importer 路徑的誠實答案

三方一致:badge 側無法讓 importer 路徑即時。正確做法是讓使用者永遠走 ccxray claude(proxy 路徑),importer 是 best-effort fallback。badge 的 stale 偵測 + requestImport 是既有的誠實降級。

驗證建議

  • fail-on-old:舊碼的 250ms delay 在 waitForTelemetry 裡,新碼用 long-poll 取代它
  • 測試:mock /_api/telemetry/visible endpoint,斷言 terminal event 才等、non-terminal 直接讀
  • live e2e:proxy-launched pane 的 badge 在 turn 完成後 <100ms 內更新(不再依賴 250ms 猜測)

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions