* feat: add cache-aware token usage dashboard
Track normalized Claude, Codex, and ACP usage with incremental SQLite backfill. Exclude imported transcript history, rebuild usage after history rewrites, and expose an owner-only dashboard with cache-aware totals and breakdowns.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix: preserve usage model and local dates
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix: normalize cached usage and timezone buckets
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
---------
Co-authored-by: HAPI <noreply@hapi.run>
* test: reproduce issue #864
Assert Cursor error paths emit agent error payloads and web UI renders
them as warning-styled events instead of neutral session messages.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): surface agent errors with warning styling in web UI (closes#864)
Route Cursor stderr, init, prompt, and legacy exit failures through
sendAgentMessage({ type: 'error' }) and teach the web chat layer to
render error events with a warning icon instead of neutral info text.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): requeue user message on transient agent exit (auth, rate limit)
cursorLegacyRemoteLauncher.runMainLoop popped a user message off the queue
before spawning `agent` and silently discarded it whenever `agent` exited
non-zero (auth expiry, rate limit, transient network). The wrapper logged
the failure at debug level only, never surfaced it to the web UI, and
emitted `ready` as if a normal turn had ended.
Capture stderr from the spawned process; classify exit-1 with a transient
signature (Authentication required, rate limit, ETIMEDOUT, ECONNRESET,
EAI_AGAIN) as recoverable; re-head the message via `queue.unshift`, surface
a friendly banner via `sendSessionEvent({type:'message',...})`, and backoff
~2s before the loop picks it up again. Cap at 5 consecutive transient
failures, after which the message is dropped with a clear "resolve and
resend" event so we never spin forever on a genuinely broken auth.
Non-transient non-zero exits also surface the stderr to the UI now (instead
of only the local ring buffer), so a real crash is visible to the operator.
Backoff is overridable via CURSOR_LEGACY_TRANSIENT_BACKOFF_MS for tests.
Tests cover: success path, transient auth requeue + banner, rate-limit
banner, non-transient crash surfaced without requeue, and the 5-failure
drop cap.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): preserve slash-command isolation on requeue; wait for stderr flush
Two findings from the cold-review bot on the requeue path:
1. `enqueueCursorUserMessage` uses `pushIsolated` for pass-through slash
commands (e.g. `/compress`) so they never batch with sibling prompts.
The transient-requeue path used plain `unshift`, which dropped the
isolate bit and allowed the next collected batch to merge the slash
command with a sibling - changing command semantics. Add
`MessageQueue2.unshiftIsolated` and use it when the popped batch was
isolated or when `parseCursorSpecialCommand` recognises the message.
2. `runAgentProcess` resolved on `child.on('exit', ...)`. Node may emit
`exit` while the stderr pipe is still draining, so a fast "auth
required" error printed-and-exited could be classified as
non-transient with empty stderr and silently drop the user message -
the exact bug this PR was supposed to fix. Resolve on `close` instead,
which waits for stdio streams to flush.
Adds a unit test that requeues `/compress` after a transient auth failure
and asserts the second spawn still receives the slash command alone (not
batched with a sibling).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): restrict transient retry to exit code 1 only
Upstream codex review #823 (Minor): the helper treated any non-zero exit
with matching stderr as transient, which could requeue a signal-killed
(SIGTERM 143, SIGKILL 137) or crashed (SIGABRT 134) process whose stderr
happens to contain a keyword like "rate limit". Documented contract is
exit-1-for-transient; tighten the classifier accordingly.
Adds regression test covering exit 143 + rate-limit stderr → no retry.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): clean up transientBackoff abort listener on timer completion
Upstream codex review #823 (Minor): transientBackoff added an abort
listener with { once: true } but only removed it when the abort fired.
Because the launcher reuses one AbortController, repeated transient
retries accumulated stale listeners until the next abort.
Switch to a single completion path that clears the timer AND removes the
abort listener whichever side wins.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): cap in-memory stderr capture at 8 KB
Upstream codex review #823 (Minor): runAgentProcess accumulated every
stderr chunk for the full child lifetime. A noisy `agent` failure could
grow CLI process memory without bound even though only the first 400
chars are ever displayed. Cap the retained copy at 8 KB; debug log of the
full stream is unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli,web,hub): migrate Cursor remote sessions to ACP with model/effort pickers
Move stream-json remote launcher to legacy path and add ACP launcher with
set_config_option model/mode sync, optimistic keepalive on config changes, and
shared catalog caching. Web gets dual base/effort Cursor pickers for session and
new-session flows; hide composer status bar when Cursor sends no usage_update.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,web,shared): Cursor model picker — ACP wires + CLI sku variants
Enrich the web/mobile picker with agent --list-models SKUs grouped under
ACP wire bases, fix session-open base highlight, and keep catalog discovery
safe while the ACP transport holds the CLI lock.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor-acp): apply ACP default model when web resets to Default
Web sends model: null for Default; push session/set_config_option with the
ACP default[] wire so Cursor backend matches hub state. Regression tests
for setModel(null) and applyModelConfig(null).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(acp): clear stale agent-acp lock when owning process is gone
Check lock pid with signal 0; remove orphaned lock dirs after SIGKILL or
crash so listCursorModels can run cold probes again. Regression tests for
guard and catalog discovery.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(cursor): use live pid for ACP lock handler tests
Stale-lock cleanup clears dead pids; handler tests must simulate an
active lock with the current process pid to avoid cold probes/timeouts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(acp): scope agent CLI lock guard to Cursor agent command only
Gemini/OpenCode/Kimi ACP sessions must not register agent-acp-active;
that blocked listCursorModels while unrelated backends were running.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,web): reject Cursor model changes for local sessions
Hub returns 409 when controlledByUser is set, matching Codex. Web hides
model and variant pickers for local Cursor sessions so users do not hit
a dead RPC path. Document pre-push-review in AGENTS.md.
Verified: bun typecheck; bun run test (919 cli + 243 hub + 768 web + 46 shared).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): send stable ids for Cursor ask_question replies
Parse and submit question.id and option.id so ACP receives keys like
{ approach: ['a'] } instead of index/label. Verified: bun typecheck && bun run test.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>