Skip to content

feat: v8 ctx-split swimlane — dashboard integration (#91)#124

Merged
lis186 merged 6 commits into
mainfrom
feat/v8-dashboard-integration
Jul 4, 2026
Merged

feat: v8 ctx-split swimlane — dashboard integration (#91)#124
lis186 merged 6 commits into
mainfrom
feat/v8-dashboard-integration

Conversation

@lis186

@lis186 lis186 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

繁體中文摘要

把 v8 ctx-split swimlane 設計(prototype 9.03/10)整合進正式 dashboard 的 workflow view,並依實際使用回饋完成六輪迭代:

  1. v8 編碼落地:turn bar 高度 = context window %,內部拆 cache read(teal)/ cache write(橘)/ input(紫)三色堆疊;8px cost 軌(>3× 中位數轉橘);4 條固定事件軌(Faults/Context/Mutations/Safety),collapsed 只顯示 Faults+Safety
  2. 可讀性:全 SVG 字級下限 9px(原 7–8px 實機不可讀);cache read 從 #58a6ff 改 teal #39c5cf——原色與 accent 藍完全同值,資料填色會與選取框線/色帶混淆
  3. Agent 身分上鏈:server 端 KNOWN_AGENTS 偵測結果(agentKey/agentLabel)現在跟著每個 entry 走(SSE/HTTP/WebSocket/index.ndjson/rebuild),舊資料由 restore 從 shared sys_ 檔反查 backfill
  4. Lane 分類改 agent 身分優先:session 中途換 model(如 /fast 切換、opus→fable)不再把主 agent 切成假 subagent lane(fix: lane inference misclassifies /fast model switch as subagent #113 的 1050-turn 誤判、157c0faa 的 287-turn 誤判都由此修復)
  5. Lane gutter 三行資訊塊:agent 名 / model·context window / system prompt 版本 chips(點 chip 跳到該版本第一個 turn;↗ 深連結到 System Prompt 分頁)
  6. 點 lane = 全範圍:選 lane 直接渲染最後一個 turn 的完整 Timeline detail(request 含整段對話 = 全範圍),不上 lock 視覺、無中間狀態

Codex CLI 二審四輪收斂(R1 3 findings → R4 PASS),詳見下方 Review trail。

Lane gutter (240px)

Before                          After
┌──────────────────┐            ┌──────────────────────┐
│ ▶ main           │            │ ▶ main               │  11px  ← agentLabel (sub lanes:
│                  │            │ fable-5 · 1000K      │  10px    "Explore", "General Purpose")
│                  │            │ sys v2.1.f92 v2.1.79a ↗  10px  ← per-coreHash chips, click =
│                  │            │                      │          jump to first turn of that
└──────────────────┘            └──────────────────────┘          version; ↗ = System Prompt page

Lane classification (agent-identity-first)

Before (model heuristic)                 After (agentKey from server)
session 157c0faa, opus→fable switch:     ┌─────────────────────────────────┐
┌─────────────────────────────────┐      │ main            ██████████ 287t │ ← opus+fable merged,
│ main (opus)     ██          32t │      │                                 │   dashed line at switch
│ Orchestrator    ░░██████████287t│ ←bug │ Explore         · ▂       41t   │
│ Explore         · ▂       41t   │      │ General Purpose ·· ▄▄     151t  │
│ General Purpose ·· ▄▄     151t  │      └─────────────────────────────────┘
└─────────────────────────────────┘      main = longest, on top; sub lanes
"Orchestrator" as a *sub* lane =         sort by first-turn time
the heuristic exposing its own bug

Classification: agentKey ∈ {orchestrator, sdk-agent, default} → main lane, regardless of model or isSubagent; other keys → agent-<key> lane named by agentLabel; entries without identity (old data, no system prompt) fall back to the previous model/ctx heuristics.

Agent identity data flow (new)

request ─→ registerPromptVersion ──→ agentKey/agentLabel/coreHash
              │  (KNOWN_AGENTS)          │
              │  fallback:               ├─→ entry (forward ctx → buildEntryFields)
              │  extractPromptAgentType  ├─→ index.ndjson (INDEX_FIELDS)
              │                          ├─→ SSE summary (summarizeEntry)
   WS path (ws-proxy) mirrors this:      └─→ /_api/entries
   sysHash + shared files + header
   agent_type merge (fill-if-absent,     restore: old index lines backfilled
   body wins, = withCodexMetadata)       via shared sys_ scan (sysHash → id)
   rebuild-index: recomputes identity
   from rehydrated system prompt

English details

Commit 1 — legibility pass

  • Type scale floor 9px: lane label 9→11, axis ticks / legend / zoom badge / overview canvas 8→10, 40/80 threshold + event track labels 7→9
  • WF_V8_CACHE_READ #58a6ff#39c5cf; rule recorded in spec: accent blue is reserved for position/selection chrome, data fill must not use it

Commit 2 — agent identity on every entry (server)

  • SSE/HTTP: identity from registerPromptVersion, extractPromptAgentType fallback for short-form prompts (subagents, title-gen)
  • WebSocket (codex's normal transport): computes sysHash/toolsHash, registers prompt identity, writes shared instructions/tools/prompt-meta files — full parity with the HTTP path incl. x-codex-turn-metadata agent_type merged fill-if-absent (body wins) and no-instructions fallback (→ codex default)
  • index.ndjson schema: agentKey/agentLabel added; restore backfills old lines from the shared sys_ scan
  • rebuild-index: preserves sysHash/toolsHash from stripped reqs, recomputes identity from the rehydrated system prompt (offline twin of registerPromptVersion)

Commit 3 — client UI

  • 3-row gutter info block; version chips width-budgeted (~196px, +n overflow), hex/charset-validated before innerHTML interpolation (escapeHtml doesn't cover quotes)
  • deep-link via spPendingDeepLink state handoff — URL params don't survive syncUrlFromState's rebuild (note: the prompt badge's "View in System Prompt" has the same latent race, untouched here)
  • Agent-identity-first lane classification with heuristic fallback
  • Lane selection renders the last turn's Timeline detail directly (no lock visuals — _wfShowTurnDetail suppresses the selectTurn → wfHighlightTurn echo); no flat-list intermediate state

Review trail (codex CLI, 4 rounds)

Round Verdict Findings → resolution
R1 FAIL WS entries missing identity fields (blocker) → ws-proxy parity; rebuild-index losing identity (major) → recompute offline; SVG data-attr injection (major) → hex/charset validation
R2 FAIL WS: no fallback without instructions; header agent_type unmerged → detectBody + fallback
R3 FAIL merge precedence: header must not overwrite body agent_type → fill-if-absent
R4 PASS no findings

Testing

  • Unit: workflow-timeline 13/13, websocket-proxy 18/18 (incl. new WS identity-parity + no-instructions assertions), rebuild-index 10/10 (incl. identity recomputation)
  • Full suite vs empty CCXRAY_HOME: only pre-existing failure is flaky test: codex-adapter rate_limits fixture fails intermittently (expected 1, got 0) #119 (codex-adapter fixture time bomb, root cause posted there)
  • Browser smoke on isolated CCXRAY_HOME + port with real session data: 6-lane misclassified session reclassifies to main=287t; chips click-to-turn and deep-link assertions; lane click → full-range detail with selectedTurnId === null

Closes #91, closes #113, closes #118, closes #121
Partially addresses #114 (fonts + lane height done; resize-handle grip and taller minimap remain) and #117 (part A identity recording done; part B spawn/return arcs remain)

🤖 Generated with Claude Code

Justin Lee and others added 6 commits July 4, 2026 07:00
Replace v7 turn bars in public/workflow-timeline.js with the v8 design
(9.03/10, autoresearch 3 rounds):

- bar height = ctx window % with cache read/write/input 3-color split
- 40%/80% threshold dashed lines (gray/red) replace zone-hue bars
- cost moves to a dedicated 8px mini-bar track (orange when >3x lane median)
- 4 event tracks (Faults/Context/Mutations/Safety), exclusive color family;
  collapsed lanes show Faults+Safety only
- tri-state interaction: Idle -> Hover (bars 1..N cumulative spotlight,
  unique per-lane cursor guide, rich tooltip) -> Locked (click, cross-lane
  dim 0.35, Esc/re-click unlock)
- event detection aligned to real SSE entry fields (status/isRetry/
  isCompacted/toolCalls/hasCredential/usage); undetectable design events
  (perm-denied, git-commit, danger-bash, perm-prompt, unsafe-blocked)
  deferred until server signals exist

Smoke-tested against real restored logs on an isolated CCXRAY_HOME
(light + dark theme, hover/lock/unlock/lane-expand verified in Chrome).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Net result of the post-integration design review:

- legend drawn inside the main SVG axis row label zone (x 0-240 was
  empty) instead of squeezing the overview minimap
- no per-lane cursor guide / lock ghost: the pre-existing #wf-cursor
  band carries lock position and the bar-highlight boundary marks the
  hover edge (prototype's guide compensated for having no band)
- hovered lane fully undims (class toggle, not CSS :hover 0.7); cross-
  lane dim restored on leave
- leaving the locked lane mid-hover snaps its highlight back to the
  locked turn
- tooltip shows a lock reminder when hovering a non-locked turn on the
  locked lane

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- workflow-view-design.md: new §v8 encoding section (lane anatomy, event
  detection table wired to real SSE fields, tri-state interaction); mark
  v1.10 zone-coloring superseded for turn bars; P12 amendment — swimlane
  expresses zone thresholds by position, other elements by color
- design-principles.md: Implicit Bridging example updated (bridge moved
  from zone hue to shared threshold position in v8); lane heights 64/88
- DESIGN-DECISIONS.md: integration decisions 15-17 (legend placement,
  guide/ghost removal rationale, band stays single-turn) + lessons 17-18
  (audit production affordances before porting prototype compensations;
  detect events from real fields only, defer the rest)
- CLAUDE.md: add missing public/workflow-timeline.js row to client table

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User feedback: 7-8px SVG text unreadable; cache read #58a6ff identical to
--accent so data fill was indistinguishable from viewport border / cursor
band / selection chrome.

- Type scale: lane label 9→11px, axis ticks/legend/zoom badge/overview
  canvas 8→10px, 40/80 threshold + event track labels 7→9px (9px floor)
- WF_V8_CACHE_READ #58a6ff → #39c5cf (teal); accent blue now reserved
  for position/selection chrome only
- Docs: design spec §v8 + DESIGN-DECISIONS integration decisions 18-19

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
KNOWN_AGENTS detection already ran server-side to feed versionIndex,
but the result never reached entries or the client. Now:

- SSE/HTTP path (server/index.js): agentKey/agentLabel from
  registerPromptVersion, with extractPromptAgentType fallback for
  short-form prompts (subagents, title-gen); carried on forward ctx
  and returned by both wire parsers' buildEntryFields
- WebSocket path (ws-proxy): computes sysHash/toolsHash, registers
  prompt identity, and writes shared instructions/tools/prompt-meta
  files exactly like the HTTP path — codex's normal transport gets
  the same fields. Header agent_type (x-codex-turn-metadata) merges
  fill-if-absent, matching withCodexMetadata precedence (body wins);
  captured requests without instructions classify via
  extractPromptAgentType (codex default)
- index.ndjson: agentKey/agentLabel added to INDEX_FIELDS; restore
  backfills old lines from the shared sys_ file scan
  (sysHashToAgentKey now carries {key, label})
- rebuild-index: rebuilt lines preserve sysHash/toolsHash from the
  stripped req and recompute coreHash/agentKey/agentLabel from the
  rehydrated system prompt (offline twin of registerPromptVersion)
- summarizeEntry exposes both fields to SSE/API clients

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation

Lane label gutter (240px) becomes a 3-row agent info block:
- Row 1 (11px): agent name — detected agentLabel; child lanes append
  the 8-char session id; fallback subagent-<model> when the request
  has no system prompt to detect from
- Row 2 (10px): model · context window size
- Row 3 (10px): sysprompt version chips, one per distinct coreHash in
  first-seen order, width-budgeted (~196px) with +n overflow. Chip
  click locks the turn where that version first appeared; ↗ opens the
  System Prompt page pre-selected via spPendingDeepLink state handoff
  (URL params don't survive syncUrlFromState's rebuild — the prompt
  badge has the same latent race). Hashes/keys are validated
  (hex / safe charset) before innerHTML interpolation since
  escapeHtml doesn't cover quotes.

Lane classification is agent-identity-first: main keys (orchestrator /
sdk-agent / codex default) always go to the main lane regardless of
model switches or the isSubagent flag — a mid-session opus→fable
switch no longer splits the orchestrator into a phantom sub-lane
(157c0faa: 32/287 turns split, sub-lane literally named
"Orchestrator"). Other keys get an agent-<key> lane named by
agentLabel. Model/ctx heuristics remain as fallback for entries
without agent identity. Main lane is longest and on top; sub lanes
sort by first-turn time.

Lane selection with no locked turn renders the last turn's Timeline
detail directly (its request carries the whole conversation = full
range) with no lock visuals — _wfShowTurnDetail suppresses the
selectTurn → wfHighlightTurn echo. No flat-list intermediate state.

Closes #121

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant