Skip to content

refactor!: move client adoption, MCP editing and the client scan to the desktop app - #186

Merged
fylorn merged 2 commits into
mainfrom
feat/drop-adopt-scan
Sep 24, 2026
Merged

fylorn merged 2 commits into
mainfrom
feat/drop-adopt-scan

Conversation

@fylorn

@fylorn fylorn commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Phase P3 of the remote-core plan, core side. Adopting AI clients, editing their MCP servers and scanning their configuration all change files on the machine the desktop app runs on. With remote core that may not be the machine running core, so this work moves to the app: ThinkWatch-Lite takes over the tw-adopt and tw-scan crates and the file watcher in ThinkWatchProject/ThinkWatch-Lite#180. Merge that PR first. It stops calling these endpoints and still works against v0.46.0.

Removed

  • The tw-adopt and tw-scan crates
  • tw-control: scan.rs (scan endpoint and client-config watcher); clients.rs except POST /clients/{id}/key; ControlState.home and home_dir()
  • tw_api::ep: Scan, Clients, PlanAdopt, Adopt, PlanRestore, Restore, Why, McpTargets, McpPlan, McpApply, plus every type only they used (ScanRequest/Response, ScanFinding, McpView, SkillView, HookView, McpOp*, McpTargetView, ClientsResponse, DetectedClient, ManualClient, ManualSetup, AdoptRequest/Response, PlanView, FieldChange, FieldOp, FindingView, FindingLevel, TakesEffect, Verification, KeySynced, KeySyncFailed)
  • The Event::ClientsChanged and Event::ScanAlert events
  • The twcore scan and twcore clients subcommands
  • Key rotation no longer writes the new key into an adopted client (KeyRotated is just { version, key }), and key deletion no longer checks for an adopted client. The app does both, because only it can see the client's files.
  • tw-control/tests/m4_acceptance.rs: its checks (adopt, restore byte for byte, zero-width detection, never deleting) moved with the crates.

Kept

  • POST /clients/{id}/key. It returns the key already bound to that client or creates one bound to it (claude-code, then claude-code-2 if that name is taken). Core no longer holds the client list, so it accepts any id that looks like a client id (lowercase letters, digits, -, at most 64 characters). The app decides which clients exist. With no gateway key at all it still answers control.no_keys.

Contract

  • Rebased onto feat(control): one control key and a Noise handshake on every control connection #184 (Noise handshake, which took 18): CONTROL_API_VERSION 18 → 19, with a doc entry. The handshake test in tests/gate.rs now checks against CONTROL_API_VERSION instead of a hardcoded 18.
  • msg-codes.txt drops adopt.*, scan.*, control.key_bind_failed and control.key_used_by_client. Lite now keeps its own src-tauri/msg-codes.txt for the codes it emits, including control.key_used_by_client. When lite bumps to this core, it must delete the control.key_bind_failed translation, drop the two scan_alert/clients_changed cases in src/types.ts, and remove the transitional sync handling in rotate_key. I checked this by building lite against this branch with a [patch]: only those spots break.

Docs and tests

  • README / README.zh-CN crate layers and CONTRIBUTING now say where adoption lives.
  • scripts/smoke.sh checks the client-key endpoint (issued once, then the same key again) instead of the adopt round trip.
  • New tw-guard/tests/reads_only.rs: the two files the client scan uses (hidden.rs, tools/rules.rs) cannot write or delete anything. The scan's own copy of this check moved with it.
  • The remaining replay/diagnostics tests from tests/adopt.rs are now in tests/bundle.rs. The client-key tests are in tests/client_key.rs.

Local run: cargo fmt --check, cargo clippy --workspace --all-targets -D warnings and cargo test --workspace all pass; scripts/smoke.sh passes 56, fails 0 (after the rebase).

Crate and binary versions are unchanged and will be set at merge time.

🤖 Generated with Claude Code

fylorn and others added 2 commits September 24, 2026 23:54
…he desktop app

Adopting a client, editing its MCP servers and scanning its configuration
change files on the machine the desktop app runs on, which need not be the
one running core. The desktop app now does all of it (it took the tw-adopt
and tw-scan crates and the file watcher), so core drops them:

- crates tw-adopt and tw-scan
- tw-control's scan.rs and the client-config watcher; clients.rs keeps only
  POST /clients/{id}/key, which issues a client its own gateway key. It no
  longer knows the client list, so it accepts any id shaped like one
  (lowercase letters, digits, `-`) and leaves recognising clients to the app
