feat(usage): per-agent Context Timeline inspector#1150
Conversation
Adds a turn-by-turn view of how an agent's context window fills over a run, inspired by the interactive context-window visualization that landed in the Claude Code docs. Provider-agnostic by construction: it taps the single per-turn usage stream every provider already feeds (process:usage -> useAgentUsageListener) and stores normalized UsageStats points, so there is no per-agent code. The window denominator is provider-reported when available (e.g. Codex) and a static estimate otherwise; the panel says so rather than implying false precision. Providers that only report usage at end-of-run (e.g. Factory Droid) show a single point instead of an evolving series. - contextTimelineStore: in-memory, bounded, always-on per-session capture - useAgentUsageListener: records one point per usage event (no new IPC) - ContextTimelinePanel: floating inspector, opened by clicking the context-usage readout in the Main Panel header; anchored bottom-left so it never collides with the Thought Stream panel - unit tests for the store lifecycle and buffer bounding Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new Zustand-backed ChangesContext Timeline Feature
Sequence Diagram(s)sequenceDiagram
participant MainPanelHeader
participant contextTimelineStore
participant useAgentUsageListener
participant ContextTimelinePanel
MainPanelHeader->>contextTimelineStore: openPanel(sessionId)
useAgentUsageListener->>contextTimelineStore: appendPoint(baseSessionId, usagePoint)
ContextTimelinePanel->>contextTimelineStore: read panelSessionId, minimized, points
contextTimelineStore-->>ContextTimelinePanel: session state and buffer
ContextTimelinePanel->>ContextTimelinePanel: render rows, fill bars, chips
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryAdds a per-agent Context Timeline inspector: a turn-by-turn floating panel showing how the context window fills, opened by clicking the existing context-usage
Confidence Score: 4/5Safe to merge; all changes are additive and isolated to a new opt-in inspector panel with no modifications to existing data paths or IPC channels. During accumulated multi-tool turns the stored contextTokens can exceed contextWindow, producing a misleading token display in the panel. Session buffers are also never pruned when sessions are deleted, which can silently grow memory for pipeline-heavy users. src/renderer/hooks/agent/internal/useAgentUsageListener.ts — the point recorded during overflow turns needs the same fallback logic that calculateContextDisplay already applies; also houses the triplicated window-resolution calculation. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Provider as Agent Provider (CLI)
participant IPC as process:usage IPC
participant Listener as useAgentUsageListener
participant CTS as contextTimelineStore
participant Panel as ContextTimelinePanel
participant Header as MainPanelHeader
Provider->>IPC: emit usage event (per turn)
IPC->>Listener: onUsage(sessionId, usageStats)
Listener->>Listener: estimateContextUsage() → contextPercentage
Listener->>Listener: calculateContextTokens() → contextTokens
Listener->>Listener: resolve contextWindow (3-tier lookup)
Listener->>CTS: appendPoint(baseSessionId, point)
CTS->>CTS: "stamp id + timestamp, trim if > MAX_POINTS_PER_SESSION"
Listener->>Listener: batchedUpdater.updateUsage / updateContextUsage
Header->>CTS: openPanel(activeSession.id) [on click]
CTS-->>Panel: panelSessionId, minimized, buffers[sessionId]
Panel->>Panel: selectPoints → ordered (newest first)
Panel->>Panel: render fill bars, token breakdown, cost
Panel->>CTS: closePanel() / minimizePanel() / clearSession() [on action]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Provider as Agent Provider (CLI)
participant IPC as process:usage IPC
participant Listener as useAgentUsageListener
participant CTS as contextTimelineStore
participant Panel as ContextTimelinePanel
participant Header as MainPanelHeader
Provider->>IPC: emit usage event (per turn)
IPC->>Listener: onUsage(sessionId, usageStats)
Listener->>Listener: estimateContextUsage() → contextPercentage
Listener->>Listener: calculateContextTokens() → contextTokens
Listener->>Listener: resolve contextWindow (3-tier lookup)
Listener->>CTS: appendPoint(baseSessionId, point)
CTS->>CTS: "stamp id + timestamp, trim if > MAX_POINTS_PER_SESSION"
Listener->>Listener: batchedUpdater.updateUsage / updateContextUsage
Header->>CTS: openPanel(activeSession.id) [on click]
CTS-->>Panel: panelSessionId, minimized, buffers[sessionId]
Panel->>Panel: selectPoints → ordered (newest first)
Panel->>Panel: render fill bars, token breakdown, cost
Panel->>CTS: closePanel() / minimizePanel() / clearSession() [on action]
Reviews (1): Last reviewed commit: "feat(usage): add per-agent Context Timel..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/renderer/hooks/agent/internal/useAgentUsageListener.ts (2)
64-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated context-window resolution with an inconsistency for
terminal.
resolvedWindow(64-72) guardsagentToolType !== 'terminal', but theeffectiveWindowblock (91-99) callsgetContextWindowForAgentwheneveragentToolTypeis truthy — including'terminal'. The two blocks compute the same denominator but disagree on the terminal case. Consider extracting a single helper so both paths stay in sync.♻️ Suggested extraction
+ const resolveWindow = () => + usageStats.contextWindow > 0 + ? usageStats.contextWindow + : agentToolType && agentToolType !== 'terminal' + ? getContextWindowForAgent( + agentToolType, + useAgentStore.getState().getCapabilitySnapshot(agentToolType, sessionRemoteId) + ) + : 0;Then reuse
resolveWindow()for bothresolvedWindowandeffectiveWindow.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/hooks/agent/internal/useAgentUsageListener.ts` around lines 64 - 99, The context-window calculation in useAgentUsageListener is duplicated and inconsistent for the terminal agent type. Extract the shared resolution logic into a single helper such as resolveWindow() and use it for both the resolvedWindow assignment and the effectiveWindow fallback so the terminal guard stays consistent. Keep the logic aligned with getContextWindowForAgent, useAgentStore.getState().getCapabilitySnapshot, and the current sessionRemoteId/baseSessionId flow.
73-84: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUnbounded per-session buffer accumulation.
appendPointruns on every usage tick for every session, even ones whose timeline is never opened. Each session buffer is capped atMAX_POINTS_PER_SESSION, but there appears to be no eviction of whole-session buffers when a session is deleted, so thebuffersmap grows for the app's lifetime. Consider clearing a session's buffer on session deletion (e.g., wiringclearSessioninto the session-removal path).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/hooks/agent/internal/useAgentUsageListener.ts` around lines 73 - 84, The per-session timeline buffers in useAgentUsageListener are accumulating for the lifetime of the app because appendPoint keeps creating entries even for unopened sessions and nothing clears them when a session is removed. Update the session-removal flow to call the existing clearSession logic from useContextTimelineStore for the deleted session’s baseSessionId, and verify any session cleanup path that already handles deletion routes through that same buffer eviction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/__tests__/renderer/stores/contextTimelineStore.test.ts`:
- Around line 140-145: The test for selectPoints in contextTimelineStore only
asserts deep equality for unknown sessions, so it would miss regressions where a
new empty array is allocated each time. Update the existing test around
selectPoints/useContextTimelineStore to also assert referential stability with
identity checks, using the same selector calls for the unknown-session cases and
verifying the returned empty array instance is reused.
In `@src/renderer/components/ContextTimelinePanel.tsx`:
- Around line 199-223: The per-row percentage label and bar fill in
ContextTimelinePanel are using different sources, which can make a single row
look inconsistent. In the row rendering inside ordered.map, make the fill width
follow the same source as the displayed percentage by using p.percentage when it
is non-null, and only fall back to the contextTokens/contextWindow ratio when
percentage is unavailable. Keep getContextColor aligned with the same value so
the label, width, and color all reflect the same state.
---
Nitpick comments:
In `@src/renderer/hooks/agent/internal/useAgentUsageListener.ts`:
- Around line 64-99: The context-window calculation in useAgentUsageListener is
duplicated and inconsistent for the terminal agent type. Extract the shared
resolution logic into a single helper such as resolveWindow() and use it for
both the resolvedWindow assignment and the effectiveWindow fallback so the
terminal guard stays consistent. Keep the logic aligned with
getContextWindowForAgent, useAgentStore.getState().getCapabilitySnapshot, and
the current sessionRemoteId/baseSessionId flow.
- Around line 73-84: The per-session timeline buffers in useAgentUsageListener
are accumulating for the lifetime of the app because appendPoint keeps creating
entries even for unopened sessions and nothing clears them when a session is
removed. Update the session-removal flow to call the existing clearSession logic
from useContextTimelineStore for the deleted session’s baseSessionId, and verify
any session cleanup path that already handles deletion routes through that same
buffer eviction.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3bdcf7cf-9ddf-4f55-9c98-fc0b26f4fa64
📒 Files selected for processing (7)
src/__tests__/renderer/stores/contextTimelineStore.test.tssrc/renderer/App.tsxsrc/renderer/components/ContextTimelinePanel.tsxsrc/renderer/components/MainPanel/MainPanelHeader.tsxsrc/renderer/constants/modalPriorities.tssrc/renderer/hooks/agent/internal/useAgentUsageListener.tssrc/renderer/stores/contextTimelineStore.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5f8a15aef
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- De-duplicate context-window resolution: compute resolvedWindow once and
reuse it for both the timeline point and the accumulated-growth fallback,
fixing a terminal-case divergence (Greptile/CodeRabbit).
- Prune per-session buffers on agent deletion: add removeSession() to the
store and call it from sessionStore.removeSession so deleted agents don't
leak buffers; it also hides the panel if the deleted session was focused.
- Guard the misleading overflow readout: on an accumulated multi-tool turn
whose raw tokens exceed the window, cap the displayed figure to the window
and flag it ("200k+ / 200k") instead of implying an impossible breach.
- Tests: assert the selector's stable-empty-array identity (toBe) and cover
removeSession (focused + unfocused panel).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/renderer/stores/sessionStore.ts (1)
186-194: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSide effect inside the
setupdater.
useContextTimelineStore.getState().removeSession(id)is a cross-store mutation executed inside what is otherwise a pure state-updater. It works (zustand invokes the updater once), but mixing the side effect into the reducer makes the action harder to reason about and test. Consider performing the no-op check and cleanup outsideset:♻️ Proposed refactor
removeSession: (id) => - set((s) => { - const filtered = s.sessions.filter((session) => session.id !== id); - // Skip if nothing was removed - if (filtered.length === s.sessions.length) return s; - // Drop the deleted agent's context-timeline buffer so it doesn't leak. - useContextTimelineStore.getState().removeSession(id); - return { sessions: filtered }; - }), + set((s) => { + const filtered = s.sessions.filter((session) => session.id !== id); + // Skip if nothing was removed + if (filtered.length === s.sessions.length) return s; + return { sessions: filtered }; + }),and trigger the timeline cleanup from the caller / a small wrapper after the
setresolves. The timeline store's own no-op guard keeps this safe even ifremoveSessionis called for an unknown id.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/stores/sessionStore.ts` around lines 186 - 194, The `removeSession` action in `sessionStore` mixes a cross-store cleanup call into the `set` updater, which should stay pure and easier to reason about. Move the no-op check and `useContextTimelineStore.getState().removeSession(id)` call out of the updater in `removeSession`, then update the caller or add a small wrapper so the session removal happens first and the timeline cleanup runs afterward; keep the existing `removeSession`/`useContextTimelineStore` guard behavior intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/renderer/stores/sessionStore.ts`:
- Around line 186-194: The `removeSession` action in `sessionStore` mixes a
cross-store cleanup call into the `set` updater, which should stay pure and
easier to reason about. Move the no-op check and
`useContextTimelineStore.getState().removeSession(id)` call out of the updater
in `removeSession`, then update the caller or add a small wrapper so the session
removal happens first and the timeline cleanup runs afterward; keep the existing
`removeSession`/`useContextTimelineStore` guard behavior intact.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9d5f15be-dc0e-4068-9218-35520c3ab8a2
📒 Files selected for processing (5)
src/__tests__/renderer/stores/contextTimelineStore.test.tssrc/renderer/components/ContextTimelinePanel.tsxsrc/renderer/hooks/agent/internal/useAgentUsageListener.tssrc/renderer/stores/contextTimelineStore.tssrc/renderer/stores/sessionStore.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/renderer/hooks/agent/internal/useAgentUsageListener.ts
- src/renderer/components/ContextTimelinePanel.tsx
- src/renderer/stores/contextTimelineStore.ts
- src/tests/renderer/stores/contextTimelineStore.test.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04afb9faaf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Thanks for this, @chr1syy - genuinely clean piece of work. Tapping the existing Credit also for how responsively you've already worked through the bot feedback. Reading the current head, the overflow case now caps the shown figure to the window and flags it with A few things I'd like addressed before this merges, leading with the one that matters: 1. Synthetic runs (synopsis / Auto Run batch) will pollute the visible agent's timeline. 2. Worth confirming: Copilot output-only deltas. 3. Minor polish: per-row "%" label vs fill-bar width can disagree. The label uses No merge conflicts and the overall design is solid, so once the synthetic-run filtering is sorted (and ideally the Copilot delta confirmed) I'm happy to approve. Thanks again for the contribution! |
- Exclude synthetic runs (synopsis / Auto Run batch) from the timeline: they map to the parent baseSessionId but consume a separate process context, so recording them polluted the visible agent's timeline. Only interactive runs (ai-tab / legacy-ai / regular) are recorded now, matching the visible usage gauge which keys off actualSessionId. (chatgpt-codex-connector) - Skip Copilot output-only deltas (output tokens only, zero input/cache) so a normal Copilot run no longer dips to just the latest output tokens between context snapshots; mirrors the snapshot-preserving guard in useBatchedSessionUpdates. (chatgpt-codex-connector) - Drive the per-row bar width from the same source as the % label/color (provider percentage when available, else tokens/window ratio) so width and label can't visually disagree. (CodeRabbit) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aab603a359
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Register the inspector as an OVERLAY layer, not a modal: opening a passive
inspector was tripping hasOpenModal() and suppressing global shortcuts +
file-tree navigation. Extends useModalLayer with an optional layerType
('modal' default, 'overlay' opt-in); the inspector still takes Escape to
minimize but no longer blocks shortcuts. (chatgpt-codex-connector)
- Hide the inspector while any real modal is open, so its high z-index can't
float it above lower-z dialogs (Create PR, expanded Auto Run) that own the
foreground. It stays registered (Escape still works) but renders nothing.
(chatgpt-codex-connector)
- Prune timeline buffers from setSessions deletions: the real delete flows
(single-agent, group delete) filter the array and call setSessions directly,
bypassing removeSession. Diff old vs new on length-decrease and drop the
vanished agents' buffers there. (chatgpt-codex-connector)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5a5810597
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… a11y Root-cause pass on the layer integration: - The inspector no longer registers a layer at all. Any layer (even an overlay) trips hasOpenLayers()/hasOpenModal() and suppresses global shortcuts (Cmd+K, Cmd+N) + file-tree keys while open - wrong for a passive inspector. It's dismissed via its own X/minimize buttons. This reverts the overlay layerType experiment, so useModalLayer is back to pristine and the unused CONTEXT_TIMELINE priority is removed. (chatgpt-codex-connector #1/#2) - Fix the modal-state read: it imported hooks/ui/useLayerStack (the stack CREATOR, with its own useState) instead of the context accessor, so hasOpenModal() observed a private empty stack and never hid the panel. Now imports contexts/LayerStackContext, so the hide-while-modal-open guard actually works. (chatgpt-codex-connector #4) - Make the header opener keyboard accessible: role="button", tabIndex, an aria-label, and Enter/Space handling, so the inspector is reachable without a mouse in this keyboard-first UI. (chatgpt-codex-connector #3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/renderer/components/ContextTimelinePanel.tsx (1)
153-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueIcon-only header buttons lack accessible labels.
titlealone isn't reliably announced by all screen readers; since this same commit adds keyboard/button semantics to the header opener for accessibility, consider extending that to these clear/minimize/close icon buttons witharia-label.♿ Proposed fix
<button onClick={() => clearSession(panelSessionId)} title="Clear recorded history" + aria-label="Clear recorded history" className="p-1 rounded hover:bg-white/10 transition-colors shrink-0" > <Trash2 className="w-4 h-4" style={{ color: theme.colors.textDim }} /> </button> <button onClick={minimizePanel} title="Minimize" + aria-label="Minimize" className="p-1 rounded hover:bg-white/10 transition-colors shrink-0" > <Minus className="w-4 h-4" style={{ color: theme.colors.textDim }} /> </button> <button onClick={closePanel} title="Close (keeps history)" + aria-label="Close (keeps history)" className="p-1 rounded hover:bg-white/10 transition-colors shrink-0" > <X className="w-4 h-4" style={{ color: theme.colors.textDim }} /> </button>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/components/ContextTimelinePanel.tsx` around lines 153 - 173, The header icon buttons in ContextTimelinePanel are missing reliable accessible labels because they only use title. Update the clear/minimize/close button elements alongside clearSession, minimizePanel, and closePanel to include explicit aria-labels that describe each action so screen readers announce them correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/renderer/components/ContextTimelinePanel.tsx`:
- Around line 153-173: The header icon buttons in ContextTimelinePanel are
missing reliable accessible labels because they only use title. Update the
clear/minimize/close button elements alongside clearSession, minimizePanel, and
closePanel to include explicit aria-labels that describe each action so screen
readers announce them correctly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b82f20ce-e471-499b-a621-7fc4eef686f9
📒 Files selected for processing (2)
src/renderer/components/ContextTimelinePanel.tsxsrc/renderer/components/MainPanel/MainPanelHeader.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/renderer/components/MainPanel/MainPanelHeader.tsx
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ce43cc3f3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The timeline denominator now matches the header gauge's window precedence (resolveConfiguredContextWindow): a per-agent `customContextWindow` or a `[1m]` model marker wins over the reported/static window. Previously a session configured for e.g. 1M had timeline points stored against a 200k denominator with an inflated percentage while the header gauge used 1M. The per-row percentage is now derived from that same configured-aware window, so the row is internally consistent and agrees with the header. The async agent-config lookup is intentionally skipped on this hot per-turn path; the two synchronous sources cover the configured cases that would otherwise mis-size. (chatgpt-codex-connector) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 63126ce8ad
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const contextTokens = calculateContextTokens(usageStats, agentToolType); | ||
| // Percentage against the SAME (configured-aware) window the point | ||
| // stores, so the row is internally consistent and matches the header. | ||
| // null when tokens exceed the window (accumulated multi-tool turn) - | ||
| // the panel renders that as "~" plus an accumulated flag. | ||
| const pointPercentage = | ||
| resolvedWindow > 0 && contextTokens <= resolvedWindow | ||
| ? Math.round((contextTokens / resolvedWindow) * 100) | ||
| : null; |
There was a problem hiding this comment.
Accumulate Codex usage before plotting timeline
For Codex runs with more than one token-count event, this uses the process:usage payload after StdoutHandler has delta-normalized Codex's total_token_usage totals (src/main/process-manager/handlers/StdoutHandler.ts:466-469, src/main/parsers/codex-output-parser.ts:497-515). That means each timeline row records only the latest delta rather than the cumulative context currently occupying the model window, so a long Codex conversation can show low, non-growing fill even as the actual context fills. Keep a per-session cumulative value for cumulative/combined providers, or append from the original total before normalization.
Useful? React with 👍 / 👎.
…n deltas Addresses the P2 review thread on useAgentUsageListener: for providers whose CLI reports cumulative session usage that we delta-normalize before emitting (Codex), the timeline was recording each per-turn DELTA as the context figure, so a long Codex conversation showed low, non-growing fill even as the window actually filled. - StdoutHandler.normalizeUsageToDelta now attaches the pre-normalization cumulative totals as `absoluteUsage` on the emitted UsageStats. The top-level fields stay per-turn deltas (correct for token accumulation); `absoluteUsage` carries the running total that actually occupies the context window. Only set on delta events - the first event is already absolute. - The Context Timeline listener plots contextTokens/percentage from `absoluteUsage` when present, falling back to the per-turn stats for per-call providers (Claude/Copilot/OpenCode). The point's per-turn token chips stay as the deltas (this turn's activity). - Added `absoluteUsage` (optional) to the shared UsageStats type. Tests: StdoutHandler asserts the cumulative snapshot rides along on the delta event (and is absent on the first raw event); the listener asserts the timeline plots occupancy from the absolute totals, not the delta. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed in You're right - for Codex the timeline was recording each per-turn delta as the context figure, so a long conversation showed low, non-growing fill even as the window filled. Rather than reconstruct the cumulative in the renderer (fragile across the raw/delta and process-resume boundaries that only the main process can see), I took the "append from the original total before normalization" route:
Added Note: the header gauge has the same latent delta-vs-cumulative behavior for Codex, but that predates this PR and is out of scope here - happy to open a follow-up if you'd like it fixed the same way. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35090c07c7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| (usageStats.cacheReadInputTokens || 0) === 0 && | ||
| (usageStats.cacheCreationInputTokens || 0) === 0 && | ||
| usageStats.outputTokens > 0; | ||
| if (isInteractiveRun && !isOutputOnlyDelta) { |
There was a problem hiding this comment.
Do not drop Codex absolute usage snapshots
When Codex emits cumulative total_token_usage events where input and cache totals are unchanged but output grows, normalizeUsageToDelta sends a delta with inputTokens and cache fields at 0, outputTokens > 0, and absoluteUsage holding the real cumulative snapshot. This generic output-only guard skips the append before the absoluteUsage path below can run, so those Codex turns never appear and the timeline can miss context growth; limit the skip to providers without absoluteUsage or to Copilot instead.
Useful? React with 👍 / 👎.
| activeTabContextWindow > 0 && ( | ||
| <div | ||
| className="header-context-widget flex items-center mr-2 relative cursor-pointer" | ||
| className="header-context-widget flex items-center mr-2 relative cursor-pointer outline-none" |
There was a problem hiding this comment.
Preserve focus visibility on the timeline trigger
When keyboard users tab through the header, this newly focusable context trigger removes the native focus outline without adding any replacement focus-visible styling, so the only keyboard path to the timeline can receive focus invisibly. In this keyboard-first UI, keep the browser outline or add a visible focus ring on this control.
Useful? React with 👍 / 👎.
| // already absolute for the turn. The per-turn token fields recorded on | ||
| // the point below are intentionally left as the deltas (this turn's | ||
| // activity); only the context-fill figures use the absolute source. | ||
| const contextSource = usageStats.absoluteUsage ?? usageStats; |
There was a problem hiding this comment.
Restrict absoluteUsage plotting to Codex
When Claude Code usage stays monotonic, normalizeUsageToDelta also attaches absoluteUsage because it is called for both claude-code and codex, but this line then bypasses the delta-normalized values for every provider. A normal Claude run can therefore plot pre-normalization cumulative token spend as context fill while the existing gauge continues to use the normalized snapshot; only use absoluteUsage for the cumulative combined-context provider that needs it, or stop emitting it for Claude.
Useful? React with 👍 / 👎.
| resolvedWindow > 0 && contextTokens <= resolvedWindow | ||
| ? Math.round((contextTokens / resolvedWindow) * 100) | ||
| : null; | ||
| useContextTimelineStore.getState().appendPoint(baseSessionId, { |
There was a problem hiding this comment.
Coalesce duplicate Codex usage events
Codex's parser turns both event_msg token_count events and turn.completed messages into process:usage updates (src/main/parsers/codex-output-parser.ts:433-441 and 497-508). When both arrive for the same turn with the same cumulative totals, the second update becomes a zero delta with absoluteUsage, and this unconditional append records a second row with no token activity, inflating the panel's turn count and adding duplicate context points; skip zero-delta cumulative repeats or append only the final usage event for a Codex turn.
Useful? React with 👍 / 👎.
… turns, drop repeats, fix focus ring Follow-up review on the Codex context fix: - Restrict `absoluteUsage` to combined-context providers (Codex). normalizeUsageToDelta also runs for Claude Code, which can look monotonic for the first turns of a session; Claude's per-call values are NOT cumulative occupancy, so attaching a snapshot there would let the timeline plot cumulative token spend as context fill. Gated on COMBINED_CONTEXT_AGENTS. - Do not drop Codex output-only turns: the output-only skip now applies only when there is no `absoluteUsage`. A Codex output-only turn still carries an absolute snapshot reflecting real context growth and must be recorded. - Coalesce duplicate Codex usage events: Codex emits a usage update for BOTH the token_count event and turn.completed with identical cumulative totals, so the second is an all-zero delta. Skip no-activity events so the panel doesn't gain a duplicate row / repeated context point. - Restore keyboard focus visibility on the context-timeline trigger: the focusable header widget dropped the native outline with no replacement; add a focus-visible ring (keyboard-first UI). Tests: Claude Code delta carries no absoluteUsage; Codex delta does; Codex output-only turn is recorded from the absolute snapshot; zero-delta Codex repeat is skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Follow-up review addressed in
Tests: Claude Code delta carries no |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95a57a48a0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const configuredWindow = | ||
| typeof sessionForUsage.customContextWindow === 'number' && | ||
| sessionForUsage.customContextWindow > 0 | ||
| ? sessionForUsage.customContextWindow | ||
| : getModelContextWindowOverride(sessionForUsage.customModel) || 0; |
There was a problem hiding this comment.
Honor provider-configured context windows
Fresh evidence in this final diff is that this calculation still only checks customContextWindow and the session model marker, while resolveConfiguredContextWindow also reads window.maestro.agents.getConfig(...) for the provider config. For existing agents without a per-session override, or after changing the provider's configured contextWindow or [1m] model, the header gauge uses that configured value but timeline points fall through to reported or static windows here, so the panel can plot the same turn against 200k/128k instead of the configured window. Cache or pass the resolved configured window into this listener before appending points.
Useful? React with 👍 / 👎.
…e timeline Follow-up review: the timeline listener sized points from only the per-session custom window and the model marker, while the header gauge also resolves the provider's configured window via the async getConfig step. For agents whose window is configured at the provider level only (e.g. OpenCode, which doesn't report contextWindow live), the timeline plotted against the static table while the gauge used the configured value. Add a small synchronous cache in contextWindowResolver (getCached... + ensureConfiguredContextWindowCached) so the hot per-turn listener can honor the provider-configured window without awaiting getConfig on every event: the two sync sources still take precedence and cover the common cases immediately, and the cache - primed off-path - closes the gap on subsequent turns. Provider-config edits that don't change the per-session key are picked up on the next load. Test: an OpenCode session with no per-session override and no reported window sizes the timeline against the cached 300k provider window once primed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the provider-configured window thread in Honor provider-configured context windows ( |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0a6b39b24a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const configuredWindow = | ||
| syncConfiguredWindow > 0 | ||
| ? syncConfiguredWindow | ||
| : getCachedConfiguredContextWindow(sessionForUsage); |
There was a problem hiding this comment.
Prime provider windows before first timeline point
When the effective window only comes from async provider config, such as an OpenCode agent-level contextWindow or provider model with [1m], ensureConfiguredContextWindowCached() starts the lookup but this immediate cache read is still 0, so the point appended for that same usage event falls through to the reported/static window. A one-turn run, or just the first row of any run, is then permanently stored with an inflated percentage like 128k instead of the configured 300k, even if the header has already resolved the correct value via resolveConfiguredContextWindow; prime this cache before recording or defer/patch the first point once the async value resolves.
Useful? React with 👍 / 👎.
What
Adds a per-agent Context Timeline inspector: a turn-by-turn view of how an agent's context window fills over the course of a run. Inspired by the interactive context-window visualization that recently landed in the Claude Code docs.
Open it by clicking the context-usage
%readout in the Main Panel header. It docks bottom-left (so it never collides with the Thought Stream panel, which docks bottom-right).Why it's provider-agnostic by construction
Every supported provider's parser already normalizes its CLI usage into the shared
UsageStatsshape, and that shape already streams to the renderer once per turn overprocess:usage->useAgentUsageListener. This feature taps that same stream and records normalized points. There is no per-agent code and no new IPC channel.Honest degradation (surfaced in the UI, not hidden)
Cross-provider audit of every parser informed these caveats, which the panel states plainly instead of implying false precision:
model_context_windowlive) and a static estimate otherwise (claude-code/copilot/factory-droid fall back to the table; OpenCode is user-configured). The panel labels the window as "provider-reported when available, otherwise estimated".parentToolCallIdbut still attributes to the parent), so a faithful subagent breakdown isn't possible from usage data.Changes
contextTimelineStore- in-memory, bounded (MAX_POINTS_PER_SESSION), always-on per-session capture. Close keeps history; Clear wipes it.useAgentUsageListener- records one normalized point per usage event (provider-awarecalculateContextTokens+ resolved window).ContextTimelinePanel- floating inspector; per-turn fill bar colored by the existing context-warning thresholds, plus a token breakdown (in / cache r / cache w / out / reason / cost).MainPanelHeader- the existing context readout becomes the open trigger.modalPriorities-CONTEXT_TIMELINElayer (Escape minimizes, non-blocking).Validation
npm run lint(tsc, all configs): cleanvitest runnew store test: 11/11 passNotes / follow-ups (not in this PR)
This is "Phase 1" of a larger idea. Deliberately not included: a "what's loaded" composition breakdown (system prompt / project docs / tools), which is only estimable for claude-code and isn't in
UsageStatsfor the others; and subagent bands (infeasible cross-provider per the audit above).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes