Skip to content

Add a Codex Usage tab to Settings, shown when a Codex login is detected; fix main-repo header branch and session-lookup sequencing - #398

Merged
parsakhaz merged 8 commits into
dcouple:mainfrom
Styrse:agent-status
Aug 17, 2026

Conversation

@Styrse

@Styrse Styrse commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a Usage tab to Settings (right after "AI & Agents") that shows the Codex subscription plan, remaining usage, and reset time for every Codex rate-limit bucket. The tab exists only when a Codex login is detected on the machine running Pane; otherwise Settings is unchanged. Nothing to enable, no preference to persist.

How it works

  • Data comes from codex app-server (JSON-RPC over stdio: initializeaccount/read with refreshToken: falseaccount/rateLimits/read), not from terminal-output parsing.
  • The probe (main/src/services/agentUsageService.ts) targets the host that runs Pane's daemon (local machine, or the remote host when connected remotely), with a 60 s cache and manual refresh. On Windows, if the host has no Codex login, every distinct WSL distribution Pane knows from its projects is probed next and the first available login wins. It is hardened against a missing/hung codex binary (12 s timeout, Windows process-tree kill, stdin EPIPE guards, WSL cwd quoting).
  • Only the plan type crosses IPC; account identity stays in the main process.
  • Detection = the same probe: provider codex reports available → tab shown; otherwise the tab is absent (a logged-out or missing Codex never adds chrome).

Also fixes (independent, kept from the earlier iteration of this PR)

  • Main-repository detail header no longer shows unknown when the synthetic main-repo session has no base branch — the branch is resolved through the existing project branch-detection IPC and keyed to the active project.
  • Main-repository session lookups are sequenced by request generation, so switching repos quickly can't render a stale repo's branch or leave the loading skeleton behind; a failed lookup clears the skeleton.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING.md guidelines
  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have run pnpm typecheck and pnpm lint locally
  • I have tested the Electron app locally with pnpm electron-dev

Critical Areas Modified

  • State management/IPC events

Tests

  • Unit (main): agentUsageService.test.ts (normalization, cache, WSL spawn shape, stdin-close safety, Windows tree kill), agentUsage.test.ts (host target, Windows WSL fallback order, refresh flag validation).
  • Playwright: tests/agent-usage.spec.ts — Usage tab present and populated when Codex is available (desktop nav + 640 px selector); absent when unavailable; refresh failure clears the snapshot; plus the three main-repository sequencing/skeleton tests. tests/settings.spec.ts unchanged and green.

Screenshots

Settings → Usage (Codex login detected) — see codex-usage-settings.png attached to the Playwright report; a screenshot from the running app will be added by the maintainer.

Additional Notes

  • Earlier iterations of this PR placed the widget in the per-pane detail sidebar behind a toolbar toggle. That placement was removed: subscription usage is account-level information, so it now lives in Settings and appears only when it has something to show. DetailPanel.tsx, PanelTabBar.tsx, and SessionView.tsx are byte-identical to main.
  • No dependencies or database schemas were changed. Related: Add Usage & Limits: tokens, cost and rate limits per agent CLI #383 (Usage & Limits) — this tab is intended to be the first occupant of that Settings surface, not a rival to it.

@parsakhaz parsakhaz changed the title Added Codex usage widget Add optional Codex usage widget to the detail panel; fix main-repo header showing 'unknown' branch Aug 16, 2026
Handle protocol pipe failures and wrapper process-tree cleanup, preserve WSL working directories, and clear stale renderer state. Consolidate the persisted widget preference and extend focused coverage.

Applied-by: Pane refactor pass
@parsakhaz

Copy link
Copy Markdown
Member

Applied refactor pass APL398A in 0be8fde.

Applied

