Repository navigation
feat: native MCP protocol support on socket server - #1
Conversation
…1-ssh-remote-port-proxying" This reverts commit f7cbbad.
# Conflicts: # .github/workflows/ci.yml # CLI/cmux.swift # Sources/GhosttyTerminalView.swift # Sources/SocketControlSettings.swift # Sources/TabManager.swift # Sources/TerminalController.swift # Sources/Workspace.swift # ghostty # scripts/reload.sh
|
@coderabbitai review |
Self-Review: Round 1 Findings & Fixes (41231ba)Critical fixes:
Security fix:
Bug fix:
Improvements:
@coderabbitai The MCP server auto-detects protocol on first socket read (Content-Length framing = MCP, otherwise V1/V2 NDJSON). This is intentional — both protocols share the same Unix domain socket. The |
* Migrate CI/CD to WarpBuild, consolidate test jobs Replace all macOS runner labels across workflows: - depot-macos-latest → warp-macos-15-arm64-6x - macos-15 → warp-macos-15-arm64-6x - macos-14 → warp-macos-14-arm64-6x Consolidates tests + tests-depot into a single tests job that runs unit tests, regressions, UI tests, and lag tests sequentially on one WarpBuild runner. Ubuntu jobs remain on ubuntu-latest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Upgrade stale zig on runners that have an outdated version pre-installed WarpBuild macos-14 ships zig 0.15.1 but the project requires 0.15.2. The install step skipped because zig was found, just outdated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Pin zig 0.15.2 via direct tarball instead of Homebrew Homebrew's zig bottle for macOS 14 (Sonoma) is stuck at 0.15.1 but the ghostty submodule requires 0.15.2. Download zig directly from ziglang.org to guarantee the correct version on all runner images. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix zig tarball URL: arch-os order is aarch64-macos, not macos-aarch64 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Create /usr/local/bin and /usr/local/lib before copying zig WarpBuild runners don't have /usr/local/lib by default. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add 20-min timeout to WarpBuild jobs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix UI test hang: stream output instead of variable capture, use GitHub runner for macOS 14 The OUTPUT=$(...) pattern buffers all xcodebuild output into a bash variable. For the full cmux scheme (build + UI tests), this can be hundreds of MB, causing the shell to hang. Replace with tee streaming. macOS 14 on WarpBuild consistently hangs (unit tests timeout at 20min vs 4min on macOS 15, same M4 Pro hardware). Use GitHub-hosted macos-14 runner for compat tests instead, which works on main today. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Split UI tests to GitHub-hosted runner (WarpBuild can't activate GUI apps) WarpBuild macOS VMs leave XCUIApplication stuck in "Running Background" state, causing every UI test to burn ~62s waiting for activation and timing out the job. Root cause: WarpBuild ephemeral VMs don't provide a full GUI session for app activation. Split CI into parallel jobs: - tests: WarpBuild (unit tests + regressions, ~6 min) - tests-ui: GitHub-hosted macos-15 (UI tests + lag regression) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move tests-ui to WarpBuild with TCC permission grants Grant accessibility, post-event, and screen capture TCC permissions to Xcode and XCTest processes on WarpBuild ephemeral VMs. This should fix "Failed to activate application (Running Background)" errors that prevent XCUITests from bringing the app to foreground. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add GUI session diagnostics and DevToolsSecurity for WarpBuild UI tests Add session diagnostics (who, console user, GUI domain, WindowServer, loginwindow) to understand WarpBuild VM session state. Also enable DevToolsSecurity and security authorizationdb for XCTest process control. Try bootstrapping GUI session if missing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix TCC permissions: use Xcode-Helper + user DB (CircleCI approach) Previous TCC grants used wrong client IDs (com.apple.dt.Xcode) and only wrote to the system database. CircleCI's proven approach grants: - kTCCServiceAccessibility to com.apple.dt.Xcode-Helper (not Xcode) - kTCCServiceDeveloperTool to com.apple.Terminal - Both system AND user-level TCC databases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Reduce UI test timeout to 15s for WarpBuild expected failures WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background"). Tests still execute and report expected failures. But the 62s per-test activation timeout makes 30+ tests take 30+ minutes total. Set per-test timeout to 15s so expected failures resolve quickly. Full interactive UI test coverage runs via test-e2e.yml on GitHub-hosted runners with proper display support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replace XCUITest run with build + lag regression on WarpBuild WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background" with 62s activation timeout per test). Tried TCC permissions, DevToolsSecurity, virtual display, reduced timeouts, nothing fixes the framework-level issue. Replace tests-ui job with tests-build-and-lag: - Build the full cmux scheme (verifies compilation) - Run workspace churn typing-lag regression (socket-based, no GUI) - XCUITests run via test-e2e.yml on GitHub-hosted runners Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move macOS 14 compat to WarpBuild (no GitHub-hosted runners) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add diagnostic workflow to probe WarpBuild GUI activation Tests multiple app activation approaches on WarpBuild VMs: - open -a, NSWorkspace, NSRunningApplication.activate, osascript - Virtual display state before/after CGVirtualDisplay - TCC/accessibility permissions, Quartz session info - VM type detection This is a workflow_dispatch-only diagnostic to determine if XCUITest can work on WarpBuild with the right configuration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Trigger GUI probe on branch push (workflow_dispatch needs main) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Rewrite GUI probe with Swift (Python lacks AppKit on WarpBuild) v1 failed because WarpBuild's Python isn't a framework build and can't import AppKit/Quartz. v2 uses a compiled Swift binary to test NSRunningApplication.activate(), osascript, Quartz session state, display info, and AX trust. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * GUI probe v3: try 5 approaches to unlock WarpBuild screen 1. defaults write (screensaver, loginwindow, pmset) 2. automationmodetool enable-automationmode-without-authentication 3. CGSSessionSetScreenLocked private API + System Events keystroke 4. sysadminctl -screenLock off + keychain unlock 5. CGEvent simulation (mouse move + Return key to dismiss lock) Each approach is followed by an activation check to see if it worked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Test GUI activation on macOS 14, 15, and 26 (Tahoe) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add DerivedData and GhosttyKit caching to CI workflows Major caching improvements across ci.yml and ci-macos-compat.yml: - Cache GhosttyKit.xcframework keyed on ghostty submodule SHA (skip download on cache hit) - Cache DerivedData keyed on OS + Xcode version + Package.resolved + project.pbxproj (enables incremental builds across runs) - Remove explicit DerivedData wipe (rely on cache key invalidation) - Use download-prebuilt-ghosttykit.sh in compat workflow too This should significantly speed up macOS 14 compat tests which were taking 20+ min due to full recompilation every run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Bump macOS 14 compat timeout to 45 min for cold cache seeding The DerivedData cache wasn't saved because the job timed out at 30 min, causing the post-job cache save step to be skipped. 45 min gives enough headroom for the first uncached run to complete and seed the cache. Subsequent runs should be much faster with incremental builds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use Depot runners for E2E tests (WarpBuild has screen lock on macOS 15/26) WarpBuild VMs on macOS 15 and 26 have CGSSessionScreenIsLocked=1, which prevents XCUIApplication activation. Depot runners have working GUI activation. Can switch back to WarpBuild once they fix the VM images. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Skip smoke test on macOS 14 compat, remove GUI diagnostic workflow macOS 14 was slow because it built the full app (cmux scheme) on top of unit tests (cmux-unit scheme). Unit tests are the real compat check; smoke test runs on macOS 15 only. Also removes the temporary test-warpbuild-gui.yml diagnostic workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace Sonoma with Tahoe in compat matrix, drop macOS 14 Swap macOS 14 (Sonoma) for macOS 26 (Tahoe). Smoke test runs on macOS 15 only (WarpBuild screen lock blocks app activation on 26). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Drop macOS 26 from compat matrix (zig 0.15.2 linker failure) Zig 0.15.2 can't link against the macOS 26 (Tahoe) SDK: undefined symbols for basic libc functions (_abort, _free, _fork, etc.). The zig toolchain needs an update to support Tahoe. Keep macOS 15 only for now. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…-build-failure Fix nightly release browser portal compile
…ai#1590) Zig 0.15.2's MachO linker can't resolve libSystem on macOS 26 (the version number jump from 15 to 26 breaks zig's SDK handling). The unit tests don't need the CLI helper binary at runtime, so we skip the zig build on macOS 26 by setting CMUX_SKIP_ZIG_BUILD=1, which creates a stub binary to satisfy the Xcode Run Script file check. Smoke test (full app build + launch) is skipped on macOS 26 since it needs the real CLI helper. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…anaflow-ai#1595) The Go build runs in a subshell that cd's to daemon/remote/, but OUTPUT_DIR was relative to the repo root. Resolve to absolute path after mkdir so go build -o writes to the correct location. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p-review-fixes Fix SSH remote CLI wrapper and proxy follow-ups
ChatThere are 3 ways to chat with CodeRabbit:
CodeRabbit commands
Other keywords and placeholders
CodeRabbit configuration file (
|
|
@coderabbitai help |
ChatThere are 3 ways to chat with CodeRabbit:
CodeRabbit commands
Other keywords and placeholders
CodeRabbit configuration file (
|
|
@coderabbitai Full review and also review the comments that were left for you and all the reviews that were done, and then conduct a fuller review with all of it in mind. |
|
✏️ Learnings added
✅ Actions performedFull review triggered. |
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (110)
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Add MCP (Model Context Protocol) Content-Length framing support directly
to the cmux socket server, enabling AI tools to connect via socat without
needing a separate Node.js MCP wrapper process.
Protocol detection on first read: "Content-Length:" → MCP mode,
"{" → V2 JSON-RPC, else V1 plain text. All three protocols coexist
on the same Unix socket.
New files:
- MCPServer.swift: Content-Length framing parser, MCPHandler with 3
JSON-RPC methods (initialize, tools/list, tools/call), and 20 MCP
tool schemas that route to existing V2 socket methods
- MCPServerTests.swift: 28 unit tests covering framing, handler logic,
and tool routing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix test initialization: tests calling tools/list and tools/call now send initialize first (XCTest creates fresh instances per test) - Add testToolsListBeforeInitializeReturnsError to cover the guard - Fix MCP message loop: continue parsing after each message instead of breaking after one, avoiding latency when multiple messages arrive in a single socket read - Forward press_enter param in send_input tool routing - Quote V1 command arguments to prevent injection via tokenizer - Add writeSocketData helper with EINTR/partial-write handling - Add encodeResponse fallback for non-serializable dicts - Require initialized handshake before tools/list and tools/call - Reject MCP connections when password auth is required - Close connection on corrupted data after MCP detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
41231ba to
001508d
Compare
* Add browser import flow with installed-browser detection * Tone down empty browser import overlay * Make browser import a 2-step choice flow * Use single-window browser import wizard with close button * Mention extensions not yet supported in import note * Reapply "Merge pull request manaflow-ai#239 from manaflow-ai/issue-151-ssh-remote-port-proxying" This reverts commit f7cbbad. * Fix ssh stack review regressions * Address ssh stack review follow-ups * Optimize remote daemon builds and TCP latency * Add remote favicon proxy regression * Proxy remote browser favicon fetches * Add ssh profile-noise regression * Avoid sourcing profile in ssh bootstrap * Add ssh stack regression tests * Fix ssh stack review regressions * Fix ghostty deferred-init regression harness * Fix SSH workspace priming and restore state * Fix SSH transport dedupe and loopback review issues * Fix browser move and zsh bootstrap regressions * Add regressions for v1 panel focus preservation * Fix socket focus and startup env regressions * Add regression test for deferred terminal portal sync * Defer terminal portal sync past layout churn * Keep portal sync responsive during live resize * fix: show sidebar update banner from background checks (manaflow-ai#1543) * Update bonsplit for split transparency * Update bonsplit for split transparency * Support folder drops on dock icon (manaflow-ai#1571) * Fix sidebar PR badges for restored workspaces (manaflow-ai#1570) * test: cover sidebar PR explicit branch fallback * fix: restore sidebar PR badges for workspace branches * test: preserve sidebar PR badge on first prompt * fix: keep sidebar PR badges through first prompt * feat: add browser profile mapping import flow * Avoid blocking browser PR metadata updates (manaflow-ai#1564) * Fix manaflow-ai#1574: remove top update banner in sidebar (manaflow-ai#1575) * test: cover sidebar update indicator regression * fix: remove duplicate sidebar update banner * fix: address browser import review feedback * Stabilize SSH remote flow after merging main * Make remote proxy close idempotent * Fix UI test helper closure captures * Add remote CLI relay regressions * Fix nightly remote daemon and SSH relay wiring * Migrate CI/CD to WarpBuild, consolidate test jobs (manaflow-ai#1501) * Migrate CI/CD to WarpBuild, consolidate test jobs Replace all macOS runner labels across workflows: - depot-macos-latest → warp-macos-15-arm64-6x - macos-15 → warp-macos-15-arm64-6x - macos-14 → warp-macos-14-arm64-6x Consolidates tests + tests-depot into a single tests job that runs unit tests, regressions, UI tests, and lag tests sequentially on one WarpBuild runner. Ubuntu jobs remain on ubuntu-latest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Upgrade stale zig on runners that have an outdated version pre-installed WarpBuild macos-14 ships zig 0.15.1 but the project requires 0.15.2. The install step skipped because zig was found, just outdated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Pin zig 0.15.2 via direct tarball instead of Homebrew Homebrew's zig bottle for macOS 14 (Sonoma) is stuck at 0.15.1 but the ghostty submodule requires 0.15.2. Download zig directly from ziglang.org to guarantee the correct version on all runner images. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix zig tarball URL: arch-os order is aarch64-macos, not macos-aarch64 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Create /usr/local/bin and /usr/local/lib before copying zig WarpBuild runners don't have /usr/local/lib by default. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add 20-min timeout to WarpBuild jobs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix UI test hang: stream output instead of variable capture, use GitHub runner for macOS 14 The OUTPUT=$(...) pattern buffers all xcodebuild output into a bash variable. For the full cmux scheme (build + UI tests), this can be hundreds of MB, causing the shell to hang. Replace with tee streaming. macOS 14 on WarpBuild consistently hangs (unit tests timeout at 20min vs 4min on macOS 15, same M4 Pro hardware). Use GitHub-hosted macos-14 runner for compat tests instead, which works on main today. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Split UI tests to GitHub-hosted runner (WarpBuild can't activate GUI apps) WarpBuild macOS VMs leave XCUIApplication stuck in "Running Background" state, causing every UI test to burn ~62s waiting for activation and timing out the job. Root cause: WarpBuild ephemeral VMs don't provide a full GUI session for app activation. Split CI into parallel jobs: - tests: WarpBuild (unit tests + regressions, ~6 min) - tests-ui: GitHub-hosted macos-15 (UI tests + lag regression) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move tests-ui to WarpBuild with TCC permission grants Grant accessibility, post-event, and screen capture TCC permissions to Xcode and XCTest processes on WarpBuild ephemeral VMs. This should fix "Failed to activate application (Running Background)" errors that prevent XCUITests from bringing the app to foreground. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add GUI session diagnostics and DevToolsSecurity for WarpBuild UI tests Add session diagnostics (who, console user, GUI domain, WindowServer, loginwindow) to understand WarpBuild VM session state. Also enable DevToolsSecurity and security authorizationdb for XCTest process control. Try bootstrapping GUI session if missing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix TCC permissions: use Xcode-Helper + user DB (CircleCI approach) Previous TCC grants used wrong client IDs (com.apple.dt.Xcode) and only wrote to the system database. CircleCI's proven approach grants: - kTCCServiceAccessibility to com.apple.dt.Xcode-Helper (not Xcode) - kTCCServiceDeveloperTool to com.apple.Terminal - Both system AND user-level TCC databases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Reduce UI test timeout to 15s for WarpBuild expected failures WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background"). Tests still execute and report expected failures. But the 62s per-test activation timeout makes 30+ tests take 30+ minutes total. Set per-test timeout to 15s so expected failures resolve quickly. Full interactive UI test coverage runs via test-e2e.yml on GitHub-hosted runners with proper display support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replace XCUITest run with build + lag regression on WarpBuild WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background" with 62s activation timeout per test). Tried TCC permissions, DevToolsSecurity, virtual display, reduced timeouts, nothing fixes the framework-level issue. Replace tests-ui job with tests-build-and-lag: - Build the full cmux scheme (verifies compilation) - Run workspace churn typing-lag regression (socket-based, no GUI) - XCUITests run via test-e2e.yml on GitHub-hosted runners Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move macOS 14 compat to WarpBuild (no GitHub-hosted runners) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add diagnostic workflow to probe WarpBuild GUI activation Tests multiple app activation approaches on WarpBuild VMs: - open -a, NSWorkspace, NSRunningApplication.activate, osascript - Virtual display state before/after CGVirtualDisplay - TCC/accessibility permissions, Quartz session info - VM type detection This is a workflow_dispatch-only diagnostic to determine if XCUITest can work on WarpBuild with the right configuration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Trigger GUI probe on branch push (workflow_dispatch needs main) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Rewrite GUI probe with Swift (Python lacks AppKit on WarpBuild) v1 failed because WarpBuild's Python isn't a framework build and can't import AppKit/Quartz. v2 uses a compiled Swift binary to test NSRunningApplication.activate(), osascript, Quartz session state, display info, and AX trust. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * GUI probe v3: try 5 approaches to unlock WarpBuild screen 1. defaults write (screensaver, loginwindow, pmset) 2. automationmodetool enable-automationmode-without-authentication 3. CGSSessionSetScreenLocked private API + System Events keystroke 4. sysadminctl -screenLock off + keychain unlock 5. CGEvent simulation (mouse move + Return key to dismiss lock) Each approach is followed by an activation check to see if it worked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Test GUI activation on macOS 14, 15, and 26 (Tahoe) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add DerivedData and GhosttyKit caching to CI workflows Major caching improvements across ci.yml and ci-macos-compat.yml: - Cache GhosttyKit.xcframework keyed on ghostty submodule SHA (skip download on cache hit) - Cache DerivedData keyed on OS + Xcode version + Package.resolved + project.pbxproj (enables incremental builds across runs) - Remove explicit DerivedData wipe (rely on cache key invalidation) - Use download-prebuilt-ghosttykit.sh in compat workflow too This should significantly speed up macOS 14 compat tests which were taking 20+ min due to full recompilation every run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Bump macOS 14 compat timeout to 45 min for cold cache seeding The DerivedData cache wasn't saved because the job timed out at 30 min, causing the post-job cache save step to be skipped. 45 min gives enough headroom for the first uncached run to complete and seed the cache. Subsequent runs should be much faster with incremental builds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use Depot runners for E2E tests (WarpBuild has screen lock on macOS 15/26) WarpBuild VMs on macOS 15 and 26 have CGSSessionScreenIsLocked=1, which prevents XCUIApplication activation. Depot runners have working GUI activation. Can switch back to WarpBuild once they fix the VM images. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Skip smoke test on macOS 14 compat, remove GUI diagnostic workflow macOS 14 was slow because it built the full app (cmux scheme) on top of unit tests (cmux-unit scheme). Unit tests are the real compat check; smoke test runs on macOS 15 only. Also removes the temporary test-warpbuild-gui.yml diagnostic workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace Sonoma with Tahoe in compat matrix, drop macOS 14 Swap macOS 14 (Sonoma) for macOS 26 (Tahoe). Smoke test runs on macOS 15 only (WarpBuild screen lock blocks app activation on 26). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Drop macOS 26 from compat matrix (zig 0.15.2 linker failure) Zig 0.15.2 can't link against the macOS 26 (Tahoe) SDK: undefined symbols for basic libc functions (_abort, _free, _fork, etc.). The zig toolchain needs an update to support Tahoe. Keep macOS 15 only for now. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Fix release browser portal compile * Add macOS 26 (Tahoe) compat tests, skip zig build via stub (manaflow-ai#1590) Zig 0.15.2's MachO linker can't resolve libSystem on macOS 26 (the version number jump from 15 to 26 breaks zig's SDK handling). The unit tests don't need the CLI helper binary at runtime, so we skip the zig build on macOS 26 by setting CMUX_SKIP_ZIG_BUILD=1, which creates a stub binary to satisfy the Xcode Run Script file check. Smoke test (full app build + launch) is skipped on macOS 26 since it needs the real CLI helper. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add regression tests for SSH remote CLI follow-ups * Fix SSH remote CLI and loopback proxy follow-ups * Fix remote daemon build script using relative output path after cd (manaflow-ai#1595) The Go build runs in a subshell that cd's to daemon/remote/, but OUTPUT_DIR was relative to the repo root. Resolve to absolute path after mkdir so go build -o writes to the correct location. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address SSH follow-up PR review comments * fix: restore Sparkle automatic update checks (manaflow-ai#1597) * feat: add native MCP protocol support to socket server Add MCP (Model Context Protocol) Content-Length framing support directly to the cmux socket server, enabling AI tools to connect via socat without needing a separate Node.js MCP wrapper process. Protocol detection on first read: "Content-Length:" → MCP mode, "{" → V2 JSON-RPC, else V1 plain text. All three protocols coexist on the same Unix socket. New files: - MCPServer.swift: Content-Length framing parser, MCPHandler with 3 JSON-RPC methods (initialize, tools/list, tools/call), and 20 MCP tool schemas that route to existing V2 socket methods - MCPServerTests.swift: 28 unit tests covering framing, handler logic, and tool routing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address MCP server review findings - Fix test initialization: tests calling tools/list and tools/call now send initialize first (XCTest creates fresh instances per test) - Add testToolsListBeforeInitializeReturnsError to cover the guard - Fix MCP message loop: continue parsing after each message instead of breaking after one, avoiding latency when multiple messages arrive in a single socket read - Forward press_enter param in send_input tool routing - Quote V1 command arguments to prevent injection via tokenizer - Add writeSocketData helper with EINTR/partial-write handling - Add encodeResponse fallback for non-serializable dicts - Require initialized handshake before tools/list and tools/call - Reject MCP connections when password auth is required - Close connection on corrupted data after MCP detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Austin Wang <austinwang115@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add browser import flow with installed-browser detection * Tone down empty browser import overlay * Make browser import a 2-step choice flow * Use single-window browser import wizard with close button * Mention extensions not yet supported in import note * Reapply "Merge pull request manaflow-ai#239 from manaflow-ai/issue-151-ssh-remote-port-proxying" This reverts commit f7cbbad. * Fix ssh stack review regressions * Address ssh stack review follow-ups * Optimize remote daemon builds and TCP latency * Add remote favicon proxy regression * Proxy remote browser favicon fetches * Add ssh profile-noise regression * Avoid sourcing profile in ssh bootstrap * Add ssh stack regression tests * Fix ssh stack review regressions * Fix ghostty deferred-init regression harness * Fix SSH workspace priming and restore state * Fix SSH transport dedupe and loopback review issues * Fix browser move and zsh bootstrap regressions * Add regressions for v1 panel focus preservation * Fix socket focus and startup env regressions * Add regression test for deferred terminal portal sync * Defer terminal portal sync past layout churn * Keep portal sync responsive during live resize * fix: show sidebar update banner from background checks (manaflow-ai#1543) * Update bonsplit for split transparency * Update bonsplit for split transparency * Support folder drops on dock icon (manaflow-ai#1571) * Fix sidebar PR badges for restored workspaces (manaflow-ai#1570) * test: cover sidebar PR explicit branch fallback * fix: restore sidebar PR badges for workspace branches * test: preserve sidebar PR badge on first prompt * fix: keep sidebar PR badges through first prompt * feat: add browser profile mapping import flow * Avoid blocking browser PR metadata updates (manaflow-ai#1564) * Fix manaflow-ai#1574: remove top update banner in sidebar (manaflow-ai#1575) * test: cover sidebar update indicator regression * fix: remove duplicate sidebar update banner * fix: address browser import review feedback * Stabilize SSH remote flow after merging main * Make remote proxy close idempotent * Fix UI test helper closure captures * Add remote CLI relay regressions * Fix nightly remote daemon and SSH relay wiring * Migrate CI/CD to WarpBuild, consolidate test jobs (manaflow-ai#1501) * Migrate CI/CD to WarpBuild, consolidate test jobs Replace all macOS runner labels across workflows: - depot-macos-latest → warp-macos-15-arm64-6x - macos-15 → warp-macos-15-arm64-6x - macos-14 → warp-macos-14-arm64-6x Consolidates tests + tests-depot into a single tests job that runs unit tests, regressions, UI tests, and lag tests sequentially on one WarpBuild runner. Ubuntu jobs remain on ubuntu-latest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Upgrade stale zig on runners that have an outdated version pre-installed WarpBuild macos-14 ships zig 0.15.1 but the project requires 0.15.2. The install step skipped because zig was found, just outdated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Pin zig 0.15.2 via direct tarball instead of Homebrew Homebrew's zig bottle for macOS 14 (Sonoma) is stuck at 0.15.1 but the ghostty submodule requires 0.15.2. Download zig directly from ziglang.org to guarantee the correct version on all runner images. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix zig tarball URL: arch-os order is aarch64-macos, not macos-aarch64 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Create /usr/local/bin and /usr/local/lib before copying zig WarpBuild runners don't have /usr/local/lib by default. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add 20-min timeout to WarpBuild jobs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix UI test hang: stream output instead of variable capture, use GitHub runner for macOS 14 The OUTPUT=$(...) pattern buffers all xcodebuild output into a bash variable. For the full cmux scheme (build + UI tests), this can be hundreds of MB, causing the shell to hang. Replace with tee streaming. macOS 14 on WarpBuild consistently hangs (unit tests timeout at 20min vs 4min on macOS 15, same M4 Pro hardware). Use GitHub-hosted macos-14 runner for compat tests instead, which works on main today. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Split UI tests to GitHub-hosted runner (WarpBuild can't activate GUI apps) WarpBuild macOS VMs leave XCUIApplication stuck in "Running Background" state, causing every UI test to burn ~62s waiting for activation and timing out the job. Root cause: WarpBuild ephemeral VMs don't provide a full GUI session for app activation. Split CI into parallel jobs: - tests: WarpBuild (unit tests + regressions, ~6 min) - tests-ui: GitHub-hosted macos-15 (UI tests + lag regression) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move tests-ui to WarpBuild with TCC permission grants Grant accessibility, post-event, and screen capture TCC permissions to Xcode and XCTest processes on WarpBuild ephemeral VMs. This should fix "Failed to activate application (Running Background)" errors that prevent XCUITests from bringing the app to foreground. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add GUI session diagnostics and DevToolsSecurity for WarpBuild UI tests Add session diagnostics (who, console user, GUI domain, WindowServer, loginwindow) to understand WarpBuild VM session state. Also enable DevToolsSecurity and security authorizationdb for XCTest process control. Try bootstrapping GUI session if missing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix TCC permissions: use Xcode-Helper + user DB (CircleCI approach) Previous TCC grants used wrong client IDs (com.apple.dt.Xcode) and only wrote to the system database. CircleCI's proven approach grants: - kTCCServiceAccessibility to com.apple.dt.Xcode-Helper (not Xcode) - kTCCServiceDeveloperTool to com.apple.Terminal - Both system AND user-level TCC databases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Reduce UI test timeout to 15s for WarpBuild expected failures WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background"). Tests still execute and report expected failures. But the 62s per-test activation timeout makes 30+ tests take 30+ minutes total. Set per-test timeout to 15s so expected failures resolve quickly. Full interactive UI test coverage runs via test-e2e.yml on GitHub-hosted runners with proper display support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replace XCUITest run with build + lag regression on WarpBuild WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background" with 62s activation timeout per test). Tried TCC permissions, DevToolsSecurity, virtual display, reduced timeouts, nothing fixes the framework-level issue. Replace tests-ui job with tests-build-and-lag: - Build the full cmux scheme (verifies compilation) - Run workspace churn typing-lag regression (socket-based, no GUI) - XCUITests run via test-e2e.yml on GitHub-hosted runners Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move macOS 14 compat to WarpBuild (no GitHub-hosted runners) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add diagnostic workflow to probe WarpBuild GUI activation Tests multiple app activation approaches on WarpBuild VMs: - open -a, NSWorkspace, NSRunningApplication.activate, osascript - Virtual display state before/after CGVirtualDisplay - TCC/accessibility permissions, Quartz session info - VM type detection This is a workflow_dispatch-only diagnostic to determine if XCUITest can work on WarpBuild with the right configuration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Trigger GUI probe on branch push (workflow_dispatch needs main) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Rewrite GUI probe with Swift (Python lacks AppKit on WarpBuild) v1 failed because WarpBuild's Python isn't a framework build and can't import AppKit/Quartz. v2 uses a compiled Swift binary to test NSRunningApplication.activate(), osascript, Quartz session state, display info, and AX trust. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * GUI probe v3: try 5 approaches to unlock WarpBuild screen 1. defaults write (screensaver, loginwindow, pmset) 2. automationmodetool enable-automationmode-without-authentication 3. CGSSessionSetScreenLocked private API + System Events keystroke 4. sysadminctl -screenLock off + keychain unlock 5. CGEvent simulation (mouse move + Return key to dismiss lock) Each approach is followed by an activation check to see if it worked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Test GUI activation on macOS 14, 15, and 26 (Tahoe) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add DerivedData and GhosttyKit caching to CI workflows Major caching improvements across ci.yml and ci-macos-compat.yml: - Cache GhosttyKit.xcframework keyed on ghostty submodule SHA (skip download on cache hit) - Cache DerivedData keyed on OS + Xcode version + Package.resolved + project.pbxproj (enables incremental builds across runs) - Remove explicit DerivedData wipe (rely on cache key invalidation) - Use download-prebuilt-ghosttykit.sh in compat workflow too This should significantly speed up macOS 14 compat tests which were taking 20+ min due to full recompilation every run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Bump macOS 14 compat timeout to 45 min for cold cache seeding The DerivedData cache wasn't saved because the job timed out at 30 min, causing the post-job cache save step to be skipped. 45 min gives enough headroom for the first uncached run to complete and seed the cache. Subsequent runs should be much faster with incremental builds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use Depot runners for E2E tests (WarpBuild has screen lock on macOS 15/26) WarpBuild VMs on macOS 15 and 26 have CGSSessionScreenIsLocked=1, which prevents XCUIApplication activation. Depot runners have working GUI activation. Can switch back to WarpBuild once they fix the VM images. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Skip smoke test on macOS 14 compat, remove GUI diagnostic workflow macOS 14 was slow because it built the full app (cmux scheme) on top of unit tests (cmux-unit scheme). Unit tests are the real compat check; smoke test runs on macOS 15 only. Also removes the temporary test-warpbuild-gui.yml diagnostic workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace Sonoma with Tahoe in compat matrix, drop macOS 14 Swap macOS 14 (Sonoma) for macOS 26 (Tahoe). Smoke test runs on macOS 15 only (WarpBuild screen lock blocks app activation on 26). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Drop macOS 26 from compat matrix (zig 0.15.2 linker failure) Zig 0.15.2 can't link against the macOS 26 (Tahoe) SDK: undefined symbols for basic libc functions (_abort, _free, _fork, etc.). The zig toolchain needs an update to support Tahoe. Keep macOS 15 only for now. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Fix release browser portal compile * Add macOS 26 (Tahoe) compat tests, skip zig build via stub (manaflow-ai#1590) Zig 0.15.2's MachO linker can't resolve libSystem on macOS 26 (the version number jump from 15 to 26 breaks zig's SDK handling). The unit tests don't need the CLI helper binary at runtime, so we skip the zig build on macOS 26 by setting CMUX_SKIP_ZIG_BUILD=1, which creates a stub binary to satisfy the Xcode Run Script file check. Smoke test (full app build + launch) is skipped on macOS 26 since it needs the real CLI helper. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add regression tests for SSH remote CLI follow-ups * Fix SSH remote CLI and loopback proxy follow-ups * Fix remote daemon build script using relative output path after cd (manaflow-ai#1595) The Go build runs in a subshell that cd's to daemon/remote/, but OUTPUT_DIR was relative to the repo root. Resolve to absolute path after mkdir so go build -o writes to the correct location. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address SSH follow-up PR review comments * fix: restore Sparkle automatic update checks (manaflow-ai#1597) * feat: add native MCP protocol support to socket server Add MCP (Model Context Protocol) Content-Length framing support directly to the cmux socket server, enabling AI tools to connect via socat without needing a separate Node.js MCP wrapper process. Protocol detection on first read: "Content-Length:" → MCP mode, "{" → V2 JSON-RPC, else V1 plain text. All three protocols coexist on the same Unix socket. New files: - MCPServer.swift: Content-Length framing parser, MCPHandler with 3 JSON-RPC methods (initialize, tools/list, tools/call), and 20 MCP tool schemas that route to existing V2 socket methods - MCPServerTests.swift: 28 unit tests covering framing, handler logic, and tool routing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address MCP server review findings - Fix test initialization: tests calling tools/list and tools/call now send initialize first (XCTest creates fresh instances per test) - Add testToolsListBeforeInitializeReturnsError to cover the guard - Fix MCP message loop: continue parsing after each message instead of breaking after one, avoiding latency when multiple messages arrive in a single socket read - Forward press_enter param in send_input tool routing - Quote V1 command arguments to prevent injection via tokenizer - Add writeSocketData helper with EINTR/partial-write handling - Add encodeResponse fallback for non-serializable dicts - Require initialized handshake before tools/list and tools/call - Reject MCP connections when password auth is required - Close connection on corrupted data after MCP detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Austin Wang <austinwang115@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add browser import flow with installed-browser detection * Tone down empty browser import overlay * Make browser import a 2-step choice flow * Use single-window browser import wizard with close button * Mention extensions not yet supported in import note * Reapply "Merge pull request manaflow-ai#239 from manaflow-ai/issue-151-ssh-remote-port-proxying" This reverts commit f7cbbad. * Fix ssh stack review regressions * Address ssh stack review follow-ups * Optimize remote daemon builds and TCP latency * Add remote favicon proxy regression * Proxy remote browser favicon fetches * Add ssh profile-noise regression * Avoid sourcing profile in ssh bootstrap * Add ssh stack regression tests * Fix ssh stack review regressions * Fix ghostty deferred-init regression harness * Fix SSH workspace priming and restore state * Fix SSH transport dedupe and loopback review issues * Fix browser move and zsh bootstrap regressions * Add regressions for v1 panel focus preservation * Fix socket focus and startup env regressions * Add regression test for deferred terminal portal sync * Defer terminal portal sync past layout churn * Keep portal sync responsive during live resize * fix: show sidebar update banner from background checks (manaflow-ai#1543) * Update bonsplit for split transparency * Update bonsplit for split transparency * Support folder drops on dock icon (manaflow-ai#1571) * Fix sidebar PR badges for restored workspaces (manaflow-ai#1570) * test: cover sidebar PR explicit branch fallback * fix: restore sidebar PR badges for workspace branches * test: preserve sidebar PR badge on first prompt * fix: keep sidebar PR badges through first prompt * feat: add browser profile mapping import flow * Avoid blocking browser PR metadata updates (manaflow-ai#1564) * Fix manaflow-ai#1574: remove top update banner in sidebar (manaflow-ai#1575) * test: cover sidebar update indicator regression * fix: remove duplicate sidebar update banner * fix: address browser import review feedback * Stabilize SSH remote flow after merging main * Make remote proxy close idempotent * Fix UI test helper closure captures * Add remote CLI relay regressions * Fix nightly remote daemon and SSH relay wiring * Migrate CI/CD to WarpBuild, consolidate test jobs (manaflow-ai#1501) * Migrate CI/CD to WarpBuild, consolidate test jobs Replace all macOS runner labels across workflows: - depot-macos-latest → warp-macos-15-arm64-6x - macos-15 → warp-macos-15-arm64-6x - macos-14 → warp-macos-14-arm64-6x Consolidates tests + tests-depot into a single tests job that runs unit tests, regressions, UI tests, and lag tests sequentially on one WarpBuild runner. Ubuntu jobs remain on ubuntu-latest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Upgrade stale zig on runners that have an outdated version pre-installed WarpBuild macos-14 ships zig 0.15.1 but the project requires 0.15.2. The install step skipped because zig was found, just outdated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Pin zig 0.15.2 via direct tarball instead of Homebrew Homebrew's zig bottle for macOS 14 (Sonoma) is stuck at 0.15.1 but the ghostty submodule requires 0.15.2. Download zig directly from ziglang.org to guarantee the correct version on all runner images. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix zig tarball URL: arch-os order is aarch64-macos, not macos-aarch64 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Create /usr/local/bin and /usr/local/lib before copying zig WarpBuild runners don't have /usr/local/lib by default. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add 20-min timeout to WarpBuild jobs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix UI test hang: stream output instead of variable capture, use GitHub runner for macOS 14 The OUTPUT=$(...) pattern buffers all xcodebuild output into a bash variable. For the full cmux scheme (build + UI tests), this can be hundreds of MB, causing the shell to hang. Replace with tee streaming. macOS 14 on WarpBuild consistently hangs (unit tests timeout at 20min vs 4min on macOS 15, same M4 Pro hardware). Use GitHub-hosted macos-14 runner for compat tests instead, which works on main today. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Split UI tests to GitHub-hosted runner (WarpBuild can't activate GUI apps) WarpBuild macOS VMs leave XCUIApplication stuck in "Running Background" state, causing every UI test to burn ~62s waiting for activation and timing out the job. Root cause: WarpBuild ephemeral VMs don't provide a full GUI session for app activation. Split CI into parallel jobs: - tests: WarpBuild (unit tests + regressions, ~6 min) - tests-ui: GitHub-hosted macos-15 (UI tests + lag regression) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move tests-ui to WarpBuild with TCC permission grants Grant accessibility, post-event, and screen capture TCC permissions to Xcode and XCTest processes on WarpBuild ephemeral VMs. This should fix "Failed to activate application (Running Background)" errors that prevent XCUITests from bringing the app to foreground. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add GUI session diagnostics and DevToolsSecurity for WarpBuild UI tests Add session diagnostics (who, console user, GUI domain, WindowServer, loginwindow) to understand WarpBuild VM session state. Also enable DevToolsSecurity and security authorizationdb for XCTest process control. Try bootstrapping GUI session if missing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix TCC permissions: use Xcode-Helper + user DB (CircleCI approach) Previous TCC grants used wrong client IDs (com.apple.dt.Xcode) and only wrote to the system database. CircleCI's proven approach grants: - kTCCServiceAccessibility to com.apple.dt.Xcode-Helper (not Xcode) - kTCCServiceDeveloperTool to com.apple.Terminal - Both system AND user-level TCC databases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Reduce UI test timeout to 15s for WarpBuild expected failures WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background"). Tests still execute and report expected failures. But the 62s per-test activation timeout makes 30+ tests take 30+ minutes total. Set per-test timeout to 15s so expected failures resolve quickly. Full interactive UI test coverage runs via test-e2e.yml on GitHub-hosted runners with proper display support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replace XCUITest run with build + lag regression on WarpBuild WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background" with 62s activation timeout per test). Tried TCC permissions, DevToolsSecurity, virtual display, reduced timeouts, nothing fixes the framework-level issue. Replace tests-ui job with tests-build-and-lag: - Build the full cmux scheme (verifies compilation) - Run workspace churn typing-lag regression (socket-based, no GUI) - XCUITests run via test-e2e.yml on GitHub-hosted runners Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Move macOS 14 compat to WarpBuild (no GitHub-hosted runners) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add diagnostic workflow to probe WarpBuild GUI activation Tests multiple app activation approaches on WarpBuild VMs: - open -a, NSWorkspace, NSRunningApplication.activate, osascript - Virtual display state before/after CGVirtualDisplay - TCC/accessibility permissions, Quartz session info - VM type detection This is a workflow_dispatch-only diagnostic to determine if XCUITest can work on WarpBuild with the right configuration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Trigger GUI probe on branch push (workflow_dispatch needs main) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Rewrite GUI probe with Swift (Python lacks AppKit on WarpBuild) v1 failed because WarpBuild's Python isn't a framework build and can't import AppKit/Quartz. v2 uses a compiled Swift binary to test NSRunningApplication.activate(), osascript, Quartz session state, display info, and AX trust. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * GUI probe v3: try 5 approaches to unlock WarpBuild screen 1. defaults write (screensaver, loginwindow, pmset) 2. automationmodetool enable-automationmode-without-authentication 3. CGSSessionSetScreenLocked private API + System Events keystroke 4. sysadminctl -screenLock off + keychain unlock 5. CGEvent simulation (mouse move + Return key to dismiss lock) Each approach is followed by an activation check to see if it worked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Test GUI activation on macOS 14, 15, and 26 (Tahoe) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add DerivedData and GhosttyKit caching to CI workflows Major caching improvements across ci.yml and ci-macos-compat.yml: - Cache GhosttyKit.xcframework keyed on ghostty submodule SHA (skip download on cache hit) - Cache DerivedData keyed on OS + Xcode version + Package.resolved + project.pbxproj (enables incremental builds across runs) - Remove explicit DerivedData wipe (rely on cache key invalidation) - Use download-prebuilt-ghosttykit.sh in compat workflow too This should significantly speed up macOS 14 compat tests which were taking 20+ min due to full recompilation every run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Bump macOS 14 compat timeout to 45 min for cold cache seeding The DerivedData cache wasn't saved because the job timed out at 30 min, causing the post-job cache save step to be skipped. 45 min gives enough headroom for the first uncached run to complete and seed the cache. Subsequent runs should be much faster with incremental builds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use Depot runners for E2E tests (WarpBuild has screen lock on macOS 15/26) WarpBuild VMs on macOS 15 and 26 have CGSSessionScreenIsLocked=1, which prevents XCUIApplication activation. Depot runners have working GUI activation. Can switch back to WarpBuild once they fix the VM images. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Skip smoke test on macOS 14 compat, remove GUI diagnostic workflow macOS 14 was slow because it built the full app (cmux scheme) on top of unit tests (cmux-unit scheme). Unit tests are the real compat check; smoke test runs on macOS 15 only. Also removes the temporary test-warpbuild-gui.yml diagnostic workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace Sonoma with Tahoe in compat matrix, drop macOS 14 Swap macOS 14 (Sonoma) for macOS 26 (Tahoe). Smoke test runs on macOS 15 only (WarpBuild screen lock blocks app activation on 26). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Drop macOS 26 from compat matrix (zig 0.15.2 linker failure) Zig 0.15.2 can't link against the macOS 26 (Tahoe) SDK: undefined symbols for basic libc functions (_abort, _free, _fork, etc.). The zig toolchain needs an update to support Tahoe. Keep macOS 15 only for now. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Fix release browser portal compile * Add macOS 26 (Tahoe) compat tests, skip zig build via stub (manaflow-ai#1590) Zig 0.15.2's MachO linker can't resolve libSystem on macOS 26 (the version number jump from 15 to 26 breaks zig's SDK handling). The unit tests don't need the CLI helper binary at runtime, so we skip the zig build on macOS 26 by setting CMUX_SKIP_ZIG_BUILD=1, which creates a stub binary to satisfy the Xcode Run Script file check. Smoke test (full app build + launch) is skipped on macOS 26 since it needs the real CLI helper. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add regression tests for SSH remote CLI follow-ups * Fix SSH remote CLI and loopback proxy follow-ups * Fix remote daemon build script using relative output path after cd (manaflow-ai#1595) The Go build runs in a subshell that cd's to daemon/remote/, but OUTPUT_DIR was relative to the repo root. Resolve to absolute path after mkdir so go build -o writes to the correct location. Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address SSH follow-up PR review comments * fix: restore Sparkle automatic update checks (manaflow-ai#1597) * feat: add native MCP protocol support to socket server Add MCP (Model Context Protocol) Content-Length framing support directly to the cmux socket server, enabling AI tools to connect via socat without needing a separate Node.js MCP wrapper process. Protocol detection on first read: "Content-Length:" → MCP mode, "{" → V2 JSON-RPC, else V1 plain text. All three protocols coexist on the same Unix socket. New files: - MCPServer.swift: Content-Length framing parser, MCPHandler with 3 JSON-RPC methods (initialize, tools/list, tools/call), and 20 MCP tool schemas that route to existing V2 socket methods - MCPServerTests.swift: 28 unit tests covering framing, handler logic, and tool routing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address MCP server review findings - Fix test initialization: tests calling tools/list and tools/call now send initialize first (XCTest creates fresh instances per test) - Add testToolsListBeforeInitializeReturnsError to cover the guard - Fix MCP message loop: continue parsing after each message instead of breaking after one, avoiding latency when multiple messages arrive in a single socket read - Forward press_enter param in send_input tool routing - Quote V1 command arguments to prevent injection via tokenizer - Add writeSocketData helper with EINTR/partial-write handling - Add encodeResponse fallback for non-serializable dicts - Require initialized handshake before tools/list and tools/call - Reject MCP connections when password auth is required - Close connection on corrupted data after MCP detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> Co-authored-by: Austin Wang <austinwang115@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add CmuxMobileSSH core and direct SSH PRD SwiftNIO SSH over Network.framework: connect with host key policy, key/password auth, exec, PTY shells with resize, direct-tcpip channels, jump hosts, subsystem channels. OpenSSH private key parsing for Ed25519 and ECDSA. Live integration tests against a local sshd lab. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add SSH port forwarding, key/host stores, key installer, encrypted key import Local forwards over direct-tcpip for the in-app browser, Secure Enclave and imported keys in the Keychain, local host records with known-hosts pinning, password-once authorized_keys install, and bcrypt_pbkdf + AES decryption for passphrase-protected OpenSSH keys. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add SFTP v3 client for the SSH file browser Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add SSH computers runtime to the mobile shell SSH hosts publish workspace rows through workspacesByMac like the demonstration computer. Demo-only branch points become locally-served checks so SSH surfaces route input, replay, viewport, and composer paste to the SSH runtime instead of a Mac. Plain, tmux, and cmux-tui persistence providers; cmux-tui client with bytes-mode attach and phone geometry; npm-verified cmux-tui upload; TOFU and changed-key prompts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Expose SSH file, forwarding, and lookup API for the UI Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add SSH terminal fidelity, file browser, port forwarding, image paste The phone's emulator answers terminal queries for plain/tmux SSH surfaces and filters its replies for cmux-tui (whose server already answers), SSH surfaces get local pixel scrolling and mouse clicks, sign-out keeps SSH rows, the app injects a persistent SSH runtime, and SSH workspaces gain an SFTP file browser, port forwarding into the native browser, and image paste over SFTP. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add SSH computers UI: Computers section, host editor, keys, prompts, signed-out use SSH hosts get their own section in Computers and open their workspace list; add/edit form with key picker, jump host, persistence, idle close, and password-once key install; SSH key management (Secure Enclave generate, OpenSSH import); root-level trust/changed-key/persistence prompts; and SSH without a cmux account. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Register CmuxMobileSSH in workspaces and pin SwiftNIO SSH dependencies Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: per-terminal idle-close policy (terminal-idle-close-v1) set-terminal-idle-policy stores an idle close time per hosted terminal in the registry; an owner-side reaper closes terminals with no attached views past their deadline through the normal close path. Reattach resets the clock; null clears the policy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add cmux-tui browser surfaces over SSH, idle policy client, installer lab test, translations cmux-tui browser tabs stream into the existing browser stream pane with tap/scroll/keys/navigation; the phone applies the host's idle-close policy when the server supports terminal-idle-close-v1; the cmux-tui upload is lab-tested; all SSH strings are localized in the 9 catalog languages. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * reload-build: cap Xcode selection at the pinned toolchain major The hosted Blacksmith macos-26 image now carries the Xcode 27 RC and select-ci-xcode.sh ranks by newest macOS SDK, so every dispatched iOS dev-build archive switched to the 27 SDK and fails compiling main's SwiftUI (toolbarMinimizeBehavior: https://github.com/manaflow-ai/cmux/actions/runs/35783022784 and https://github.com/manaflow-ai/cmux/actions/runs/35786136840). Feed the selector's existing CMUX_CI_MAX_MACOS_SDK_MAJOR ceiling from .xcode-version's major so the ranking keeps the newest pinned-major Xcode and skips unvalidated newer SDKs, with the older-runner fallback unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * cmux-tui: rustfmt idle-close files Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Accept smart-punctuation-mangled OpenSSH key armor Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * reload-build: sign the simulator leg to run locally so Keychain works Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: address seeded idle-close test terminals by stable id Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: satisfy clippy in idle-close reaper Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Fix SSH issues found in simulator verification - Handshake deadline pauses while the host key prompt is open; a declined key reports hostKeyRejected (including behind a jump host), not a timeout. - Replayed history and cmux-tui snapshots strip terminal query requests, so the phone never answers stale queries into the PTY (Mac manaflow-ai#10332 class). - Replacements fully reset the local terminal before replaying history. - Selected SSH host auto-connects on launch/foreground; explicit disconnect or a declined prompt stays manual. - Signed-out SSH mode skips Mac-centric What's New/pairing sheets. - Literal text entry (no autocorrect) on SSH host and key fields. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * PRD: record verification status Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH round 2: tmux control mode, browser modes, UI polish - tmux via control mode: session = workspace, pane = tab, New Terminal opens a window, phone attaches through its own grouped session. - cmux-tui New Terminal; plain workspaces have no terminal tabs. - cmux-tui: re-snapshot when a full-screen app exits so shell history behind it returns. - Browser: shared bottom chrome for streamed and native browsers, a Streamed / On iPhone mode picker remembered per browser, SSH On iPhone routed through a SOCKS5 proxy over SSH plus a loopback port mirror. - Files chip opens SFTP at the shell's directory; title-menu SSH items and the open-port sheet removed. - Sign-in 'Use with SSH only', mode-aware empty states with Mac-parity status, + chooses a computer under All Computers, declined identity pauses auto-connect across relaunch, key origin on every key row, friendly Face ID errors, no dev-tag suffix on SSH hosts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Fix tmux server crash on abrupt phone disconnect and SSH list bugs - Phone grouped sessions no longer use destroy-unattached (tmux 3.7c frees a session another exiting client still references and segfaults); the phone kills its grouped session on close and collects stale ones on connect. Repro: 8/10 crashes before, 0/20 after. - Paired-Mac reconcile, team switches, and Mac outage handling leave SSH computers alone; newest listing wins; lists refresh on appear/active. - SSH workspace ids resolve through the row's RPC id, so New Terminal works when several computers are live. - Strip screen-style title sequences (ESC k ... ST) from tmux pane output. - A successful refresh after a failure reports the host connected. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * PRD: round 2 decisions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH round 3: mixed session kinds per host One SSH connection per host serves cmux-tui workspaces, tmux sessions, and plain shells side by side. Each workspace row is one kind's top-level primitive (subtitle shows the kind); + offers a menu of kinds; existing server sessions and cmux-tui workspaces from any cmux-tui session are discovered; the tab switcher groups tmux windows and cmux-tui screens with New Window / Split Pane / New Screen / New Tab. Per-host persistence mode and the first-connect prompt are removed. The phone claims cmux-tui geometry only while a terminal is on screen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Confirm before ending tmux sessions and cmux-tui workspaces over SSH Every close entrypoint asks through one store decision: tmux and cmux-tui rows outlive the phone, so ending one explains what stops on the computer; plain shells close in one tap. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: test that a displaced terminal geometry owner reclaims when the new owner leaves Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: hand terminal geometry back to the displaced owner When a phone claimed a shared terminal's geometry and then released its viewport or disconnected, the grid froze at the phone's size and the laptop client that it displaced stayed non-authoritative until its user focused a pane. Each terminal runtime now remembers the owners a claim displaced. When the current owner releases, disables its sizing, or disconnects, the most recent displaced owner that still reports a viewport for a view of that terminal becomes the owner again and the PTY resizes to its report. Departed clients are forgotten, and an explicit use-all-sizes release still freezes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test SSH version line race and shared trust prompts Two failing regressions found in simulator verification of password-once key install: - A server version line that arrives before the caller resumes from the TCP connect is dropped, so the handshake stalls until the timeout (every app-launch auto-connect timed out in the simulator). - Saving a new computer auto-connects it while Install with Password logs in; the second trust question cancelled the first, failing the install with "server identity not trusted" before the user answered. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Install the SSH handler before connect; share identical trust prompts SSHConnection.connect added NIOSSHHandler to the pipeline only after the awaited TCP connect resumed. Servers send their version line on accept, so when the caller's resumption was delayed (a busy cooperative pool at app launch) the line hit an empty pipeline and was discarded, and the client never sent KEXINIT. The handler and handshake observer are now installed by the bootstrap's channel initializer (and the jump channel's initializer), so no inbound byte can precede them. The handshake budget now also covers the TCP connect, matching its documentation. MobileSSHComputers.ask cancelled any pending waiter with the same prompt id. An identical question (same host, address, and keys) now joins the pending prompt and receives the same answer; a question about a different key still replaces the stale one with a cancel. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: reconnect after key install; plain wording for connect failures A key install that succeeds clears the refusal left by a connect that ran before the key existed and connects with the key. Refused, timed-out, unresolvable and unreachable connects read as sentences instead of POSIXErrorCode values. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test that a stale Cancel after Trust does not pause the host When the trust sheet goes away after Trust and Connect, SwiftUI writes nil through the sheet binding and the presenter answers the last rendered prompt with Cancel. That pauses the host's automatic connects, so a newly added computer never reconnects at app launch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Ignore answers for SSH prompts that are no longer pending After Trust and Connect, the dismissing prompt sheet writes nil through its item binding and the presenter answers the prompt it last rendered with Cancel. answer() treated that as a decline and persisted autoConnectPaused, so a newly added computer never auto-connected again. answer() now acts only on a prompt that is still pending with the same question; a stale or superseded answer is a no-op. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Conform the SSH packages to the iOS package conventions The iOS package-conventions lint flagged 28 errors in code this branch added. Each namespace enum becomes a value or moves onto the type it serves: - Copy (L10nSSH, SSHCopy, SSHKeyErrorCopy, MobileSSHBiometryErrorCopy) becomes instantiable structs, like MobilePairingCopy. - Parsers and codecs become initializers on their output: SSHParsedPrivateKey(openSSH:passphrase:), SSHHostKeyVerdict(presented: pinned:), MobileSSHTmuxLayout(_:).leaves, CmuxTUIBrowserEventWire(line:) .surfaceEvent, Decodable.init(cmuxTUILine:), Data(cmuxTUIBase64:). - Configured workers become values: BcryptPBKDF(rounds:), SSHPrivateKeyDecryption(cipher:kdfOptions:), SSHKeyInstaller( sshDirectory:), MobileSSHCmuxTUIInstaller(binDirectory:). - SSH ids become a MobileSSHIdentifier value over the raw string. - Wire constants become typed values (SFTPStatusCode, SFTPAttributeFlags as an OptionSet). - Shell quoting and the terminal query-reply filter move onto String and Data. The two NIO auth delegates drop OSAllocatedUnfairLock and become actors. NIO completes both callbacks through promises, so the actor hop only delays the promise; the host-key delegate still pauses the handshake deadline synchronously on the event loop before hopping. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: four regression-pass UX fixes - SSH-only mode no longer reads "Mac update required" on the Computers button. The toolbar label treated SSH computer ids as Macs; with no version on record the Mac floor called them outdated. The label now scopes its warning to paired-Mac ids. - Saving a new SSH host, or new connection details, connects it through MobileSSHComputers.autoConnect (saveHostAndConnect). Changed details close the stale connection and clear the old failure. The password install keeps its own connect after the key lands. - The terminal keeps the keyboard after the system "Allow Paste" alert. The alert makes the app inactive, and the input-session reducer forgot the focused owner on every resign. It now restores the owner a foreground interruption took, and forgets it on background or any newer intent. Shared by SSH and paired-Mac terminals. - The SSH Files chip shows when the terminal opens. It is the only entry to the server's files, so it no longer waits for a scroll reveal (TerminalFilesChipReveal.always); the Mac chip stays scroll-revealed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * PRD: round 4 decisions and verification Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: await queued auto-connect pause writes instead of polling The pause flag is persisted from an unstructured Task, and the tests waited for it with a 1000-yield poll that the test-determinism gate rejects (yield-count-poll). Chain the writes through one stored task so a pause and a later resume always land on disk in order, and let tests await that task directly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui SDKs: count set-terminal-idle-policy in the command inventory tests The idle-close change added set-terminal-idle-policy to the generated protocol (113 commands) but the per-language coverage tests still pinned 112, so every SDK package job failed on the exact count. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: only the newest pause write restores the in-memory flag Each pause-flag write re-applied its own value to the in-memory host after landing, to undo a reload that read the old flag. When a resume followed a pause (opening a host behind a declined jump host), the older pause write landed afterwards and set the flag back to paused until the resume's write caught up, which aDeclinedJumpHostPausesTheHostsBehindIt caught under full-suite load. Writes now carry a per-host generation and only the newest one touches memory. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: creating on a selected SSH computer does not need the Mac WorkspaceMacSelectionScope.canCreateWorkspace checked the foreground Mac's create gate before the locally served (SSH) case, so with no Mac connected the SSH computer's create action was disabled even though it creates on its own connection. sshComputerIsSelectableAndCreatableWithout WorkspacesOrMac covers this and failed in the iOS simulator lanes. The locally served case now returns before that gate; a pending computer switch still blocks it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: keep the Devices toolbar label's ID filter off the main actor MobileDevicesToolbarLabel.macPairingIDs is a static helper on a SwiftUI View, so its filter closure inherited main-actor isolation and trapped the whole xctest process when sshComputersNeverCountAsMacsForTheWarning called it from a nonisolated test. In the iOS simulator lanes that crash took down hundreds of unrelated tests per launch (xcresult: "Crash: xctest at closure #1 in static MobileDevicesToolbarLabel.macPairingIDs"). The pure helpers are now nonisolated, and the test that builds the view runs on the main actor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui SSH: list hashed session sockets, add split, learn session from identify Sessions whose names are too long for a socket path live at cmux-tui-hashed-<uid>/<sha256>.sock. Discovery now lists them next to named sockets (first runtime directory per session wins) and matches a session to a hashed socket by SHA-256 digest. The control's session is the name the owner reports in identify, so a hashed socket learns it. Socket names follow the server's validate_session_name (spaces, Unicode, long names are valid); server ensure keeps its stricter rule. Adds CmuxTUIControl.split(pane:direction:) for Split Pane and SSHConnection.isOpen. Lab-free wire tests drive a scripted peer over an in-memory channel: identify-reported session, subscribe routing of tree-changed/surface-exited, split params and errors. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: cmux-tui rows follow remote topology; Split Pane per screen Topology: each cmux-tui provider subscribes on its control connection. tree-changed, surface-exited, empty, overflow, daemon shutdown, and an owner that went away (control dropped while SSH lives) ask the runtime to relist through the existing onTopologyChange path. A gate coalesces a burst to one request per listing; titles, sizes, and bells never relist. No timers. Hashed sessions: the registry keys a hashed socket's provider by the name its owner reports and finds cached providers by digest. Split Pane (D32): a cmux-tui screen section offers New Tab, then Split Pane, which splits the screen's active pane to the right (split). tmux windows keep Split Pane only. Section actions are one enum shared by the store, the registry, and the picker; the label reuses the localized mobile.ssh.tabs.splitPane string. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: test that another client's creations keep a frontend's view A phone creating a screen, a tab, or a split through new-screen, new-tab, or split moves the shared tree's active fields only; an attached frontend keeps the screen, pane, and tab it shows (preserve_client_view, present since before 0.13.4). Test-only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * PRD: D40-D43 for the deferred cmux-tui items Hashed sockets listed, rows follow remote topology through subscribe, cmux-tui Split Pane, and phone creations keep the laptop's view. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: plain shells report their folder to Files through OSC 7 The Files chip in a plain SSH shell always opened the remote home folder: MobileSSHPlainProvider did not conform to MobileSSHCurrentDirectoryProviding, so currentDirectory(surfaceID:) returned nil for every shell. tmux and cmux-tui can be asked for a pane's folder; a plain login shell cannot. Terminals learn a shell's folder from the OSC 7 report (ESC ] 7 ; file://host/path) the shell prints before each prompt, which fish sends by default and zsh/bash send with terminal shell integration. MobileSSHWorkingDirectoryReport reads those reports passively from the channel's output (split chunks joined, BEL or ST terminated, percent-decoded, bounded), and the plain provider keeps the newest one per shell and answers currentDirectory with it. Nothing is sent to the shell and no dotfile is touched; a shell that never reports keeps the home-folder fallback. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: a relaunch lands on the SSH computer used last The signed-out SSH shell cannot show "All Computers" (its empty states are Mac-oriented), so on launch selectSSHComputerForSignedOutShellIfNeeded scopes the list to an SSH computer whenever the saved scope is not one. It always picked hosts.first, the oldest host, so after using another host under "All Computers" (or deleting the selected host) every relaunch went back to the first computer instead of the one in use. A paired Mac is restored from its persisted active flag, written when the user switches to it. SSH computers now keep the same fact: MobileSSHComputers.open(hostID:), the one path every SSH selection goes through (title picker, Computers screen, new host, row switch), records the host in SSHHostStore (ssh-last-used-host.json, cleared when the host is deleted), reload() restores it before publishing hosts, and preferredHost (last used, else oldest) is what the signed-out shell selects. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: name the switcher's browser section by computer kind The terminal switcher listed an SSH computer's browser tabs under "Mac Browsers": TerminalPickerMenu hard-coded that section title for every workspace, and the tabs of a cmux-tui host are not on a Mac. TerminalPickerMenuValue now carries whether the workspace belongs to an SSH computer and titles the section "Browsers" there (new key mobile.ssh.browserStream.menuTitle, all nine languages); cmux Mac workspaces keep "Mac Browsers". The kind is part of the menu value, so the native menu rebuilds when it changes. HIG Menus: a section title names its group. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: Browse Files in the workspace title menu Browsing an SSH terminal's files was only reachable from the Files chip on the terminal, a control the user is deciding whether to keep always visible. HIG Toolbars places whole-document commands in the document menu next to the title, and HIG Menus asks for verb labels, so the workspace title menu now offers "Browse Files" (folder symbol, own section above the workspace actions) whenever an SSH terminal is showing. It calls the chip's own presentSSHFiles(terminalID:), so both open the same SFTP browser at the shell's current folder. The chip is unchanged. New key mobile.ssh.files.menuItem in all nine languages. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH tmux: pin the control-mode seed order (B6 investigation) B6 reports a tmux pane that is sometimes blank on first open and renders after reopening. This Mac has no free PTYs, so tmux cannot start a pane here; instead the seed path is pinned with an in-memory tmux -C stream. MobileSSHTmuxControlClient now takes its byte pipe through a small MobileSSHTmuxControlTransport protocol (SSHSessionChannel conforms; tests pass a pipe), with no behavior change. MobileSSHTmuxSeedOrderTests drives the provider's attach order and checks that refresh-client -C precedes the pause/capture/state/continue batch in one ordered stream, a flag-0 startup reply block is not taken as the reply to the phone's first command, %output before the capture reply is dropped (the capture has it), and the pane gets the grid, then the snapshot, then output that followed the capture. It passes on the current code, so the blank first open does not come from the client's seed ordering; the remaining suspects are on the phone side (see artifacts/dssh/v3/night/results.md). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: pin seed delivery across late teardown and before the first grid (failing) Two of the three new tests fail on the current code: a late teardown of the previous view retires the new view's viewport negotiation and the SSH attach never starts, and a replay before any grid attaches at 80x24. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: attach at the phone's grid as soon as the view subscribes (B6) The SSH attach was triggered by the Mac cold-replay deferral, which waits for the viewport acknowledgement. A late teardown of the previous view (clearTerminalViewport after the new view subscribed) retires that negotiation, so the replay never ran: no attach, no tmux capture-pane or cmux-tui vt-state seed, blank until a reopen. And a replay with no grid yet attached at a placeholder 80x24 and resized afterwards. The phone owns SSH geometry, so the grid is recorded when the viewport is prepared (before the sink registers), SSH sinks replay at registration without waiting for the negotiation, and an attach with no known grid waits for the first one (input typed meanwhile is queued) instead of seeding at 80x24. Output that arrives with no subscriber was already kept in the surface's replay buffer and repainted on subscription; the new test pins that too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: offer Reconnect only when the host or the shown session is down The workspace title menu used the Mac rule for every row, so a connected SSH shell offered Reconnect, and choosing it ran the Mac redial path (switchToMac with an SSH id, then reconnectOrRefresh of the foreground Mac) instead of touching the SSH host. MobileSSHComputers now owns both halves: canReconnect(hostID:surfaceID:) is true when the host is idle or failed, or the shown terminal's session ended (tracked in endedSurfaces), false while connecting and for a live terminal on a connected host; reconnect(hostID:surfaceID:) opens the host and reattaches the shown terminal when it is not live (an ended shell opens a new one). Mac rows keep canReconnectFromTitleMenu. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: a refused terminal request reads as a sentence channelRequestRejected("pty-req") reached the terminal as a raw enum dump. A refused pty-req or shell now reads "This computer refused to open a terminal. Try again."; any other refused request reads "This computer refused the request (<reason>)." keeping tmux's own reason. Nine languages. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * SSH: New Workspace follows a host switch The + menu is Equatable on its value alone, and the value held only the kinds, which are the same on every host. After switching SSH hosts SwiftUI kept the old menu and its create closure, so the first New Shell opened on the previous host: in the night pass that host was a real sshd out of PTYs, which refused pty-req (channelRequestRejected), and the server being watched logged no session. A retry worked because the menu had re-rendered by then. The value now carries the SSH target host, so a host switch invalidates the menu and its action. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * PRD: overnight changelog Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: test that an empty workspace create reaches a subscriber `workspace create --empty` goes through the resource router's pure creation path, which commits the registry patch without emitting a coarse MuxEvent, so `subscribe` clients (phones, native attach frontends) never see a tree-changed push for it; the row appears only when the next real change flushes. Seen live in the direct-SSH v4 pass (artifacts/dssh/v4/pty-live/results.md, check 5). This commit adds the failing regression; the fix follows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * cmux-tui: push tree-changed when an empty workspace is created The resource router's pure creation path (resource_create_empty_workspace_selected) committed the registry patch and published only the journal event, never a MuxEvent, so subscribe clients learned about `workspace create --empty` only when the next real change flushed. Emit the same WorkspaceAdded TreeDelta the legacy create-workspace path emits, after the patch applies, with the entity snapshot and workspace revision the delta contract requires. Server-side only: SSH hosts see it once a cmux-tui release past the pinned 0.13.4 ships (PRD changelog updated). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: test that a dead tmux server asks for a relist v4 pty-live finding: when the tmux server ends, its control-mode exec channel dies while the SSH connection stays up, and sessions on the next server never appear until a manual pull-to-refresh. Adds the failing regression (an unexpected control death on a live host must request one relist and forget the per-server grouped-session collection pass) plus the behavior-preserving seams it drives: a nil-connection test path like MobileSSHPlainProvider's, the control wiring extracted into adopt(_:session:), an injectable hostConnectionIsOpen, and a pump completion await on the control client. Scripted in-memory tmux -C pipe, no PTY, no sleeps. The fix follows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: regression test for a deleted key reading as a raw error A host whose key was deleted keeps a dangling keyID; a connect then loads a key whose secret is gone and throws SSHKeyStoreError.missingSecret, which MobileSSHComputers.describe(_:) rendered as the raw enum case name in the terminal and the row status. This test pins that it must read as the 'choose a key' sentence instead. Fails on the current code; the next commit fixes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: a deleted key reads as 'choose a key', not a raw error MobileSSHComputers.describe(_:) had no case for SSHKeyStoreError, so a connect on a host whose key was deleted (the host keeps a dangling keyID, and privateKey(for:) throws missingSecret) fell to String(describing:) and showed the literal 'missingSecret' in the terminal and the row status. Map SSHKeyStoreError.missingSecret to the existing localized noKey copy ('Choose a key for this computer first.'), the same guidance the noKey path gives and the exact recovery the user needs (re-pick a key in the editor). Covers every missing-key-secret cause (deleted key, keychain eviction, restore) with no new string. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: relist once when the tmux server dies under a live connection A control client that ends while still registered was closed by tmux, not the phone (phone-initiated closes remove it from the registry first). When the SSH connection is still open that means the server (or this session) ended: forget the per-server stale-grouped-session collection pass, which belonged to the dead server, and fire the existing onTopologyChange relist path once, so a restarted server's sessions appear without pull-to-refresh. The relist reuses refreshWorkspaces' generation coalescing and failure handling; no timers, one relist per death. Connection teardown stays on the runtime's own close path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: test that a failed Reconnect answers in the terminal Tapping Reconnect in the title menu while the computer is still down changes nothing on screen: the title already read Disconnected, the row already carried the failure sentence, and nothing new reaches the terminal, so the tap looks ignored (v4 edges pass, scenario 1). Expected to fail until the next commit delivers the failure sentence to the shown surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: a failed Reconnect prints its failure sentence in the terminal Reconnect against a still-down computer already walks connecting -> failed, but a refused loopback connect resolves in milliseconds and the failed state renders the exact chrome (red Disconnected) shown before the tap, so nothing visibly happens. reconnect(hostID:surfaceID:) now delivers the failure sentence to the shown surface after a failed open, with the same red-notice rendering a failed attach uses (shared errorNotice helper). A declined identity prompt leaves the status idle, so cancelling stays quiet; the still-visible Connecting/Reconnecting state during a slow connect is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: All Computers empty state stops pitching Mac pairing to SSH-only users The aggregated (All Computers) empty state always rendered the Mac-pairing copy ("Enable iOS pairing in cmux Settings > Mobile on your Mac..."), which describes a Mac an SSH-only user does not have; per-host SSH empty states were already right (v4 edges pass, secondary observation; PRD D29 mode-aware empty states). WorkspaceListEmptyGuidance decides from what exists: SSH computers with no paired Mac get SSH guidance (the per-host "No Workspaces" title, a terminal icon, and "Choose a computer from the menu at the top, or add one from the Computers screen."), and any paired Mac keeps the Mac copy. The SSH variant also drops Retry and See Docs, which drive the Mac workspace-list recovery and the Mac pairing docs. The guidance rides the table snapshot into the empty row model, so a change re-renders the row. New string localized in all nine app languages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: regression that a dropped transport leaves the terminal with no 'Session ended' line connectionClosed removes the host's attachments silently and relies on each child channel's own close event to write the '[Session ended]' line, so a transport drop whose channel close lags leaves the shown terminal with nothing. MobileSSHConnectionDropTests.droppedTransportEndsTheShownTerminal drives the connection-close path alone and fails: no notice, endedSurfaces empty. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: a dropped transport ends its terminals deterministically A whole-connection drop (server death) now routes through the same idempotent per-surface end path a child channel's close uses, so the '[Session ended]' line and the endedSurfaces mark land on the transport close instead of waiting on each channel's own close event, which can lag arbitrarily under load. It also drops stale attachAwaitingGrid entries so a reconnect re-seeds through the ordinary subscribe path. handle(.ended) and connectionClosed share endTerminalSurface, which is idempotent through endedSurfaces so the two paths never double-print. MobileSSHConnectionDropTests.droppedTransportEndsTheShownTerminal now passes; channelCloseEndsTheShownTerminal and reconnectWhileSubscribedRepaints guard the channel-close line and the reconnect repaint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * PRD: overnight 2 changelog Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: the Files chip is scroll-revealed again, exactly like a Mac's Round 4 made the SSH Files chip always visible (TerminalFilesChipReveal .always) because it is the only entry to the server's files, but a chip that never fades sits over the first terminal rows. Aziz: it should look and behave exactly like Files on a paired Mac. The reveal special case is reverted: every terminal's chip is hidden at rest, shown while scrolling, and faded after the same linger, with the same assistive-technology bypass, through the same component. Browse Files in the workspace title menu (D28 follow-up) remains the always-visible entry point, so discoverability does not regress. TerminalFilesChipReveal and its tests are deleted as dead code; the SSH chip's persistent mount (no artifact count to gate it) is unchanged (PRD D44). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: Split Right and Split Down replace the single Split Pane The grouped tab switcher's one "Split Pane" always split one way (tmux stacked below, cmux-tui to the right), with no way to pick the other orientation. It is now the two directional actions the cmux macOS app has, with the same names, translations (all nine app languages, copied from the macOS catalog), and SF Symbols: Split Right puts the new pane side by side (tmux `split-window -h`, cmux-tui `split dir:"right"`) and Split Down stacks it (`-v` / `dir:"down"`), on both tmux windows and cmux-tui screens, still detached so no attached client's view moves (PRD D45, superseding D32/D42's single action; HIG Menus: one item per action). MobileSSHSectionAction carries its direction; the tmux flag mapping is a pure helper with a unit test, and the cmux-tui wire tests already pin `dir` for both values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * SSH: empty workspace lists render the paired-Mac empty state An SSH host's "No Workspaces" overlay was a bare ContentUnavailableView and the SSH-variant All Computers row used its own terminal icon and title, so SSH empty states looked like a different app from a paired Mac's. The paired-Mac empty row's visual scaffold (macbook.and.iphone icon at 44pt, "No workspaces yet" title2.bold, secondary message, spacing, width cap) is now one shared view, WorkspaceListEmptyStateScaffold, used by the table row and the per-host SSH overlay, so every empty state is pixel-identical. Only what must differ differs: SSH messages carry the host's status line (per host, still inside the pull-to-refresh scroll view with auto-connect) or the choose/add-computer line (All Computers), and the Mac-only Retry and See Docs buttons stay on the Mac variant, which drives Mac workspace-list recovery and pairing docs (PRD D46). The mobile.ssh.empty.title key is now unused and removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * iOS: remove the SSH-only entry; every user signs in The sign-in screen's quiet "Use with SSH only" entry, the signed-out SSH shell it opened (SSHOnlyWelcomeView, MobileSSHOnlyPreference, the mobileSSHOnlyEntry environment action), and the audience machinery that suppressed Mac notices for it (MobileWhatsNewAudience) are removed: MobileRootAuthGate.shouldShowSignIn no longer takes a bypass, so a signed-out launch always lands on sign-in and every user signs in before using the app (PRD D47, superseding D5's no-account entry; deliberate deviation from HIG Managing accounts, since every cmux surface is account-backed). The SSH computers feature itself is untouched for signed-in users: hosts and keys stay on-device, the Computers screen and pairing's "Connect with SSH Instead" still add hosts, and WorkspaceListEmptyGuidance still keys on has-SSH-computers / no-paired-Mac, which a signed-in account before its first pairing hits. An attach-ticket session (no Stack account) keeps its settings sign-in row. Localization keys for the removed strings are deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * PRD: round 5 (D44-D47) Files chip back to the Mac scroll reveal, Split Right/Split Down, one empty-state scaffold, and required sign-in; D5/D29/D42 and the round-4 chip note updated to point at their supersessions; changelog line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
socat STDIO UNIX-CONNECT:/tmp/cmux.sock— zero external processes neededContent-Length:→ MCP,{→ V2, else V1 — all three coexist on the same socketArchitecture
New file:
Sources/MCPServer.swift(~400 lines)MCPFraming: Content-Length framing parser/encoder (LSP-styleContent-Length: N\r\n\r\n{json})MCPHandler: JSON-RPC 2.0 handler forinitialize,tools/list,tools/callMCPToolSchemas: 20 tool definitions + routing table mapping MCP tool names to V2 methodsModified:
Sources/TerminalController.swift(+142 lines)handleClient(): Detect MCP framing on first read, branch to MCP or V1/V2 read loopsmcpExecuteV2(): Bridge MCP tool calls → V2processV2Command()dispatchmcpExecuteV1(): Bridge MCP sidebar tools → V1processCommand()dispatchTest plan
MCPFramingTests: 8 tests — parse complete/partial/multi-message, notMCP detection, zero-length body, encode round-tripMCPHandlerTests: 10 tests — initialize response, tools/list (20 tools), tools/call routing, error handling, notifications, pingMCP
.mcp.jsonconfig (after merge){ "mcpServers": { "cmux": { "command": "socat", "args": ["STDIO", "UNIX-CONNECT:/tmp/cmux.sock"] } } }Part of Three-Daemon Sprint
Phase 4 of the Three-Daemon Sprint — replacing 26 duplicate MCP processes (1.6GB RAM) with 3 singleton daemons on Unix sockets.
🤖 Generated with Claude Code