Let the desktop app be a client of an existing T3 Code server - #9376
Conversation
|
Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting). This review would cost an estimated $58.10, which exceeds your per-review limit of $10.00. The top 3 files driving up this estimate:
Tip To get this pull request reviewed, you can:
|
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces a broad desktop client-mode and existing-server pairing capability with substantial cross-cutting startup, lifecycle, authentication, protocol, and settings changes. Unresolved concerns include valid local pairing URLs being rejected and relaunches potentially being queued after shutdown failure, so the changes require human review. Not approved because:
Review your spending limits in Billing settings, or comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8244310. Configure here.
| reason: "request_failed", | ||
| detail: "The local T3 Code pairing command returned an invalid pairing link.", | ||
| }); | ||
| } |
There was a problem hiding this comment.
Pairing rejects valid local URLs
High Severity
Local pairing treats a t3 pair --json link as invalid unless its origin equals the discovered backend origin. t3 pair builds that link from devUrl or resolveHeadlessConnectionString, which is localhost or a LAN address when the server did not bind an explicit host, while persisted origin is 127.0.0.1. Pairing with the default local server therefore fails after the CLI succeeds.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 8244310. Configure here.
| }), | ||
| Effect.forkDetach, | ||
| Effect.asVoid, | ||
| Effect.tapError((error) => logLifecycleError(error.message, { error })), |
There was a problem hiding this comment.
Relaunch queued before shutdown
Medium Severity
Packaged relaunch now calls electronApp.relaunch() before shutdown. setBackendMode rolls settings back if that later shutdown fails, but Electron has already queued a restart and there is no way to cancel it. The next quit then relaunches unexpectedly with the restored mode, after quitting has already been set.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8244310. Configure here.
Assembly tag t3code-assembled/20260903T080821Z: base moved to upstream #2829's head, every carried topic rebased onto it, and the client suite carried as the head branch of pingdotgg/t3code#9376.
5b21d4f to
8244310
Compare
b82facd to
2ac9bfe
Compare
Press ArrowUp in an empty composer to get the last prompt sent in this thread. ArrowUp again goes further back, ArrowDown comes forward, and going past the newest prompt clears the composer. History is per thread and derived from the thread's user messages on the keypress, so nothing new is stored or synced. Send-time appends (terminal and element context, preview annotations, review comments, the ultrathink prefix) and app-composed sends are stripped. With text in the composer, arrows move the caret unless the caret is on the first or last visual line of an unedited recalled prompt. Modifiers, IME composition, menus, approvals, and pending questions take priority. Attachments are not restored. Prior art: pingdotgg#1778 by @PratyushChauhan, pingdotgg#4336 by @mfazekas, pingdotgg#7952 by @sethwebster. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…g#9964) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tgg#10021) Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>
…gdotgg#9754) Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>
…10324) Co-authored-by: Claude Code <noreply@anthropic.com>
…gdotgg#10305) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…g#10130) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…-scoped settings Let the desktop app run as a client of an existing T3 Code server. - A packaged desktop app that finds a live server owning its userdata state directory (via server-runtime.json, pid, and environment descriptor, exactly as t3 pair does) runs that launch as a client instead of picking another port and starting a second backend against the same database (pingdotgg#6097). - A persisted backend mode (managed | client-only) with a launch-only --backend-mode override, exposed over IPC. Client-only bootstrap skips the backend pool, serves the packaged renderer from static assets (or the dev-server proxy), opens the window on renderer readiness, and does not stop backends on quit. - Connections lists servers discovered through runtime state and pairs by running the bundled server's `t3 pair --json` against that state directory. Runtime-state reading moves to packages/shared so the desktop can use it; `t3 pair` gains --json. - The web app treats a client-only desktop like the hosted static app: no primary environment, and a presence scope decides which environments are remote. Server-owned settings address an explicitly selected environment through one shared header; client-owned settings are grouped separately; any connected client can manage an environment's pairing links and authorized clients. Squashed port of colonelpanic8/t3code:t3code/client-environment-suite (seven commits on upstream main d42254d, PR pingdotgg#9361) onto the orchestrator branch at d2f1f51. Settings that only exist on main (diff layout, proactive panels, panel animations) are not referenced.
8244310 to
d27c7a0
Compare