Main process

  • main/src/services/agentUsageService.ts:153 now passes the pane's resolved Linux cwd into the WSL launch command.
  • main/src/services/agentUsageService.ts:208 now ends stdin gracefully, waits briefly for close, then uses bounded taskkill /T process-tree termination for Windows cmd.exe/wsl.exe wrappers before the final direct-kill fallback.
  • main/src/services/agentUsageService.ts:248 and :302 now make JSON-RPC writes settlement-aware, handle write callbacks and stdin error events (including EPIPE), avoid ending destroyed streams, and keep settlement idempotent.
  • main/src/services/agentUsageService.ts:341 documents the app-server handshake and paired account/rate-limit reads.

Renderer

  • frontend/src/hooks/useAgentUsage.ts:6 now keys snapshots to the active session and clears them on target changes and failures, preventing old percentages from appearing during a new load or beside an unavailable state.
  • frontend/src/hooks/usePersistedBoolean.ts:3, frontend/src/components/ProjectView.tsx:50, and frontend/src/components/SessionView.tsx:1352 consolidate the shared pane-agent-usage-visible preference.
  • frontend/src/components/ProjectView.tsx:115 clears branch state before detection and for invalid/failed results, preventing a prior repository's branch from leaking into the detail header.

Regression coverage

  • main/src/services/agentUsageService.test.ts:143 covers WSL cwd propagation, closed-pipe EPIPE, and Windows process-tree fallback.
  • tests/agent-usage.spec.ts:185 covers success→failure clearing; :208 covers session A→B clearing while the new snapshot loads; :235 covers invalid branch detection.

Skipped / left for decision

  • Pre-existing lint warnings marked “not against this PR” were left untouched. The final warning counts match the baseline: main 163, frontend 164, with 0 errors.
  • No sequencing/product change was made for the overlap with Add Usage & Limits: tokens, cost and rate limits per agent CLI #383's broader Usage & Limits work. That remains a decision for parsa (whether/how to sequence the two usage surfaces).
  • The main-repository branch correction is already called out separately in the PR description, so no additional PR-body edit was needed.

Checks

  • pnpm typecheck (Node 22): PASS
  • pnpm lint (Node 22): PASS — 0 errors; baseline warnings only
  • pnpm --filter main exec vitest run src/services/agentUsageService.test.ts src/ipc/agentUsage.test.ts: PASS — 9 tests
  • PLAYWRIGHT_PORT=46402 PANE_DIR=<isolated> pnpm test -- tests/agent-usage.spec.ts: PASS — 5 tests
  • git diff --check: PASS

Use the repository's Bash quoting helper for WSL working directories and key main-repository branch state to the active project and worktree. Replace the branch regression test with a valid-to-invalid repository transition.

Applied-by: Pane refactor pass (fix round)
@parsakhaz

Copy link
Copy Markdown
Member

Fix round APL398B applied in ef280b8.

The adversarial review caught two reproduced regressions from 0be8fde:

  • main/src/utils/wslUtils.ts:118: WSL cwd construction now uses the existing escapeForBash helper, so apostrophes produce the valid '\'' sequence. main/src/services/agentUsageService.test.ts:143 now covers /home/dev/o'connor/pane, and the generated command also passes bash -n.
  • frontend/src/components/ProjectView.tsx:33: branch state is keyed by both projectId and worktreePath, and a worktree is eligible only when the retained main-repo session belongs to the active project. This hides repository A's branch synchronously when switching to B, before passive effects or B's delayed session load. tests/agent-usage.spec.ts:257 now performs valid A (main-a) → invalid B and asserts the immediate and delayed renders never contain main-a.

Checks (Node 22): pnpm typecheck PASS; pnpm lint PASS with baseline warnings only; focused main Vitest PASS (9); isolated tests/agent-usage.spec.ts PASS (5); apostrophe command bash -n PASS; git diff --check PASS.

Windows taskkill /T remains mock-only coverage and was not verified on a native Windows/WSL host in this round.

Render main-repository details only from a session owned by the active project and worktree. Exercise production-shaped base branches across the full delayed repository transition window.

Applied-by: Pane refactor pass (fix round 3)
@parsakhaz

Copy link
Copy Markdown
Member

