Make PID cleanup conditional on the exiting child still owning the PID file so a stale exit cannot erase a replacement tunnel's PID. Only null the in-memory process when the exiting child is current. Explicit disable keeps unconditional cleanup.
Spread params into better-sqlite3 Statement.run/get/all so positional ? placeholders bind correctly. better-sqlite3 accepts positional args, not an array, so binding crashed whenever a query had parameters. Matches the bun:sqlite and node:sqlite adapters.
Add separate model selectors for Grok Build main, general-purpose,
explore, and plan agents. Each override gets an independent 9Router
custom-model slot and context_window derived from 9Router model
capabilities. Preserve and restore pre-existing config on reset.
Add pxpipe as an experimental fifth Token Saver: Claude-format request
bodies above a configurable size threshold are rendered as dense PNGs
via the pxpipe-proxy library API (transformAnthropicMessages) before
dispatch, cutting estimated input tokens by ~35-60% on token-dense
contexts. Integration follows the Headroom pattern: applied to the final
body in chatCore just before dispatch, fail-open on any error/timeout.
Managed npm install into DATA_DIR/pxpipe, dynamic loader with per-version
cache-bust, JSONL event log with rotation, /api/pxpipe/* endpoints, Token
Saver card (marked experimental) + /dashboard/pxpipe page, and per-request
Activated/Skipped annotation in Request Details. Disabled by default.
Add round-robin/random proxy pool rotation for no-auth free providers
(e.g. OpenCode Free) to distribute load across all active pools and
avoid per-IP rate limits. Rotation strategy is selectable per provider
in NoAuthProxyCard and persisted to settings.providerStrategies.
- add Headroom extras status + install endpoints
- show Headroom version + code/ml extras in Token Saver UI
- fix Windows interpreter selection to read from env with headroom-ai
- Backup DB only on real SCHEMA_VERSION change, not every app version bump
- Kill idle MCP stdio bridge children to prevent orphan process leaks
- Add getDistinctProviders to avoid loading every row JSON blob (OOM fix)
- Update codex model list (gpt-5.6 sol/terra/luna, drop 5.3 codex variants)
- Reorder Claude default models (fable first)
Co-authored-by: Cursor <cursoragent@cursor.com>
Only update an existing Codex OAuth row when both rows share the same
chatgptAccountId, so a second Codex login no longer overwrites the first
account's rotated token pair. Also fall back to
workspaceId || chatgptAccountId || accountId for the chatgpt-account-id header.
Normalize every provider to one cache-inclusive convention via
canonicalizeUsage() before persist, and price cached + cache_creation as
subsets of prompt_tokens in calculateCostFromTokens() to stop
double-counting. usageRepo now delegates cost math to a single source.
Surface Cached tokens/cost across dashboard (overview, tokens, cost,
details). Merge Claude message_start cache with message_delta output so
cache counts survive. Compatible LLM nodes now allow multiple API-key
connections (key pool).
Co-authored-by: Cursor <cursoragent@cursor.com>
Add Kimchi as a browser-token OAuth provider routed through its
OpenAI-compatible gateway. Discover live models for /v1/models and
provider models, normalize Claude-compatible requests, and wire up
provider connection tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
- mask API keys in usage stats/history responses (apiKeyMasked)
- validate proxy URL scheme + reject shell metachars before env write
- escape HTML in OAuth callback page to prevent XSS
- atomic O_EXCL lock file to prevent TOCTOU race in MITM startServer
- set mitmIsRestarting guard synchronously before any await
Co-authored-by: Cursor <cursoragent@cursor.com>
Treat configured Headroom proxy as running when its /health endpoint
responds, even if local headroom CLI is not installed. Dashboard
Start/Stop stays limited to local loopback proxies while external
Docker sidecars can be enabled via HEADROOM_URL.
Closes#1948
Co-authored-by: Cursor <cursoragent@cursor.com>
Build on the optional Headroom Token Saver from Carmelo Campos
(PR: feat: add optional Headroom token saver). Add managed start/stop
of the local headroom proxy from the dashboard, install detection,
status probing, and a simplified Token Saver UI.
- detect headroom CLI + python>=3.10, probe proxy /health
- spawn/stop proxy as a detached, pid-tracked process
- /api/headroom/{status,start,stop} routes, gated local-only in dashboardGuard
- one-click Start/Stop Headroom modal, no manual config needed
- claude<->openai shape conversion for /v1/compress via 9router translators
Thanks to Carmelo Campos (@carmelogunsroses) for the original Headroom integration.
Co-authored-by: Cursor <cursoragent@cursor.com>
Reject non-AWS region values before interpolating them into upstream
URLs and stop reflecting upstream response bodies to the client.
Co-authored-by: Cursor <cursoragent@cursor.com>
getAdapter() is async; without await, db was a Promise so db.all() threw
and the outer try/catch returned [] silently — usage logs endpoints
(/api/usage/logs, /api/usage/request-logs) always returned empty data.
Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds long-lived API-key (ksk_) authentication for Kiro/AWS CodeWhisperer
and a direct claude:kiro / kiro:claude translation route that avoids the
lossy OpenAI two-hop pivot.
- translator: claude-to-kiro request + kiro-to-claude response translators,
registered on the exact source:target pair (direct route ahead of the
OpenAI pivot in index.js). claude-to-kiro uses shared schema constants
(ROLE/CLAUDE_BLOCK/DEFAULT_IMAGE_MIME) per app convention.
- auth: POST /api/oauth/kiro/api-key imports + validates a key via
ListAvailableProfiles, persists authMethod="api_key" (no refresh token).
- executor: send tokentype: API_KEY header and try *.amazonaws.com hosts
first for api-key creds; OAuth keeps kiro.dev first.
- fix: never inject the default placeholder profileArn for api-key auth
(CodeWhisperer 403s an ARN not owned by the key's account).
- ui: API Key method in the Kiro connect modal; surface api-key accounts
on the Quota Tracker and provider count.
- stream: env-overridable TTFT vs stall timeouts + Kiro keepalive frame.
- tests: claude-kiro-direct + kiro-profile-arn (11 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
clientId/clientSecret của antigravity + gemini bị lặp 3 nơi
(registry, usage.js, src/lib/oauth). Gom vào shared.js
(ANTIGRAVITY_OAUTH_CLIENT, GOOGLE_OAUTH_CLIENT), các file spread vào.
Byte-for-byte: PROVIDERS/alias/oauth-url equal, golden 142 pass.
Thêm test guard nội dung + alias resolution.
Co-authored-by: Cursor <cursoragent@cursor.com>
- /api/settings/database now requires current password (header for GET,
body for POST) in addition to session; CLI-token requests exempt
- add verifyDashboardPassword helper reusing login bcrypt check
- profile UI prompts password via modal before export/import
- /v1/web/fetch rejects internal/private/metadata targets via assertPublicUrl
Refs GHSA-qvfm-67h2-2qfx, GHSA-qj3v-64wj-q825
Co-authored-by: Cursor <cursoragent@cursor.com>
Restore Cowork feature while closing the RCE vector: drop user-defined
stdio commands so only hard-coded preset plugins (browsermcp) may spawn.
Custom MCP now accepts remote URL only. Routes stay gated to localhost.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add custom-server.js: inject unspoofable socket IP, strip client XFF
(wired into Docker CMD + CLI spawn + build-cli copy)
- loginLimiter: key on trusted x-9r-real-ip, TRUST_PROXY opt-in, global fallback
- Force password change on first remote login while default is in use
- Add /api/auth/reset-password (local-only) so CLI reset writes live SQLite
- CLI settings: reset via API instead of stale db.json
- Fix OAuth modals opening duplicate browser tabs on add-connection
- Add cli:pack / cli:publish scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
Tailscale installed via apt/snap/brew was reported "Not Installed/Logged In"
because 9Router only probed its custom userspace socket. Probe the system
socket (/var/run/tailscale/tailscaled.sock) as fallback, add /usr/sbin and
/snap/bin to candidate paths and EXTENDED_PATH, and report separate
customDaemonRunning/systemDaemonRunning flags. Caching/non-blocking behavior
preserved.
Co-authored-by: Stefan Pirker <stefan.pirker86@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a "Bulk Add" button on /dashboard/providers/codex that imports
multiple OAuth accounts at once by pasting a JSON array, single object,
or { accounts: [...] } wrapper.
- New endpoint POST /api/oauth/codex/bulk-import (serial loop, no token echo)
- New BulkImportCodexModal component with JSON textarea + success/failure summary
- Persist idToken/lastRefreshAt on first insert via OPTIONAL_FIELDS
- Backfill email/chatgptAccountId/chatgptPlanType from JWT when missing
- Derive expiresAt from expiresIn when missing
- Gated to providerId === "codex" only; other providers unaffected
Co-authored-by: Cursor <cursoragent@cursor.com>
AWS OIDC IDC/Builder-ID tokens omit profileArn, so CodeWhisperer calls
return 403 "User is not authorized". Resolve it natively via the
ListAvailableProfiles API instead of reading Kiro IDE profile.json.
- providers.js: add fetchKiroProfileArn() and resolve on poll (new logins)
- tokenRefresh.js: backfill profileArn on refresh so existing IDC
connections self-heal without re-login
Co-authored-by: Cursor <cursoragent@cursor.com>
Kiro IDE changed its API endpoint from q.us-east-1.amazonaws.com to
runtime.us-east-1.kiro.dev. Old domains kept as fallback.
- mitm/config.js: add new hosts to TARGET_HOSTS + getToolForHost()
- cliTools.js: update mitmDomain to new endpoint
- mitmToolHosts.js: prepend new domain (keep legacy for compat)
- dataDir.js: on Windows ignore Unix-style DATA_DIR paths
Co-authored-by: joutvhu <joutvhu@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Convert isTailscaleLoggedIn to cached non-blocking getter and turn
isTailscaleRunningStrict / isTailscaleLoggedInStrict into async execAsync
probes. The status poll no longer blocks the event loop, so dashboard
navigation stays responsive while tunnel/tailscale checks run.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add VIRTUAL_IFACE_REGEX to filter utun/awdl/bridge from network fingerprint
- Trust cloudflared/tailscale while process is alive, never kill on force restart
Co-authored-by: Cursor <cursoragent@cursor.com>
Add shared OAuth credential lifecycle manager with provider-aware refresh
decisions. Implement CodexExecutor.refreshCredentials so 401/403 retry
refresh works for Codex, track lastRefreshAt and refresh before the
upstream stale-token window, preserve omitted idToken, and add
per-connection single-flight refresh to avoid refresh-token rotation races.
Merged from PR #1664.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add qmodel_latest to QODER_MODEL_MAP
- Expose qmodel_latest in static Qoder provider catalog (qd)
- Generalize executor comment so model set does not go stale
- Add unit coverage for the new model key + catalog
Closes#1638
Co-authored-by: Cursor <cursoragent@cursor.com>
Move device-flow / poll / userinfo / parseExpiry from src/lib/qoder/auth.js
into a QoderService class at src/lib/oauth/services/qoder.js, matching how
KiroService is organized. Also re-add the QoderService re-export from
services/index.js.
The split now mirrors Kiro:
src/lib/oauth/services/qoder.js OAuth flow (was auth.js)
src/lib/qoder/cosy.js Per-request signing (unchanged)
src/lib/qoder/encoding.js WAF-bypass body (unchanged)
src/lib/qoder/constants.js Endpoints + model map (unchanged)
Behavior is unchanged — same functions, same signatures, just relocated
into a class so the import path lines up with `import { QoderService } from
"@/lib/oauth/services"` like every other OAuth provider. parseExpiry is now
a static method so callers and tests can use it without instantiating.
42 tests still pass; build still clean.