Skip to content

Control handshake: connect to local and remote cores through tw-link (P2) - #183

Merged
fylorn merged 5 commits into
devfrom
feat/control-handshake
Sep 24, 2026
Merged

fylorn merged 5 commits into
devfrom
feat/control-handshake

Conversation

@fylorn

@fylorn fylorn commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Rebased onto dev (after #180 and #182).

What changes

  • Core pins: every core crate (tw-api, tw-types, tw-yaml, tw-guard, tw-watch, and the new tw-link) on tag = "v0.47.0"; tw-api.ts regenerated (CONTROL_API_VERSION 20).
  • v0.46.0 transition removed: core-sync branch in keys.rs rotate_key; scan_alert / clients_changed cases in src/types.ts; control.key_bind_failed translation. tw-scan watch tests now require a received signal (Ok(Some(()))), not just "not timed out".
  • Local mode: the per-launch token is gone (token.rs, the Bearer header, TW_CONTROL_TOKEN). ControlClient reads listen.control.key from the local config.yaml on every connection and runs the Noise handshake on the unix socket / Windows loopback. A missing config or key counts as "core not ready yet"; after WrongKey the key is read once more (rotation). The supervisor no longer passes anything to core.
  • Remote mode: connector::handshake uses tw_link::connect; LinkError maps onto ConnectError (Unreachable / Closed / WrongKey / VersionMismatch / Timeout); NotYetAvailable is removed. The mismatch page shows the server core's own version (peer_version) and the app's required version (taken from the locked tw-api version; the control-protocol numbers are added when both version strings are equal, as with unreleased builds).
  • Gateway address in remote mode: the host this computer dialled for the control port + the gateway's port; gateway_reachable only as a fallback.
  • Regenerated tw-api.ts (CONTROL_API_VERSION 19); zh translations for config.control_key_missing, config.control_key_invalid, control.control_key_locked, control.remote.* (3) and config.remote_port_zero / config.remote_port_is_gateway / config.bad_remote_allow_from; control.unauthorized removed.
  • Fixes found while verifying the real app: Settings stays usable while a remote is disconnected (only the sections that edit the server's config go read-only); no "This Mac · Stopped" flash in the sidebar before the connection list loads; menubar_preview --dump prints the native NSMenu including submenus.

Verified

  • cargo fmt/clippy/test, pnpm typecheck/build/test with the v0.47.0 twcore release binary (fetch-core.sh); end-to-end re-run on v0.47.0: remote enable → test connection → save and switch → back to local.
  • Integration tests against a real core: right key, wrong key, writes, event stream, and twcore control-key --rotate (next connection gets in with the new key, the old key is refused).
  • Isolated real app instance (own THINKWATCH_HOME, own ports), driven through its webview:
    • local mode over the handshake; key rotated while the app runs — the event stream reconnects by itself;
    • remote: a core with twcore remote enable --allow 127.0.0.1/32: test OK, wrong key, denied source (dialled via the LAN address → "closed by the server"), save and switch (local core stops), server killed and restarted mid-session (banner, read-only, one notification, recovers and clears it), key rotated on the server → wrong key → key replaced in the edit dialog → reconnects, version mismatch against a P1-era core (page ⑥ with both versions);
    • launch guard: two unfinished launches → picker window first, picking a remote connects it;
    • keys file: connection-keys.json is 0600; deleting the connection removes the entry;
    • native menubar "Connection" submenu (checked current item, separator, "Manage Connections…") via --dump.
  • Not verifiable here: holding ⌥ at launch (synthetic key events need Accessibility permission, which this machine doesn't grant) — needs a manual check.

CI

Pins are on the v0.47.0 tag, whose release has all twcore assets, so fetch-core.sh resolves on every platform.

🤖 Generated with Claude Code

fylorn and others added 5 commits September 25, 2026 01:47
- Pin tw-api and the new tw-link to core main 1fae554 (same rev for both).
- Local mode: no more per-launch token. ControlClient reads
  listen.control.key from config.yaml on every connection and runs the
  Noise handshake on the socket / Windows loopback; a missing config or key
  counts as "core not ready yet"; after WrongKey the key is read once more
  (rotation). token.rs, the Bearer header and TW_CONTROL_TOKEN are gone.
- Remote mode: connector::handshake now uses tw_link::connect; LinkError maps
  onto ConnectError, VersionMismatch carries the server core's own version.
  ConnectError::NotYetAvailable is removed.
- The app's required core version comes from the locked tw-api version
  (works for tags and revs).
- Integration tests run against a real core with the key from its config,
  a wrong key, and a key rotated with `twcore control-key --rotate`.
- Regenerated tw-api.ts (CONTROL_API_VERSION 18); zh translations for
  config.control_key_missing, config.control_key_invalid,
  control.control_key_locked; control.unauthorized removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e preview; no footer flash before the list loads

Found while verifying the real app:
- The disconnect banner made the whole content read-only, including
  Settings > Connection, where the fix usually is (replace the key, switch to
  this Mac). Settings now stays usable; only the sections that edit the
  server's config go read-only.
- The sidebar footer drew "This Mac · Stopped" for a moment before the
  connection list arrived, even when connected to a remote.
- menubar_preview gains --dump (and a remote state) to print the native
  NSMenu, submenus included, without screen recording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pin tw-api and tw-link to core 892e90e (ThinkWatch-Core#187, P4).
- Remote gateway address for the sidebar / switch dialog: the host this
  computer dialled for the control port plus the gateway's port;
  gateway_reachable only as the fallback when core reports no gateway address.
- zh translations for control.remote.shutdown_refused,
  control.remote.diagnostics_refused, control.remote.control_section_locked,
  config.remote_port_zero, config.remote_port_is_gateway,
  config.bad_remote_allow_from; regenerated tw-api.ts; notice fixture gains the
  two new Status fields.

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

Unreleased builds carry the same version number on both sides; the page then
read "server core 0.46.0, this app needs 0.46.0". When the two strings are
equal, the protocol numbers from the handshake are added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- All core crates (tw-api, tw-types, tw-yaml, tw-guard, tw-watch, tw-link) on
  tag v0.47.0; regenerated tw-api.ts (CONTROL_API_VERSION 20) and lite-api.ts.
- rotate_key no longer reads core's synced/failed fields: core does not sync
  adopted clients any more, the app does it.
- scan_alert / clients_changed are gone from the core event switch in
  types.ts; control.key_bind_failed is gone from core.zh.json (no longer in
  core's msg codes).
- tw-scan watch tests: a received signal is required, a closed channel no
  longer passes as one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn
fylorn force-pushed the feat/control-handshake branch from 14aa4b0 to 5e90bc7 Compare September 24, 2026 18:00
@fylorn
fylorn changed the base branch from feat/connections to dev September 24, 2026 18:00
@fylorn fylorn closed this Sep 24, 2026
@fylorn fylorn reopened this Sep 24, 2026
@fylorn
fylorn merged commit 8d99662 into dev Sep 24, 2026
4 checks passed
@fylorn
fylorn deleted the feat/control-handshake branch September 24, 2026 18:09
@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