Final fix round APL398C applied in 578f3eb.

The round-2 adversary correctly found that ef280b8 keyed only the detected branch while detailSession could still pass through repository A's production-shaped session with baseBranch: 'main-a' during B's delayed load.

  • frontend/src/components/ProjectView.tsx:39 now derives the active main-repo session, worktree, and displayBranch solely from the active project/worktree target. A retained session owned by another project resolves to null, so SessionProvider cannot render its branch (:121).
  • tests/agent-usage.spec.ts:263 now uses production-shaped A/B sessions with base branches, delays B by 500 ms, and asserts no main-a at t=0, t=250 ms, or after B arrives; it then requires main-b after t>500 ms.

Verification: the rewritten test failed on ef280b8 at t=0 (expected false, received true) before the production change, then passed on 578f3eb. Full Node 22 checks also pass: workspace typecheck, workspace lint with baseline warnings only, 9 focused main tests, 5 isolated Playwright tests, and git diff --check.

Windows taskkill /T remains mock-only and was not verified on a native Windows/WSL host.

@parsakhaz

Copy link
Copy Markdown
Member

Hi @Styrse — nice first contribution, and thank you. Heads up that we pushed some fixes straight to your branch (maintainer edits were enabled) instead of a request-changes round. Three commits, all scoped to review findings:

  • 0be8fde — the main one: child.stdin.write was unguarded, so if codex exits mid-handshake (common where codex isn't usable) the main process threw an uncaught EPIPE. Writes are now settlement-aware with stdin error handling. Same commit: WSL cwd is passed through, Windows cmd.exe wrappers get a process-tree kill (taskkill /T) instead of only killing the shell, and the usage snapshot is keyed to the session and cleared on failure so old percentages don't render beside "unavailable". Regression tests for each.
  • ef280b8 — the WSL cwd change had injected an unescaped apostrophe into a single-quoted cd (paths like o'connor produced malformed bash); now uses the repo's escapeForBash. Also tightened the branch-state fix.
  • 578f3eb — the branch fix still leaked repo A's branch into repo B's header during a delayed load when sessions carried a real baseBranch; displayBranch is now derived purely from the active project/worktree, so a session owned by another project can't render. Test proven failing on the previous commit.

One thing left for you to know: the Windows taskkill path is covered by a mock test only — none of us verified it on a native Windows/WSL host. And a maintainer decision (not yours) remains on how this widget sequences with the broader Usage & Limits work in #383.

Typecheck, lint, focused tests all pass on the current head. Please pull before pushing again. The rest of your PR is untouched.

Discard stale async session responses and keep the active target in its
loading state until its own session arrives. Cover ordinary delayed switches
and rapid A-to-B-to-A sequencing with production-shaped sessions.

Applied-by: Pane refactor pass (fix round 4, root cause)
@parsakhaz

Copy link
Copy Markdown
Member

Root-cause fix applied in d73711b:

  • frontend/src/components/ProjectView.tsx:239: sequence main-repository session requests with a generation token, so responses and loading completions from inactive targets are discarded instead of overwriting the active target. The existing ownership derivation remains as defense-in-depth.
  • frontend/src/components/ProjectView.tsx:319 and :409: gate the provider on the active target's owned session and expose the loading skeleton while that session is in flight, avoiding the SessionProvider empty state.
  • tests/agent-usage.spec.ts:263 and :307: use production-shaped sessions to cover delayed A→B loading at t=0/t=250/>500 and the exact A→delayed B→A race, including detection of transient No session selected renders.

Both regression tests were proven against 578f3eb: the ordinary switch failed because the loading status was absent, and the rapid-switch case ended at No session selected. Both pass on d73711b.

Checks: pnpm typecheck passed; pnpm lint passed with existing warnings only; focused main Vitest passed (9 tests); focused Playwright passed (2 tests); full tests/agent-usage.spec.ts passed (6 tests); changed-file ESLint and git diff --check passed.

Windows taskkill /T remains mock-only and was not verified on a native Windows/WSL host.

Stop showing the loading skeleton after the active project's lookup settles
without a session, and guard the asynchronous session activation boundary.
Cover the rejected lookup path with a browser regression test.

Applied-by: Pane refactor pass (follow-up)
@parsakhaz

Copy link
Copy Markdown
Member

Follow-up applied in 99e855a:

  • frontend/src/components/ProjectView.tsx:38-40,245-268: loading state is owned by the active project, and the async setActiveSession() boundary is awaited and guarded by the same request generation token.
  • frontend/src/components/ProjectView.tsx:412-438: the skeleton now renders only while the active lookup is loading; a settled lookup without an owned session renders No session selected.
  • tests/agent-usage.spec.ts:361 / tests/electronApiMock.ts:515: added a forced-rejection regression test. It failed on d73711b with the skeleton still present (expected 0, received 1) and passes on 99e855a.

Checks: pnpm typecheck passed; pnpm lint passed with existing warnings only; the three session-transition Playwright tests passed; the full tests/agent-usage.spec.ts suite passed (7 tests); changed-file ESLint and git diff --check passed.

Subscription usage is account-level, so the widget leaves the per-pane
detail panel (toolbar toggle, persisted preference, auto-open) and becomes
a Settings tab shown only when the probe detects a Codex login. The probe
now targets the daemon host instead of a pane; service, hardening, hook,
widget, and the ProjectView fixes are kept. DetailPanel, PanelTabBar and
SessionView return to identical with main.
@parsakhaz parsakhaz changed the title Add optional Codex usage widget to the detail panel; fix main-repo header showing 'unknown' branch Add a Codex Usage tab to Settings, shown when a Codex login is detected; fix main-repo header branch and session-lookup sequencing Aug 17, 2026
@parsakhaz

parsakhaz commented Aug 17, 2026

Copy link
Copy Markdown
Member

@Styrse — thanks for this, and for the follow-up hardening rounds; the probe, the IPC plumbing, the widget, and your unit tests are all in as you wrote them.

I've pushed one more change to your branch, a placement move rather than a code-quality one. When I ran the PR, the sidebar design asked the user to discover three things — a gauge toggle on every panel's tab bar (including plain terminals), a persisted preference, and an auto-opening detail panel — to see one number, and that number is account-level, not per-pane. So the widget now lives in Settings → Usage, right after "AI & Agents", and the tab appears only when the probe detects a Codex login (nothing to enable; absent otherwise). That also gives #383's Usage & Limits work a natural home to converge into.

What changed:

  • New Settings "Usage" tab renders your AgentUsageWidget unchanged (only the sessionId prop went away). The tab is gated on the probe result.
  • agent-usage:get no longer takes a pane id — Settings has no pane, so it probes the daemon host (os.homedir(), no WSL context) with the same 60 s cache; on Windows it then falls back to each distinct WSL distribution Pane knows from its projects, so a WSL-only Codex login is still detected. agentUsageService.ts and its tests are untouched.
  • Removed the tab-bar toggle, pane-agent-usage-visible, usePersistedBoolean, and the showAgentUsage threading; DetailPanel.tsx, PanelTabBar.tsx, SessionView.tsx are back to identical with main.
  • Kept both of your ProjectView fixes (main-repo header branch; session-lookup sequencing + skeleton) and their three Playwright tests.
  • Playwright: removed the three sidebar tests (toggle/persistence and main-repo toggle — placement is gone, and the main-repo header assertion is already covered by your branch-detection test; per-session snapshot isolation — the probe is no longer session-keyed) and added Settings-tab tests (present + populated at desktop and 640 px, absent when unavailable, refresh failure).

Title/body updated to describe the result. Shout if anything in the move looks off to you.

A Codex login can live only inside WSL, so after the host probe the
handler tries each distinct WSL distribution Pane knows from its projects
and returns the first available snapshot; every target keeps its own cache.
@parsakhaz
parsakhaz merged commit 72862a9 into dcouple:main Aug 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants