feat(opencode): per-session sidebar routing#47
Closed
iceteaSA wants to merge 3 commits into
Closed
Conversation
…-window policy, per-account reset credits) OpenAI removed the 5h usage window; the live wire now sends a single 7-day window in the primary slot with the window length on every source. Carry the window length onto the quota window type and derive the display label and pacing ruler from it instead of the slot name, render only the windows that are actually present, evaluate killswitch/quota policy across present windows only (an absent window is not-applicable, not unknown), and surface the wham-only rate_limit_reset_credits.available_count per account in the sidebar and the /openai-quota modal. Slot names primary/secondary remain the stable keys for storage, marks, thresholds, and mid-stream reset attribution.
There was a problem hiding this comment.
All reported issues were addressed across 22 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
…-window policy, per-account reset credits) OpenAI removed the 5h usage window; the live wire now sends a single 7-day window in the primary slot with the window length on every source. Carry the window length onto the quota window type and derive the display label and pacing ruler from it instead of the slot name, render only the windows that are actually present, evaluate killswitch/quota policy across present windows only (an absent window is not-applicable, not unknown), and surface the wham-only rate_limit_reset_credits.available_count per account in the sidebar and the /openai-quota modal. Slot names primary/secondary remain the stable keys for storage, marks, thresholds, and mid-stream reset attribution. A retired window is detected by its explicit zero-length marker, not by the absence of optional metadata.
The sidebar snapshot is a single machine-global file, but which account a session is routed to (activeId) and its routing mode are per-session facts. Storing them as one shared top-level value made concurrent sessions clobber each other last-writer-wins, so a session serving from a fallback could show main as active. Add a session-keyed activeRouting map inside the shared file: machine-global quota stays shared and any-writer-fresh, while each session owns its own routing entry. Only request-scoped writes upsert an entry (keyed by the request session header); background and boot writes touch machine-global fields only. Every write path reads-latest inside a serialized chain, rechecks the file immediately before commit and re-merges if a concurrent process wrote, merges only the writer's own key against the write-time clock, advances a monotonic version, and atomically renames. The TUI and the quota modal render their own session's entry and derive the account from the routing mode when it is absent or names a removed account. Entries are pruned by age, validity, and a cap on every write and on session deletion. Legacy top-level activeId/route are retained one release for older TUI halves.
iceteaSA
force-pushed
the
fix/per-session-sidebar-routing
branch
from
July 16, 2026 23:05
4bb65c4 to
3d528d1
Compare
Contributor
|
Superseded by the integrated implementation prepared for main. The final version adds fenced cross-process sidebar writes, Request-header support with fetch precedence, internal-header stripping, session-aware account dialogs, and failure propagation without breaking the write queue. Thank you for the implementation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch is stacked on #46 (dynamic quota windows) and forks from it, not from
main. Until #46 merges, the diff shown here includes #46's commits. Review the single commitper-session sidebar routing(or diff againstfeat/dynamic-quota-windows); once #46 merges I'll rebase ontomainso this reduces to the sidebar-routing change alone.Problem
The sidebar snapshot is a single machine-global file (
sidebar-state.json, shared by every OpenCode process/session on the machine). It mixes two kinds of state:activeId(which account this session is routed to) androute(this session's routing mode).Storing a per-session fact as one shared top-level value makes it last-writer-wins: a session on
main(or a boot-seed / background-refresh write that defaultsactiveIdtomain) overwrites the file, so another session that is actually serving from a fallback displaysmainas active. Operator-reported; the live file showed the contradiction directly (route: fallback-firstwithactiveId: main).pidkeying does not fix this — one process hosts many sessions (subagents, serve/attach), so a per-process file would rebuild the same collision one level down.activeIdis session-grain.Fix
Add a session-keyed
activeRouting: { [sessionId]: { activeId, route, updatedAt } }map inside the shared file.x-session-affinity → x-opencode-session → x-session-id → session-id). Background and boot writes touch machine-global fields only and never create a session entry — this structurally removes the boot/refresh clobber.session_id). When its entry is absent or names a removed/disabled account, it derives the account from the routing mode (fallback-first + an enabled fallback → that fallback; else main) — never a baremaindefault.session.deleted, so the write path is the durable GC) and onsession.deleted.activeId/routeare retained one release (written by request paths only, preserved by machine writes) so an older TUI half reading the shared file doesn't blank; marked deprecated.Testing
bun run build·bun run typecheck·bunx biome check src— clean.bun test— 638 pass / 0 fail (25 new tests: schema normalization, roster-based validity + prune, read-latest merge preserving foreign keys, header-precedence table, two-session isolation, headerless legacy compatibility, TUI keyed/absent/invalid-account derive, deletion cleanup, and a write-time-clock regression test proving a concurrent newer foreign entry survives a delayed upsert).Operator check after merge
Two sessions on different accounts: each sidebar shows its own active account + route (a fallback-routing session shows the fallback, not
main); a brand-new session with no entry yet shows the mode-derived account, notmain.Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Make sidebar routing per-session so each terminal/session sees its own active account and mode, fixing last-writer-wins clobbering in the shared sidebar state. This keeps quota machine-global while scoping
activeId/routeto the calling session.New Features
activeRouting: { [sessionId]: { activeId, route, updatedAt } }inside the shared file.session.deleted.activeId/routefor one release to support older TUIs.Migration
x-session-affinity→x-opencode-session→x-session-id→session-id.activeId/routein the next release.Written for commit 4bb65c4. Summary will update on new commits.