Let the desktop app be a client of an existing T3 Code server
Fixes #6097. Supersedes #4444 and folds in #4496 and #4559.
Based on #2829's branch (
t3code/codex-turn-mapping) rather thanmain: the new orchestrator rewrites the client state and settings surfaces this change touches, so amain-based version would only conflict with it. The same series againstmainis kept atcolonelpanic8/t3code:t3code/client-environment-suite-mainfor reference.The problem
Since
t3 service(#4286, #6286) a T3 Code server can outlive the desktop app. But the desktop app still assumes it owns the machine's backend: on launch it sees port 3773 busy, picks 3774, and starts a second backend against the same~/.t3/userdata/state.sqlite. Two servers then contend for one SQLite database and requests fail withdatabase is locked(#6097). Users in that thread asked for the obvious alternative, a thin client, and explained why the browser PWA is not a substitute: browser hotkeys conflict and several settings are unavailable outside the app.The mobile app already works this way. It owns no backend and pairs with whatever environments it is given. This PR gives the desktop app the same option.
What changes
The desktop app never starts a second backend against a state directory that already has one. On launch it reads the
server-runtime.jsona live server persists next to its database, exactly ast3 pairdoes, confirms the pid is alive and the process answers with the environment descriptor recorded next to it, and if so runs this launch as a client of that server instead of spawning its own. This is the same discoveryt3 pairandt3 triagealready rely on, so there is no new on-disk format and no new HTTP surface. Connections explains why the app is in this state and offers to pair with the running server.Client-only mode. Users can also opt into this permanently (Settings → Connections → Backend mode) or per launch (
--backend-mode client-only). In client-only mode the app serves its packaged renderer from static assets, opens the window when the renderer is ready rather than when a backend is, and does not stop any backend on quit. A client-only app with no saved connections lands on Connections instead of a dead primary-backend state.Pairing with a server on this computer. Connections lists servers discovered through their runtime state. Pairing mints a one-time credential by running the bundled server's
t3 pair --jsonagainst that state directory, which writes into the running server's own database. Same-user proof is filesystem access to the state directory, which is the trust modelt3 pairalready uses. Works on every platform.Settings target an environment. A client with no backend of its own has nothing to call "primary", so settings that live on a server (environment general settings, providers, source control, keybindings, diagnostics) are addressed to an explicitly selected environment through one shared header that shows the same loading/offline/error state everywhere, and settings that belong to the client (general, appearance) are grouped separately in the navigation. Restore-to-defaults resets only the keys a page owns. Any connected client can manage pairing links and authorized clients for that environment, not only for a locally spawned one.
Commits
The series is squashed onto the orchestrator branch as one commit; the original seven-commit series is on the
main-based reference branch and is meant to be read in that order.feat: client-only desktop mode and environment-scoped settings, the mode, IPC, static renderer serving, web routing, presence scope, and settings pages.shared: move server runtime state reading into packages/shared, so the desktop can read runtime state without depending on the server package.cli: add --json to t3 pair, following the existingt3 auth ... --jsonconvention.desktop: discover local servers through runtime state and pair via t3 pair, replacing an earlier Linux-only advertisement scheme that needed a new unauthenticated endpoint.desktop: attach to a running server instead of spawning a second backend, the [Bug]: Desktop starts a second backend against the background service database #6097 fix.web: unify environment-scoped settings selection and presentation.web: drop per-environment accent colors and display names, cosmetic client preferences split to a follow-up.Prior art
Paseo, another local-first agent runner, settled on this shape: the daemon runs detached, the desktop app reuses a daemon that is already running rather than starting another, and a
manageBuiltInDaemonsetting turns the desktop into a pure client. Its desktop shells out to the bundled CLI fordaemon status --jsonthe way this PR shells out tot3 pair --json.Not in this PR
Per-environment accent colors and custom display names, which were part of earlier revisions, are split out to keep this reviewable. Auto-pairing on attach is a one-line follow-up once the approach is agreed; this PR asks for one explicit Pair click.
Verification
vp run --filter @t3tools/desktop --filter t3 --filter @t3tools/web --filter @t3tools/contracts --filter @t3tools/client-runtime --filter @t3tools/shared typecheck: clean.vp lint --report-unused-disable-directivesandvp fmt --checkover every changed file: clean.vp test runover apps/desktop (app, ipc, settings, electron, backend pool, exposure, updates, window), apps/server (cli, auth, startup access, server), apps/web (settings, environments, presence, grouping, hooks), packages/client-runtime (connection), packages/shared, packages/contracts: 132 files, 1307 tests, all passing.Note
High Risk
Changes desktop startup, shutdown, and custom-protocol serving (including static file handling and pairing subprocess trust), which are security- and data-integrity-sensitive paths.
Overview
Adds a managed vs client-only desktop backend launch path so the app can run without spawning its own server and can reuse one that is already running.
Launch and lifecycle: Settings and
--backend-moderesolve the effective mode; packaged managed launches auto-switch to client-only when a live userdata server is found via persisted runtime state (same discovery ast3 pair). Client-only bootstrap registers the custom protocol as a dev proxy or static packaged renderer, opens the window on renderer readiness, skips managed backend startup/shutdown, and surfaces fatal errors for invalid mode flags or missing packaged assets.Local server UX: New IPC discovers running servers and pairs by shelling out to bundled
t3 pair --json, with strict validation of pairing URLs. Changing backend mode persists settings and triggers a shutdown-then-relaunch sequence (with rollback if relaunch fails).Web: Remote/local thread and sidebar grouping use an environment presence scope instead of comparing everything to the primary id; client-only desktop hides managed local bootstraps. Connections settings logic gains local-server pairing candidates and safer pairing base URLs per connection type.
Shared/server: Server runtime state read/write moves to
@t3tools/shared;t3 pairgains--jsonfor machine-readable output.Reviewed by Cursor Bugbot for commit 5b21d4f. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add desktop
client-onlybackend mode to connect to an existing T3 Code serverDesktopBackendModeservice withmanagedandclient-onlymodes; a packaged managed launch with a live userdata server now falls back to client-only, and CLI arguments can override the persisted modepair --jsonCLI command; the desktop IPC exposes these to the rendererFirstRunGateandWelcomeWizardonboarding flow that handles connection, agent setup, and project import, including direct pairing, relay connections, and agent-session scanning/importchooseLoadBalancedEnvironmentscores connected environments by CPU/memory health and weights, andLoadBalancingSettingspersists per-environment preferencesUsageLimitsPooled) that merges native and hub accounts, shows remaining quota, and exposes reset-credit actions through popoverspaircommand now supports--jsonand existing callers relying on human-readable-only output are unaffected, butTerminalOpenInput/TerminalAttachInput/TerminalRestartInputschemas gained an optionalproviderInstanceIdfield that older servers will not send;readBrowserClientSettingsnow propagatesLocalStorageOperationErrorinstead of returning null, which may surface errors previously swallowedMacroscope summarized d27c7a0.