Repository navigation
Remote mode: wire client adoption into connections, trim what only works locally - #182
Merged
Merged
Conversation
… core Client adoption, MCP editing, the static scan and the file watcher change files on this machine, so they now run in the desktop app. The tw-adopt and tw-scan crates move here from core (src-tauri/crates/, one workspace with the app; the unused tw-config dependency is dropped). - New Tauri commands replace the control-plane endpoints: list_clients, plan_adopt, adopt_client, plan_restore, restore_client, diagnose_client, prepare_client_key, mcp_targets, plan_mcp, apply_mcp, scan_clients, gateway_base, plus delete_key and rotate_key (the adopted-client check and the key sync need this machine's files). restore_all no longer asks core. - Core is still asked for two things only: the gateway port (Overview) and keys (Keys, ClientKey). The gateway host is a single function so remote mode can swap it; retarget_adopted() repoints every adopted client. - The watcher runs in the app: `local-event` (clients_changed / scan_alert) to the webview, scan alerts into the notice bus; the first scan is a baseline. Core's own ClientsChanged/ScanAlert are ignored. - Types for these commands live in src-tauri/src/wire.rs and generate src/generated/lite-api.ts; names identical to tw-api.ts are imported. - Message codes the app emits itself are listed in src-tauri/msg-codes.txt (generated from source); translations are checked against it plus core's. - The webview endpoint whitelist drops the moved endpoints, and DeleteKey, RotateKey and ClientKey now go only through Rust commands. Works against the pinned core v0.46.0 (its endpoints just go unused). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ransport stubbed) The connection-management shell from the remote-core plan (P5, part 1): - Connection list persisted by the app itself (connections.json in the data dir): built-in, undeletable "This Mac" plus remote entries. Keys live in the OS keychain (keyring crate), never in the file and never sent back to the UI. - One connector seam (connection::connector): test/connect returns ServerInfo or a ConnectError (unreachable / closed / wrong key / version mismatch / timeout). The remote handshake is a single stub, connector::handshake, that P2 fills in with the Noise handshake; until then it answers "not available yet" after the TCP connect. - ControlClient is retargetable; every clone follows the current connection. In remote mode the local core stops once in-flight requests finish and the watchdog does not restart it; switching back starts it. Never falls back to local on its own. - Launch screen waits at most ~8 s, then the window opens in a "not connected" state (data pages disabled, Settings usable, retry with backoff, edit, switch to this Mac). Version-mismatch page. Mid-session disconnect shows a banner, makes the content read-only, raises one notification and reconciles through the existing core-state/events_dropped path. - Launch guard: two launches in a row that never got ready, or holding Option/Alt at launch, show a small connection picker first. - Sidebar switcher, Settings > Connection section, add/edit dialog with test / save / save and switch, switch confirmation with the "also point these clients at the server" option (clients::retarget_adopted_clients is a TODO hook until adoption moves into lite). - Menubar/tray "Connection" submenu on all platforms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e clients list The menu bar's copy-address action still read gateway_base from the /clients endpoint, which core is dropping. It now uses the same function as the clients and keys pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS the keychain prompts for the login password for this item (again after each update of an unsigned app), which is not worth it for a key that can be copied from the server again. There is now one code path on every platform: connection-keys.json in the data directory, created 0600 from the start (temp file with the mode, then rename), in a directory created private (0700 on unix, protected DACL on Windows; ported from core's private_dir). Kept apart from connections.json so the frequent list writes never touch the secret and the code that reads the list cannot see it. Keys still never go to the webview. The keyring crate and all keychain copy are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m into connections
Client adoption now lives in the app, so the two TODO hooks from the
connections work get their real implementations:
- switch_preflight counts the adopted clients still pointing at this
machine's gateway from the app's own view of the files, so it answers
even while the local core is stopped.
- Switching with "also point these clients at <server>" runs
clients::retarget_adopted against the new core after the switch is
committed (keys are issued by that core) and returns what was and was
not repointed (switch_connection now returns { view, retargeted }).
- clients::gateway_host is the host of the active remote connection, so
adopting, copying an endpoint and the menu bar write the server's
gateway; 127.0.0.1 locally.
- retarget_clients repoints the adopted clients still pointing at this
machine once connected to a remote core (the Clients page button).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s locally
Following mockup ⑧ and the remote-mode table in the plan:
- Clients and MCP pages carry a fixed note that they change this Mac's
files, pointing clients at the server's gateway; the server's clients
are not affected.
- Adopted clients still pointing at the local gateway are flagged on their
row ("points to the local gateway") and above the table, with a one-click
"Point at <server>"; what was and was not repointed is listed per client.
The switch-confirm dialog shows the same report when some failed.
- Settings split into "the app on this Mac" and "configuration on <server>"
(listen, retention, and a note that the remote control listener and key
can only be changed on the server). The diagnostics bundle is hidden; About
names the connection and the server's core version; uninstall says the
server is not affected. Local mode keeps its layout.
- ChatGPT sign-in offers only the device code while remote (the browser
callback can only reach the machine running core); Z.ai is unchanged.
- ${NAME} in upstream headers and the "system proxy" choice say they are
the server's environment.
- The sidebar and the test result show the gateway clients should use: the
host the app dialled plus the server's gateway port, not its bind address.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te-mode-ui # Conflicts: # src-tauri/Cargo.lock # src-tauri/src/lib.rs # src/connection/connection.i18n.tsx
This was referenced Sep 24, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked branch. It contains #181 (
feat/connections) and #180 (feat/adopt-in-app) merged in, so until those two land this diff includes their commits. Merge order: #180 → #181 → this PR. GitHub will reduce this PR to its own changes (the merge commitcabdc8dand commit47de817) once #180 and #181 are indev. The P2 handshake branch (feat/control-handshake) is stacked on #181 separately; I did not touchcontrol.rs,connector.rs, the supervisor or key handling.Wiring adoption into connections (replaces #181's TODO hooks)
switch_preflight): the adopted clients whose configured endpoint is this machine (127.0.0.1/localhost/[::1]) are counted from the app's own view of the files (clients::adopted_pointing_at_local). It no longer asks the local core, so it still answers when that core is stopped.connection::switchrunsclients::retarget_adopted(&control, &server_host)against the new core, which issues each client's key. Only clients still pointing at this machine are changed.switch_connectionnow returns{ view, retargeted: { synced, failed } | null }. If any failed, the dialog stays open and lists each client with its reason. If all succeeded, a toast names them. A failure does not undo the switch.clients::gateway_host(state)returns the host of the active remote profile while remote, and127.0.0.1locally. The port comes from the core's configured gateway port. Adopt, copy endpoint, the keys page and the menu bar's copy-address all go through it.retarget_clients: the one-click repoint on the Clients page. It does nothing in local mode.Remote-mode trimming (mockup ⑧ and the plan's remote-mode table)
Clients and MCP pages carry a fixed note: they check and change this Mac's client files and point them at
<server>'s gateway; clients on the server are not affected.Clients still pointing at the local gateway are marked on their row ("指向本机网关 127.0.0.1:8788", in the "未生效" state). A line above the table has a 改为指向 button, and per-client results show under it.
Settings has two groups in remote mode:
The diagnostics bundle is hidden. About shows the connection and the server's core version instead of the local core binary. Uninstall says the server is not affected; it only restores this Mac's clients and data. Local mode keeps its current layout and order.
ChatGPT sign-in offers only the device code while remote, with a one-line reason. Z.ai is unchanged.
${变量名}in upstream headers and the "system proxy" option in the proxy selects say they use the server's environment.The sidebar gateway address and the connection test result show the dialled host + the server's gateway port (
clientGateway), not the server's bind address (0.0.0.0:8788).The local-only environment variables that
user_envloads apply only when the app starts the local core, which is already local-only, so nothing changed there.The plan also mentions an "open data folder" action, but the app has none, so there is nothing to hide.
Copy is in
src/connection/remote.i18n.tsx(zh + en). Remote state comes from theconnectionview #181 already emits, throughuseRemote().Checks
cargo fmt --check,cargo clippy --all-targets -D warnings,cargo test,pnpm typecheck,pnpm testcontrol.remote.*,config.remote_*) are not in the pinned core yet, so their translations are left to the P2 branch.🤖 Generated with Claude Code