Add coding-plan usage tracking and fix OpenCode backend issues - #157
Add coding-plan usage tracking and fix OpenCode backend issues#157DavidSegun wants to merge 6 commits into
Conversation
Orchestration backends (Claude Code, Codex, OpenCode + Z.AI GLM) run on
subscription plans that meter usage over a rolling ~5-hour window, but
qmax-code never tracked it, so it was unclear how much was used or when the
plan would stop.
Add a time-based PlanWindow tracker (internal/api/planwindow.go) that opens on
the first orch turn, accumulates turns/tokens, rolls over after the window
elapses, and can be marked exhausted from a provider limit hit. Surface it in
the input status bar and a new /plan command (also summarized in /status and
/cost). Window length is configurable via plan_window_hours (default 5).
Also close two backend blind spots that fed the same problem:
- OpenCode and Codex now report per-turn token usage (previously CC-only),
feeding both session cost and the plan window.
- OpenCode `{type:error}` events were being silently dropped; they are now
surfaced, and a 429 / usage-limit refusal is detected as a plan-limit hit,
reading the reset time from rate-limit headers when present.
Build + vet clean; new planwindow unit tests pass. Codex/OpenCode token-field
names are parsed defensively pending confirmation against a live successful run.
…er tests Verified against live `opencode run --format json` output (opencode 1.0.105): successful turns emit a trailing status-code-less UnknownError (an internal schema-validation gripe), and this version emits no token-usage event in the stream. Surfacing every error event would therefore spam a scary line after every good turn, so only errors carrying an HTTP status code (auth/quota/5xx) and detected usage-limit hits are shown by default; status-code-less events appear only in verbose mode. Adds opencode_stream_test.go driving the parser with the real captured streams: success (renders answer, no false limit, no invented tokens), a 429 limit hit (flags the window + reads Retry-After), a 403 subscription error (surfaced but not a limit), and forward-compat token extraction for both field shapes.
… supports it
opencode 1.x removed the `run --auto` flag and now governs tool approvals
through the config `permission` block. qmax-code passed --auto unconditionally,
so on current opencode every turn failed: opencode printed its usage and exited
1 with no output ("opencode exited with error: exit status 1"), making the
OpenCode backend (incl. Z.AI GLM coding plans) unusable.
Probe `opencode run --help` once and only append --auto when the flag is still
advertised, preserving behavior on older opencode while working on 1.x. The
managed config's permission block already enforces the standard/unattended
policy, so nothing is auto-approved that was not before.
Verified against opencode 1.0.105: without --auto the run produces the answer
and the turn completes.
On Windows, npm installs opencode as a `.cmd` shim. Go's os/exec runs `.cmd` files through cmd.exe, which swallows the `--` end-of-flags separator and drops the positional message that follows it — opencode then aborts with "You must provide a message or a command" and exits 1, so no OpenCode turn (including Z.AI GLM / OpenRouter coding plans) can run on Windows. Pass the message without `--` on Windows (sanitizeCCUserPrompt already strips control bytes, so a lone positional is safely taken as the message); keep `--` on other platforms where it protects a message starting with "-". Verified via a Go exec repro: with `--` the message is dropped, without it the turn runs.
Sigilix OverviewEffort: 4/5 (large) Quality gates
Summary — latest pushAdds rolling 5-hour coding-plan usage tracking for subscription backends (Claude Code, Codex, OpenCode) with a new Important files
Sequence diagramsequenceDiagram
participant User
participant REPL
participant PlanWindow
participant OpenCodeAgent
User->>REPL: /cc or /opencode turn
REPL->>PlanWindow: planWindowFor(backend)
REPL->>OpenCodeAgent: Run(userMsg)
OpenCodeAgent-->>REPL: result / error
OpenCodeAgent-->>REPL: LastTurnStats() / LastPlanLimit()
REPL->>PlanWindow: Record(now, turnIn, turnOut)
alt Plan limit hit (429)
REPL->>PlanWindow: MarkExhausted(now, reset)
end
REPL->>User: /plan or status bar shows window state
Confidence: 4/5The core plan-tracking logic is well-isolated in a pure data type with thorough tests, and the OpenCode fixes address specific known failure modes, though the single-goroutine assumption on PlanWindow and the cached --auto probe warrant a quick check.
Suggested labels: Suggested reviewers: @Desperado
|
|
| Gate | Result |
|---|---|
| 🔍 AI diff review | ✅ Clean · gemini-3.1-flash-lite · completed · 13 eligible / 3 reviewed · gemini-3.1-flash-lite |
| 🔍 SAST | completed · 16 eligible / 16 reviewed · gemini-3.1-pro-preview |
| 🔍 Canonical PR review delivery | completed · 0 eligible / 0 reviewed · exact-head review #4857181274 and overview #5182387349 confirmed |
| 🧪 Repo Tests | ✅ 579/579 passed (go) |
| 🤖 AI Tests |
Powered by QualityMax — AI-Powered Test Automation
Review findings — request changesI found four correctness issues in the new plan-tracking paths:
Verification
|
There was a problem hiding this comment.
Hey @DavidSegun , please check the comments above.
Resolves the review on Quality-Max#157, which had been outstanding since 2026-07-25. 1. BLOCKER — limit hits could bypass exhaustion tracking. LastPlanLimit() was read only inside `if err == nil`, but a limit refusal that produces no assistant output makes the CLI subprocess exit non-zero, so the usual 429 path set the agent's limit flag and never called MarkExhausted. The bookkeeping now runs whether or not the turn errored: a successful turn still advances the window, and the limit state is consumed either way. Extracted as recordPlanTurn so the behaviour is unit-testable rather than buried in the REPL closure. 2. Provider reset times now roll the window over. Record() rolled over only after WindowLen, so when a provider reported an earlier authoritative reset the first accepted turn after it landed in the old exhausted window and the UI kept showing a limit that no longer applied. Rollover now uses the window's effective ResetAt(). 3. Live backend switching no longer mixes independent quotas. One tracker was allocated per session while /cc, /codex, and /opencode switch plans mid -session, combining turns and exhaustion state across separate subscriptions. Trackers are now keyed by quota, with OpenCode keyed per provider since its providers meter separately. 4. OpenCode no longer undercounts multi-step turns. The parser overwrote lastTurnIn/lastTurnOut on every usage-bearing event, keeping only the final step; opencode emits usage on each step-finish, so tool loops lost every earlier step. Usage now accumulates across steps, taking one canonical payload per event (the four shapes are the same numbers in different places) and skipping any step id already counted. Tests: two-step and repeated-step parser tests, provider-reset rollover boundary tests, failed-run exhaustion tests, and quota-keying tests. go build, go test ./..., and go vet ./... all pass.
# Conflicts: # CHANGELOG.md
|
@DavidSegun I've pushed fixes for the four review findings directly to this branch (your Happy to hand any of these back if you'd rather do them your way. 1. BLOCKER — limit hits bypassing exhaustion tracking. The plan bookkeeping now runs whether or not the turn returned an error. A refusal that produces no assistant output exits non-zero, so reading 2. Provider reset times now roll the window over. 3. Live backend switching no longer mixes quotas. Trackers are keyed per subscription via 4. OpenCode multi-step undercount. Usage now accumulates across steps instead of being overwritten. Two details worth flagging: the four usage shapes ( Tests added
|
QualityMax ReviewVerdict: COMMENT · Confidence: evidence-backed scan Files eligible: 16 · Files reviewed: 16 · Files with findings: 0 · Findings: 0 · Inline cards: 0 Priority findings
Review gates
Important files
Change diagram — Flowflowchart TD
subgraph RunLoop [CodexAgent.Run Loop]
A[scanner.Scan] --> B[inspectCodexEvent]
B --> C{Parse JSON Event}
C --> D[Update Token Stats]
C --> E{Check Error/Limit}
E -- Limit Hit --> F[Set lastLimitHit = true]
E -- No Limit --> G[Continue]
A --> H[extractCodexMessage]
H --> I[term.StreamText]
end
Review lifecycleUse the inline cards to inspect evidence and suggested remediation. Re-run the QualityMax review after pushing a fix; unchanged cards are identified by their stable finding marker. Dismiss with a reason through the existing QualityMax/GitHub review feedback flow. 0 prior card(s) are stale/resolved on this head. Proof legend: VERIFIED independently judged patch · REPRODUCED verified finding · GROUNDED deterministic evidence · MODEL-ONLY model judgment. QualityMax project results are available in the configured project. Receipt · commit |
Adds rolling coding-plan usage tracking with a new /plan command, status bar updates, configurable window duration (plan_window_hours), and automatic handling of plan exhaustion.
Also fixes several OpenCode issues discovered during implementation:
Testing