Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e244a63
Implement server auth bootstrap and pairing flow
juliusmarminge Apr 6, 2026
15e85fa
Address review comments on auth pairing
juliusmarminge Apr 6, 2026
f89f0d1
Fix bootstrap credential handling and secret cleanup
juliusmarminge Apr 6, 2026
b7063db
Resolve auth bootstrap URLs from HTTP origins
juliusmarminge Apr 6, 2026
148127b
Handle secret removal failures explicitly
juliusmarminge Apr 6, 2026
06a2499
Use Effect clock for session expiry verification
juliusmarminge Apr 6, 2026
05370f6
Stub auth HTTP for browser app suites
juliusmarminge Apr 6, 2026
d3e19e0
Invalidate cached auth gate after pairing
juliusmarminge Apr 6, 2026
cadcd3a
Document server auth policies and pairing methods
juliusmarminge Apr 6, 2026
9ec9410
Add remote auth pairing bootstrap flow
juliusmarminge Apr 6, 2026
f2f2186
Simplify startup and pairing bootstrap
juliusmarminge Apr 6, 2026
4769f6b
Harden auth bootstrap for remote pairing
juliusmarminge Apr 6, 2026
0762645
test: split server test layer pipeline
juliusmarminge Apr 6, 2026
b619f66
fix: restore verification after stacked rebase
juliusmarminge Apr 7, 2026
0582da5
Add remote auth pairing for desktop backend
juliusmarminge Apr 7, 2026
432717a
Add persistent auth pairing and session management
juliusmarminge Apr 7, 2026
ab98aa3
Add remote auth pairing metadata and websocket tokens
juliusmarminge Apr 8, 2026
d4c00e9
Enable pairing link creation without endpoint guard
juliusmarminge Apr 8, 2026
2d302e3
Add pairing fallback dialog for clipboard failures
juliusmarminge Apr 8, 2026
ac02f15
Centralize browser API CORS handling
juliusmarminge Apr 8, 2026
2465a4e
Consolidate remote environment connection management
juliusmarminge Apr 8, 2026
6228c3f
Rename websocket client key parameters to entryKey
juliusmarminge Apr 8, 2026
13dd95b
Add remote environment pairing to branch toolbar
juliusmarminge Apr 8, 2026
4aaac11
Resolve server environment labels from host metadata
juliusmarminge Apr 8, 2026
a430dc3
Harden web test imports against partial window mocks
Apr 8, 2026
af0a97a
Fix browser CI suites
Apr 8, 2026
820f3fe
[codex] Handle missing host-label commands safely (#1840)
juliusmarminge Apr 8, 2026
72404da
[codex] Preserve connected state after metadata refresh failures (#1841)
juliusmarminge Apr 8, 2026
c54293c
[codex] Refresh grouped sidebar refs when membership changes (#1837)
juliusmarminge Apr 8, 2026
1f33591
[codex] Reject websocket tokens after session expiry (#1838)
juliusmarminge Apr 8, 2026
778af2c
[codex] Fix snapshot recovery across environment rebinds (#1839)
juliusmarminge Apr 8, 2026
5d14893
Add selectable desktop bind host for remote auth pairing
juliusmarminge Apr 8, 2026
c4e1914
Update server exposure test to use paired host payload
juliusmarminge Apr 8, 2026
e1f3afa
Add auth control plane for pairing and sessions
juliusmarminge Apr 8, 2026
716b147
Quiet auth CLI logs in JSON mode
juliusmarminge Apr 8, 2026
884a573
Harden draft promotion routing in chat view tests
juliusmarminge Apr 8, 2026
a59d3af
Use short manual-entry pairing tokens
juliusmarminge Apr 9, 2026
53327c7
nit
juliusmarminge Apr 9, 2026
a1d8197
Move pairing tokens to URL fragments
juliusmarminge Apr 9, 2026
932dc60
Increase pairing token length to 12 characters
juliusmarminge Apr 9, 2026
4ebbed9
Add session last-connected timestamps and pairing QR codes
juliusmarminge Apr 9, 2026
53e7f00
fmt
juliusmarminge Apr 9, 2026
aad7243
Fix 1768 replay lint warnings
juliusmarminge Apr 9, 2026
df2eb24
Harden desktop bootstrap and readiness polling
juliusmarminge Apr 9, 2026
807d623
Handle secret-store races and improve auth validation errors
juliusmarminge Apr 9, 2026
b834685
Preserve requested exposure through temporary fallback
juliusmarminge Apr 9, 2026
2611ed1
mv
juliusmarminge Apr 9, 2026
16a5be7
mv
juliusmarminge Apr 9, 2026
853d447
Separate stream failures from transport disconnect retries
juliusmarminge Apr 9, 2026
26fb28c
this sucks
juliusmarminge Apr 9, 2026
f8cec10
cleanup
juliusmarminge Apr 9, 2026
a77b157
Fix Vitest mocks to preserve actual module exports
juliusmarminge Apr 9, 2026
c521343
fix: use structured reason discriminant instead of fragile string com…
cursoragent Apr 9, 2026
63460ab
fix: use structured reason discriminant instead of fragile string com…
cursor[bot] Apr 9, 2026
8b6a17e
fix: lowercase user-agent in inferBrowser and inferOs to match inferD…
cursoragent Apr 9, 2026
3540496
Polish remote pairing flow and theme surfaces
juliusmarminge Apr 9, 2026
f8a544e
Refine no-thread state and browser chrome theming
juliusmarminge Apr 9, 2026
d9e96fd
mby?
juliusmarminge Apr 9, 2026
9daa4ec
mby
juliusmarminge Apr 9, 2026
21c748b
fix: stabilize browser CI test mocks
juliusmarminge Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 302 additions & 0 deletions .docs/remote-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
# Remote Architecture

This document describes the target architecture for first-class remote environments in T3 Code.

It is intentionally architecture-first. It does not define a complete implementation plan or user-facing rollout checklist. The goal is to establish the core model so remote support can be added without another broad rewrite.

## Goals

- Treat remote environments as first-class product primitives, not special cases.
- Support multiple ways to reach the same environment.
- Keep the T3 server as the execution boundary.
- Let desktop, mobile, and web all share the same conceptual model.
- Avoid introducing a local control plane unless product pressure proves it is necessary.

## Non-goals

- Replacing the existing WebSocket server boundary with a custom transport protocol.
- Making SSH the only remote story.
- Syncing provider auth across machines.
- Shipping every access method in the first iteration.

## High-level architecture

T3 already has a clean runtime boundary: the client talks to a T3 server over HTTP/WebSocket, and the server owns orchestration, providers, terminals, git, and filesystem operations.

Remote support should preserve that boundary.

```text
┌──────────────────────────────────────────────┐
│ Client (desktop / mobile / web) │
│ │
│ - known environments │
│ - connection manager │
│ - environment-aware routing │
└───────────────┬──────────────────────────────┘
│ resolves one access endpoint
┌───────────────▼──────────────────────────────┐
│ Access method │
│ │
│ - direct ws / wss │
│ - tunneled ws / wss │
│ - desktop-managed ssh bootstrap + forward │
└───────────────┬──────────────────────────────┘
│ connects to one T3 server
┌───────────────▼──────────────────────────────┐
│ Execution environment = one T3 server │
│ │
│ - environment identity │
│ - provider state │
│ - projects / threads / terminals │
│ - git / filesystem / process runtime │
└──────────────────────────────────────────────┘
```

The important decision is that remoteness is expressed at the environment connection layer, not by splitting the T3 runtime itself.

## Domain model

### ExecutionEnvironment

An `ExecutionEnvironment` is one running T3 server instance.

It is the unit that owns:

- provider availability and auth state
- model availability
- projects and threads
- terminal processes
- filesystem access
- git operations
- server settings

It is identified by a stable `environmentId`.

This is the shared cross-client primitive. Desktop, mobile, and web should all reason about the same concept here.

### KnownEnvironment

A `KnownEnvironment` is a client-side saved entry for an environment the client knows how to reach.

It is not server-authored. It is local to a device or client profile.

Examples:

- a saved LAN URL
- a saved public `wss://` endpoint
- a desktop-managed SSH host entry
- a saved tunneled environment

A known environment may or may not know the target `environmentId` before first successful connect.

### AccessEndpoint

An `AccessEndpoint` is one concrete way to reach a known environment.

This is the key abstraction that keeps SSH from taking over the model.

A single environment may have many endpoints:

- `wss://t3.example.com`
- `ws://10.0.0.25:3773`
- a tunneled relay URL
- a desktop-managed SSH tunnel that resolves to a local forwarded WebSocket URL

The environment stays the same. Only the access path changes.

### RepositoryIdentity

`RepositoryIdentity` remains a best-effort logical repo grouping mechanism across environments.

It is not used for routing. It is only used for UI grouping and correlation between local and remote clones of the same repository.

### Workspace / Project

The current `Project` model remains environment-local.

That means:

- a local clone and a remote clone are different projects
- they may share a `RepositoryIdentity`
- threads still bind to one project in one environment

## Access methods

Access methods answer one question:

How does the client speak WebSocket to a T3 server?

They do not answer:

- how the server got started
- who manages the server process
- whether the environment is local or remote

### 1. Direct WebSocket access

Examples:

- `ws://10.0.0.15:3773`
- `wss://t3.example.com`

This is the base model and should be the first-class default.

Benefits:

- works for desktop, mobile, and web
- no client-specific process management required
- best fit for hosted or self-managed remote T3 deployments

### 2. Tunneled WebSocket access

Examples:

- public relay URLs
- private network relay URLs
- local tunnel products such as pipenet

This is still direct WebSocket access from the client's perspective. The difference is that the route is mediated by a tunnel or relay.

For T3, tunnels are best modeled as another `AccessEndpoint`, not as a different kind of environment.

This is especially useful when:

- the host is behind NAT
- inbound ports are unavailable
- mobile must reach a desktop-hosted environment
- a machine should be reachable without exposing raw LAN or public ports

### 3. Desktop-managed SSH access

SSH is an access and launch helper, not a separate environment type.

The desktop main process can use SSH to:

- reach a machine
- probe it
- launch or reuse a remote T3 server
- establish a local port forward

After that, the renderer should still connect using an ordinary WebSocket URL against the forwarded local port.

This keeps the renderer transport model consistent with every other access method.

## Launch methods

Launch methods answer a different question:

How does a T3 server come to exist on the target machine?

Launch and access should stay separate in the design.

### 1. Pre-existing server

The simplest launch method is no launch at all.

The user or operator already runs T3 on the target machine, and the client connects through a direct or tunneled WebSocket endpoint.

This should be the first remote mode shipped because it validates the environment model with minimal extra machinery.

### 2. Desktop-managed remote launch over SSH

This is the main place where Zed is a useful reference.

Useful ideas to borrow from Zed:

- remote probing
- platform detection
- session directories with pid/log metadata
- reconnect-friendly launcher behavior
- desktop-owned connection UX

What should be different in T3:

- no custom stdio/socket proxy protocol between renderer and remote runtime
- no attempt to make the remote runtime look like an editor transport
- keep the final client-to-server connection as WebSocket

The recommended T3 flow is:

1. Desktop connects over SSH.
2. Desktop probes the remote machine and verifies T3 availability.
3. Desktop launches or reuses a remote T3 server.
4. Desktop establishes local port forwarding.
5. Renderer connects to the forwarded WebSocket endpoint as a normal environment.

### 3. Client-managed local publish

This is the inverse of remote launch: a local T3 server is already running, and the client publishes it through a tunnel.

This is useful for:

- exposing a desktop-hosted environment to mobile
- temporary remote access without changing router or firewall settings

This is still a launch concern, not a new environment kind.

## Why access and launch must stay separate

These concerns are easy to conflate, but separating them prevents architectural drift.

Examples:

- A manually hosted T3 server might be reached through direct `wss`.
- The same server might also be reachable through a tunnel.
- An SSH-managed server might be launched over SSH but then reached through forwarded WebSocket.
- A local desktop server might be published through a tunnel for mobile.

In all of those cases, the `ExecutionEnvironment` is the same kind of thing.

Only the launch and access paths differ.

## Security model

Remote support must assume that some environments will be reachable over untrusted networks.

That means:

- remote-capable environments should require explicit authentication
- tunnel exposure should not rely on obscurity
- client-saved endpoints should carry enough auth metadata to reconnect safely

T3 already supports a WebSocket auth token on the server. That should become a first-class part of environment access rather than remaining an incidental query parameter convention.

For publicly reachable environments, authenticated access should be treated as required.

## Relationship to Zed

Zed is a useful reference implementation for managed remote launch and reconnect behavior.

The relevant lessons are:

- remote bootstrap should be explicit
- reconnect should be first-class
- connection UX belongs in the client shell
- runtime ownership should stay clearly on the remote host

The important mismatch is transport shape.

Zed needs a custom proxy/server protocol because its remote boundary sits below the editor and project runtime.

T3 should not copy that part.

T3 already has the right runtime boundary:

- one T3 server per environment
- ordinary HTTP/WebSocket between client and environment

So T3 should borrow Zed's launch discipline, not its transport protocol.

## Recommended rollout

1. First-class known environments and access endpoints.
2. Direct `ws` / `wss` remote environments.
3. Authenticated tunnel-backed environments.
4. Desktop-managed SSH launch and forwarding.
5. Multi-environment UI improvements after the base runtime path is proven.

This ordering keeps the architecture network-first and transport-agnostic while still leaving room for richer managed remote flows.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Verify preload bundle output
run: |
test -f apps/desktop/dist-electron/preload.js
grep -nE "desktopBridge|getWsUrl|PICK_FOLDER_CHANNEL|wsUrl" apps/desktop/dist-electron/preload.js
grep -nE "desktopBridge|getLocalEnvironmentBootstrap|PICK_FOLDER_CHANNEL|wsUrl" apps/desktop/dist-electron/preload.js
release_smoke:
name: Release Smoke
Expand Down
3 changes: 2 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"bun.lock",
"*.tsbuildinfo",
"**/routeTree.gen.ts",
"apps/web/public/mockServiceWorker.js"
"apps/web/public/mockServiceWorker.js",
"apps/web/src/lib/vendor/qrcodegen.ts"
],
"sortPackageJson": {}
}
Loading
Loading