- endpoints /scan, /clients, /clients/plan, /clients/adopt,
  /clients/{id}/restore/plan, /clients/{id}/restore, /clients/{id}/why,
  /mcp/targets, /mcp/plan, /mcp/apply, their types, and the ClientsChanged
  and ScanAlert events
- rotating a key no longer writes the new value into an adopted client
  (KeyRotated loses synced/failed), and deleting a key no longer checks
  for an adopted client; the app does both on its side
- ControlState.home and tw_control::home_dir, twcore's `scan` and `clients`
  subcommands, the M4 acceptance test (its checks moved with the crates)

CONTROL_API_VERSION goes to 18. msg-codes.txt loses the adopt.*/scan.*
codes plus control.key_bind_failed and control.key_used_by_client; the app
lists the ones it now emits itself. tw-guard gains a test that the two
files the client scan uses cannot write or delete anything (the scan's own
copy of that check moved with it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…version

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn
fylorn force-pushed the feat/drop-adopt-scan branch from 1b871dd to 28d1256 Compare September 24, 2026 15:59
@fylorn
fylorn merged commit 9ba4989 into main Sep 24, 2026
4 checks passed
@fylorn
fylorn deleted the feat/drop-adopt-scan branch September 24, 2026 17:01
fylorn added a commit that referenced this pull request Sep 24, 2026
…ide follows the CLI

- The manual now checks listen.control.remote against RemoteListen instead of
  declaring it pending: port is required (written at random, 20000-32000, by
  twcore init and twcore remote enable), bind defaults to all, allow_from to
  the private ranges, enabled to false. Regenerated both languages.
- docs/server.md and its Chinese version use `twcore remote enable/disable`,
  show what `twcore control-key` prints on stdout and stderr, and say what a
  remote connection cannot do, how throttling works, and that narrowing
  allow_from or rotating the key closes open connections.
- CONTROL_API_VERSION is 20 on top of #186's 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fylorn added a commit that referenced this pull request Sep 24, 2026
… channel (#187)

* feat(control): a remote control port, opened in addition to the local channel

listen.control.remote { enabled, bind, port, allow_from } opens a TCP port for
a desktop app on another machine. It is separate from the unix socket /
Windows loopback channel and uses the same key and Noise handshake.

- The port has no default: twcore init writes the section closed with a random
  port (20000-32000, never the gateway's), and `twcore remote enable|disable`
  opens and closes it with minimal YAML edits. Validation rejects port 0, the
  gateway's port, and malformed allow_from entries (new config.* codes).
- The listener follows config reloads: enable, disable, bind and port changes
  apply live (a new address is bound before the old one is dropped; a failure
  keeps the old one and is reported). Closing or moving the port closes the
  connections made through it. It never takes down the local channel or the
  gateway.
- Before the handshake: sources outside allow_from are closed without a byte
  (loopback is not waved through), a source with 5 failed handshakes in a
  minute is ignored for a minute, and at most 32 remote connections are open.
- Remote connections speak HTTP/1.1 only and carry a task-local marker; they
  get 403 for POST /shutdown, the diagnostic bundle, and any config write that
  changes listen.control (control.remote.* codes).
- Status gains remote_control { enabled, addr, error, allow_from, reachable }
  and gateway_reachable; CONTROL_API_VERSION is 19.
- twcore control-key also prints (to stderr) where remote control listens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(control): revoked sources lose their remote connections, and the port moves between overlapping addresses live

- Every config reload now makes each open remote connection re-check
  allow_from; a source the list no longer lets in is closed at once, the
  same way a rotated key closes connections made with the old one.
- Moving the remote port between overlapping addresses on the same port
  (all <-> an interface address) no longer fails with EADDRINUSE: when the
  new bind collides on the same port, the old listener is closed first and
  the new one bound; if that still fails, the old address is bound again and
  the reason is reported. Different ports still bind the new one first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(config): check listen.control.remote against the code; server guide follows the CLI

- The manual now checks listen.control.remote against RemoteListen instead of
  declaring it pending: port is required (written at random, 20000-32000, by
  twcore init and twcore remote enable), bind defaults to all, allow_from to
  the private ranges, enabled to false. Regenerated both languages.
- docs/server.md and its Chinese version use `twcore remote enable/disable`,
  show what `twcore control-key` prints on stdout and stderr, and say what a
  remote connection cannot do, how throttling works, and that narrowing
  allow_from or rotating the key closes open connections.
- CONTROL_API_VERSION is 20 on top of #186's 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn fylorn mentioned this pull request Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant