Skip to content

feat(usage): per-agent Context Timeline inspector#1150

Open
chr1syy wants to merge 9 commits into
RunMaestro:rcfrom
chr1syy:feat/context-timeline-inspector
Open

feat(usage): per-agent Context Timeline inspector#1150
chr1syy wants to merge 9 commits into
RunMaestro:rcfrom
chr1syy:feat/context-timeline-inspector

Conversation

@chr1syy

@chr1syy chr1syy commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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 UsageStats shape, and that shape already streams to the renderer once per turn over process: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:

  • Window denominator is provider-reported when available (e.g. Codex reports model_context_window live) 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".
  • Factory Droid only reports usage at end-of-run, so it shows a single point rather than an evolving series. claude-code / codex / opencode / copilot / pi stream per-turn and animate.
  • Subagent attribution is intentionally out of scope: no provider emits clean per-subagent token accounting today (claude folds Task-tool usage into the parent; copilot exposes parentToolCallId but 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-aware calculateContextTokens + 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_TIMELINE layer (Escape minimizes, non-blocking).
  • Unit tests for the store lifecycle and buffer bounding (11 tests).

Validation

  • npm run lint (tsc, all configs): clean
  • eslint on changed files: clean
  • vitest run new store test: 11/11 pass

Notes / 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 UsageStats for the others; and subagent bands (infeasible cross-provider per the audit above).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new context timeline panel showing per-session usage history, token breakdowns, cost, and context fill status.
    • The context window display is now interactive and opens the timeline panel.
    • Timeline data now stays in sync when sessions are removed.
  • Bug Fixes

    • Improved panel behavior for minimize, close, clear history, and session switching.
    • Fixed timeline history handling for long sessions and empty/unknown sessions.

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>
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Zustand-backed contextTimelineStore that captures per-session, turn-by-turn context usage points with a cap and trim logic. Wires the usage listener to append points, renders a new ContextTimelinePanel mounted app-wide, adds a header click entry point, and clears timeline buffers on session removal.

Changes

Context Timeline Feature

Layer / File(s) Summary
contextTimelineStore: data model and actions
src/renderer/stores/contextTimelineStore.ts
Defines ContextTimelinePoint/ContextTimelineBuffer types, MAX_POINTS_PER_SESSION, and store actions: appendPoint, openPanel, minimizePanel/restorePanel, closePanel, clearSession, removeSession, selectPoints.
Usage listener: record timeline points
src/renderer/hooks/agent/internal/useAgentUsageListener.ts
Computes a shared resolvedWindow once per usage event and appends timeline points keyed by baseSessionId with token/cost/percentage data for interactive, non-output-only deltas.
ContextTimelinePanel component and app mount
src/renderer/components/ContextTimelinePanel.tsx, src/renderer/App.tsx
New floating bottom-left panel showing per-turn fill bars, token chips, and auto-pinned scroll; mounted app-wide alongside other global panels.
Header click to open panel and session cleanup
src/renderer/components/MainPanel/MainPanelHeader.tsx, src/renderer/stores/sessionStore.ts
Context Window widget becomes a clickable/keyboard-activatable control calling openPanel; sessionStore calls removeSession on the timeline store when sessions are deleted.
contextTimelineStore tests
src/__tests__/renderer/stores/contextTimelineStore.test.ts
Vitest coverage of append, cap/trim, panel lifecycle, clear, select, and remove-session behaviors.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • RunMaestro/Maestro#848: Both PRs modify the same "Context Window" widget in MainPanelHeader.tsx, one adding click interactivity, the other changing displayed token values.

Poem

A timeline hops, turn by turn it grows,
🐇 tracking tokens where the context flows,
Click the window, watch the bars ignite,
Clear or close, but history stays in sight,
Hop along, dear session, don't you fret—
Your usage trail is captured, safe and set!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a per-agent Context Timeline inspector.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

Adds 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 % readout in the main panel header. The implementation is provider-agnostic by design — it taps the shared process:usage IPC stream that all parsers already feed, records one normalized point per turn into a new contextTimelineStore, and renders a scrollable history with fill bars coloured by the existing warning thresholds.

  • contextTimelineStore maintains an always-on, bounded (2 000 points/session) in-memory buffer per session with clean open/minimize/close/clear lifecycle; 11 unit tests cover the key invariants.
  • useAgentUsageListener is extended to record each incoming usage event as a timeline point, reusing calculateContextTokens + a three-tier window lookup already present in the same function.
  • The panel mounts once app-wide (parallel to ThoughtStreamPanel), docks bottom-left, and handles the honest per-provider caveats (estimated vs. reported window, single-point providers like Factory Droid) directly in the UI copy.

Confidence Score: 4/5

Safe 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

Filename Overview
src/renderer/stores/contextTimelineStore.ts New Zustand store for per-session turn-by-turn context accounting; well-structured with bounded buffers, stable empty-array selector, and clean open/close/minimize/clear lifecycle.
src/renderer/hooks/agent/internal/useAgentUsageListener.ts Adds timeline point recording to the existing usage listener; the stored contextTokens can exceed contextWindow during accumulated multi-tool turns (when estimateContextUsage returns null), causing the panel to display confusing "310k / 200k" token text, and the resolvedWindow calculation partially duplicates existing logic in the same function with a minor inconsistency.
src/renderer/components/ContextTimelinePanel.tsx New floating inspector panel; scroll-to-top auto-tail logic is correct, per-turn bar/color/text rendering is solid, but the text display doesn't guard against contextTokens > contextWindow for accumulated turns.
src/renderer/components/MainPanel/MainPanelHeader.tsx Adds an onClick to the existing context-usage widget to open the timeline panel; minimal and safe change.
src/renderer/constants/modalPriorities.ts Adds CONTEXT_TIMELINE priority at 209, one below THOUGHT_STREAM (210), so the two panels don't fight for Escape.
src/tests/renderer/stores/contextTimelineStore.test.ts 11 well-scoped unit tests covering store lifecycle, buffer bounding, and the selectPoints selector; doesn't exercise the null-percentage / overflow case, which is where the display bug lives.
src/renderer/App.tsx Mounts ContextTimelinePanel app-wide, mirroring the ThoughtStreamPanel pattern.

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]
Loading
%%{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]
Loading

