Skip to content

Let the desktop app be a client of an existing T3 Code server - #9361

Closed
colonelpanic8 wants to merge 1 commit into
pingdotgg:t3code/codex-turn-mappingfrom
colonelpanic8:t3code/client-environment-suite
Closed

Let the desktop app be a client of an existing T3 Code server#9361
colonelpanic8 wants to merge 1 commit into
pingdotgg:t3code/codex-turn-mappingfrom
colonelpanic8:t3code/client-environment-suite

Conversation

@colonelpanic8

@colonelpanic8 colonelpanic8 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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 than main: the new orchestrator rewrites the client state and settings surfaces this change touches, so a main-based version would only conflict with it. The same series against main is kept at colonelpanic8/t3code:t3code/client-environment-suite-main for 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 with database 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.json a live server persists next to its database, exactly as t3 pair does, 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 discovery t3 pair and t3 triage already 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 --json against 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 model t3 pair already 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.

  1. feat: client-only desktop mode and environment-scoped settings, the mode, IPC, static renderer serving, web routing, presence scope, and settings pages.
  2. shared: move server runtime state reading into packages/shared, so the desktop can read runtime state without depending on the server package.
  3. cli: add --json to t3 pair, following the existing t3 auth ... --json convention.
  4. 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.
  5. 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.
  6. web: unify environment-scoped settings selection and presentation.
  7. 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 manageBuiltInDaemon setting turns the desktop into a pure client. Its desktop shells out to the bundled CLI for daemon status --json the way this PR shells out to t3 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-directives and vp fmt --check over every changed file: clean.
  • Focused vp test run over 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
Touches desktop startup, shutdown, custom protocol static serving, and local pairing subprocesses—areas where mistakes could cause double backends, failed launches, or unsafe pairing links.

Overview
The desktop app can run as a thin client instead of always spawning its own backend. Managed vs client-only mode is persisted in settings, overridable with --backend-mode, and on packaged launch the app auto-switches to client-only when it finds a live userdata server via existing server-runtime.json discovery—avoiding a second process on the same SQLite state.

In client-only mode, startup skips the backend pool, serves the renderer via a new static custom protocol (or dev proxy), opens the window on renderer readiness, and does not stop backends on quit. Connections gains discovery of local servers and pairing by shelling out to t3 pair --json, with strict validation of pairing URLs and origins.

IPC/preload expose backend mode state, mode changes (relaunch with rollback on failure), and local-server discover/pair. The web UI uses environment presence scope (whether the app owns a local backend) for remote indicators and sidebar grouping, plus helpers for pairing candidates in Connections settings.

Supporting changes: shared server runtime state helpers, server config path derivation by dev/userdata variant, and --json on the pair CLI for machine-readable output.

Reviewed by Cursor Bugbot for commit 8244310. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add desktop client-only backend mode to connect to an existing T3 Code server

  • Adds a DesktopBackendMode service supporting managed (default) and client-only modes, with CLI parsing (--backend-mode), persisted settings, and automatic fallback to client-only when a packaged launch detects an already-running userdata server
  • Adds DesktopRunningLocalServers service for discovering running local T3 servers from persisted runtime state and pairing with a selected environment via an Electron-as-Node child process
  • Adds IPC channels and handlers for backend-mode state reads/updates, local-server discovery, and local-server pairing, exposed to renderers through the desktop bridge
  • Introduces the full Orchestration V2 system: new provider adapters (Claude, Codex, Cursor, Grok, ACP Registry, OpenCode), projection/event/command-receipt stores, thread lifecycle/fork/launch/settlement services, checkpoint capture/rollback, effect outbox/worker, scheduled tasks, legacy V1 thread importer, and MCP orchestrator/worktree toolkits — replacing the V1 orchestration surface across server, web, and mobile clients
  • Migrates client-runtime connection to perform remote descriptor discovery with orchestration protocol negotiation and bounded thread snapshot loading
  • Risk: EnvironmentApi replaces the V1 orchestration dispatch/snapshot/stream surface with orchestrationV2; out-of-tree consumers of the prior V1 interface will break. The server no longer advertises serverUpdateThreadContinuation capability, meaning running threads are terminalized on restart. Cursor provider settings no longer expose binaryPath or apiEndpoint fields.

Macroscope summarized 8244310.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Sep 3, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $10.27, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

File Diff Size Estimate
apps/web/src/components/settings/SettingsPanels.tsx 44.71KB $2.24
apps/web/src/components/settings/ConnectionsSettings.tsx 32.23KB $1.61
apps/desktop/src/app/DesktopRunningLocalServers.ts 10.35KB $0.52

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

reason: "request_failed",
detail: "The local T3 Code pairing command returned an invalid pairing link.",
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev pairing URLs fail origin checks

Medium Severity

Local pairing validates the minted pairingUrl against the backend origin from server-runtime.json, but t3 pair builds that URL from devUrl (or another pairing base) when the server recorded one. Discovery lists dev variants, so pairing a local dev server fails the origin check and surfaces an invalid pairing link even though the CLI output is legitimate.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f7c89cb. Configure here.

Comment thread apps/desktop/src/app/DesktopRunningLocalServers.ts
Comment thread apps/desktop/src/app/DesktopLifecycle.ts
@macroscopeapp

macroscopeapp Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial desktop client mode spanning startup ownership, local process discovery and pairing, packaged asset serving, authentication, and environment-scoped settings. It also changes a product default, adds a static-analysis suppression, and leaves concrete Windows and development-pairing failure risks unresolved.

Not approved because:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

colonelpanic8 added a commit to colonelpanic8/dotfiles that referenced this pull request Sep 3, 2026
Assembly tag t3code-assembled/20260903T062942Z: the consolidated
client-only desktop suite behind pingdotgg/t3code#9361, rebased onto
the orchestration-v2 base.
…-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.
@colonelpanic8
colonelpanic8 force-pushed the t3code/client-environment-suite branch from f7c89cb to 8244310 Compare September 3, 2026 07:06
@colonelpanic8
colonelpanic8 changed the base branch from main to t3code/codex-turn-mapping September 3, 2026 07:06

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ 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.

packagedClientRootCandidates: [
path.join(input.appPath, "apps/server/dist/client"),
path.join(resourcesPath, "app.asar.unpacked/apps/server/dist/client"),
],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows client assets path missing

High Severity

Packaged client-only startup only looks for the renderer under appPath and app.asar.unpacked. On Windows the server tree that contains apps/server/dist/client lives in serverRoot (server.asar), so existing-server attach and client-only mode can fail fatally when resolving renderer assets.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8244310. Configure here.

@colonelpanic8
colonelpanic8 deleted the t3code/client-environment-suite branch September 3, 2026 07:25
@colonelpanic8

Copy link
Copy Markdown
Contributor Author

Continued as #9376 from the same commit (8244310) on the branch named for its base; GitHub closed this one when the head branch was renamed.

colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 6, 2026
…-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.
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 7, 2026
…-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.
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 7, 2026
…-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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop starts a second backend against the background service database

1 participant