Reviews (1): Last reviewed commit: "feat(usage): add per-agent Context Timel..." | Re-trigger Greptile

Comment thread src/renderer/hooks/agent/internal/useAgentUsageListener.ts Outdated
Comment thread src/renderer/stores/contextTimelineStore.ts
Comment thread src/renderer/hooks/agent/internal/useAgentUsageListener.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/renderer/hooks/agent/internal/useAgentUsageListener.ts (2)

64-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated context-window resolution with an inconsistency for terminal.

resolvedWindow (64-72) guards agentToolType !== 'terminal', but the effectiveWindow block (91-99) calls getContextWindowForAgent whenever agentToolType is 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 both resolvedWindow and effectiveWindow.

🤖 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 win

Unbounded per-session buffer accumulation.

appendPoint runs on every usage tick for every session, even ones whose timeline is never opened. Each session buffer is capped at MAX_POINTS_PER_SESSION, but there appears to be no eviction of whole-session buffers when a session is deleted, so the buffers map grows for the app's lifetime. Consider clearing a session's buffer on session deletion (e.g., wiring clearSession into 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

📥 Commits

Reviewing files that changed from the base of the PR and between cde4adb and e5f8a15.

📒 Files selected for processing (7)
  • src/__tests__/renderer/stores/contextTimelineStore.test.ts
  • src/renderer/App.tsx
  • src/renderer/components/ContextTimelinePanel.tsx
  • src/renderer/components/MainPanel/MainPanelHeader.tsx
  • src/renderer/constants/modalPriorities.ts
  • src/renderer/hooks/agent/internal/useAgentUsageListener.ts
  • src/renderer/stores/contextTimelineStore.ts

Comment thread src/__tests__/renderer/stores/contextTimelineStore.test.ts
Comment thread src/renderer/components/ContextTimelinePanel.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/renderer/hooks/agent/internal/useAgentUsageListener.ts Outdated
Comment thread src/renderer/hooks/agent/internal/useAgentUsageListener.ts Outdated
- 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/renderer/stores/sessionStore.ts (1)

186-194: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Side effect inside the set updater.

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 outside set:

♻️ 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 set resolves. The timeline store's own no-op guard keeps this safe even if removeSession is 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

📥 Commits

Reviewing files that changed from the base of the PR and between e5f8a15 and 04afb9f.

📒 Files selected for processing (5)
  • src/__tests__/renderer/stores/contextTimelineStore.test.ts
  • src/renderer/components/ContextTimelinePanel.tsx
  • src/renderer/hooks/agent/internal/useAgentUsageListener.ts
  • src/renderer/stores/contextTimelineStore.ts
  • src/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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/renderer/hooks/agent/internal/useAgentUsageListener.ts Outdated
@pedramamini

Copy link
Copy Markdown
Collaborator

Thanks for this, @chr1syy - genuinely clean piece of work. Tapping the existing process:usage stream so the feature is provider-agnostic with no per-agent code and no new IPC channel is exactly the right call, and I appreciate that the "honest degradation" caveats (estimated vs provider-reported window, Factory Droid's single point, subagent attribution out of scope) are surfaced plainly in the UI rather than papered over. The lint/eslint/test validation is appreciated too.

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 +/tooltip, resolvedWindow is consolidated so the denominator can't diverge, the timeline buffer is pruned on session removal, and the referential-stability test is in. Several of the AI-review comments are stale against this commit because of that.

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.
appendPoint keys on baseSessionId, but everything else in the listener (updateUsage, updateContextUsage, updateCycleTokens) keys on actualSessionId, which is deliberate so a hidden synopsis or batch run never touches the visible agent's gauge. For {sessionId}-synopsis-{ts} / {sessionId}-batch-{ts}, parseSessionId resolves baseSessionId to the parent, so those background runs would inject points into the agent's Context Timeline even though they ran in a separate process context. Suggest filtering on parsed.type (skip synopsis/batch, and probably group-chat) before appending, so the timeline only reflects the active agent/tab.

2. Worth confirming: Copilot output-only deltas. copilot-output-parser emits output-only usage deltas, and useBatchedSessionUpdates intentionally carries forward the prior input/cache snapshot for those. appendPoint records each raw delta as-is, so a Copilot run may show context dipping to just the latest output tokens between shutdown snapshots. Worth a look to confirm whether the carried-forward snapshot should feed the timeline point too.

3. Minor polish: per-row "%" label vs fill-bar width can disagree. The label uses p.percentage and the bar color keys off pct ?? ..., but the bar width uses contextTokens / contextWindow. When the provider-reported percentage diverges from that ratio, a row can show e.g. "50%" next to a bar filled to ~30%. Driving the width from pct when it's non-null (same source as the label and color) keeps each row self-consistent.

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/renderer/components/ContextTimelinePanel.tsx Outdated
Comment thread src/renderer/stores/sessionStore.ts
Comment thread src/renderer/components/ContextTimelinePanel.tsx
- 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/renderer/components/ContextTimelinePanel.tsx Outdated
Comment thread src/renderer/components/ContextTimelinePanel.tsx
Comment thread src/renderer/components/MainPanel/MainPanelHeader.tsx
Comment thread src/renderer/components/ContextTimelinePanel.tsx Outdated
… 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/renderer/components/ContextTimelinePanel.tsx (1)

153-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Icon-only header buttons lack accessible labels.

title alone 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 with aria-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

📥 Commits

Reviewing files that changed from the base of the PR and between b5a5810 and 0ce43cc.

📒 Files selected for processing (2)
  • src/renderer/components/ContextTimelinePanel.tsx
  • src/renderer/components/MainPanel/MainPanelHeader.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/components/MainPanel/MainPanelHeader.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/renderer/hooks/agent/internal/useAgentUsageListener.ts Outdated
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>
@chr1syy chr1syy added the ready to merge This PR is ready to merge label Jun 30, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +112 to +120
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@chr1syy

chr1syy commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in 35090c07c.

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:

  • normalizeUsageToDelta (StdoutHandler) now attaches the pre-normalization cumulative totals as absoluteUsage on the emitted UsageStats, but only on delta events (the first event is returned raw and is already absolute). The top-level fields stay per-turn deltas so token accumulation is unaffected.
  • The timeline listener plots contextTokens/percentage from absoluteUsage when present, and falls back to the per-turn stats for per-call providers (Claude/Copilot/OpenCode), whose values are already absolute for the turn. The point's per-turn token chips stay as the deltas (this turn's activity), so the panel now reads "this turn added X in / Y out, context now Z% full."

Added absoluteUsage? to the shared UsageStats type. Tests cover both the main-side snapshot (present on the delta event, absent on the first raw event) and the renderer plotting occupancy from the absolute totals rather than the delta. Type-check, prettier, eslint clean.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@chr1syy

chr1syy commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up review addressed in 95a57a48a:

  • Restrict absoluteUsage to Codex (:121) - normalizeUsageToDelta runs for Claude Code too, which can look monotonic for the first turns of a session. Claude's per-call values aren't cumulative occupancy, so attaching a snapshot there would let the timeline plot cumulative spend as fill. The snapshot is now gated on COMBINED_CONTEXT_AGENTS (i.e. only Codex).
  • Don't drop Codex output-only turns (:111) - the output-only skip now applies only when there's no absoluteUsage. A Codex output-only turn still carries an absolute snapshot reflecting real context growth, so it's recorded.
  • Coalesce duplicate Codex events (:131) - added a hasNoTurnActivity guard so the second update for a turn (token_count + turn.completed with identical cumulative totals → all-zero delta) no longer adds a duplicate row / repeated context point.
  • Focus visibility on the timeline trigger (MainPanelHeader:500) - the widget dropped the native outline with no replacement; added a focus-visible:ring-2 ring-accent ring so keyboard focus is visible.

Tests: Claude Code delta carries no absoluteUsage, Codex delta does; a Codex output-only turn is recorded from the absolute snapshot; a zero-delta Codex repeat is skipped. Type-check, prettier, eslint clean.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +68 to +72
const configuredWindow =
typeof sessionForUsage.customContextWindow === 'number' &&
sessionForUsage.customContextWindow > 0
? sessionForUsage.customContextWindow
: getModelContextWindowOverride(sessionForUsage.customModel) || 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@chr1syy

chr1syy commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the provider-configured window thread in 0a6b39b24.

Honor provider-configured context windows (useAgentUsageListener.ts:72) - you're right, the listener only checked the two sync sources while the gauge also resolves the provider config via the async getConfig, so an agent whose window is configured at the provider level only (e.g. OpenCode, no live-reported window) plotted against the static table. Added a small synchronous cache in contextWindowResolver (getCachedConfiguredContextWindow + ensureConfiguredContextWindowCached): the hot per-turn path reads the cache and primes it off-path, so getConfig never runs inline. The two sync sources still take precedence for the immediate case; the cached provider window closes the gap on subsequent turns. Test added (OpenCode, no override, unreported window sizes against the cached 300k once primed).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +84 to +87
const configuredWindow =
syncConfiguredWindow > 0
? syncConfiguredWindow
: getCachedConfiguredContextWindow(sessionForUsage);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready to merge This PR is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants