Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a1950e8
docs: design secure remote desktop
codepilotios Jun 27, 2026
c24de64
docs: plan secure remote desktop
codepilotios Jun 27, 2026
b713427
chore: ignore local worktrees
codepilotios Jun 27, 2026
ce1876e
build: add remote desktop wire contracts
codepilotios Jun 27, 2026
375c834
fix: finalize remote desktop lease revocation
codepilotios Jun 27, 2026
6dd2d36
feat: add remote desktop native IPC
codepilotios Jun 27, 2026
807be31
fix: harden remote desktop native IPC
codepilotios Jun 27, 2026
c4575b0
feat: add remote desktop gateway APIs
codepilotios Jun 27, 2026
cf61d84
feat: add remote desktop trust management UI
codepilotios Jun 27, 2026
17f79a8
feat: stream and control the Mac desktop
codepilotios Jun 27, 2026
a98048e
feat: pair trusted CodePilot devices
codepilotios Jun 27, 2026
237c3ba
feat: add iPhone remote desktop controls
codepilotios Jun 27, 2026
54a3e88
feat: harden remote desktop sessions
codepilotios Jun 27, 2026
5183761
docs: specify WebRTC remote desktop transport
codepilotios Jun 28, 2026
0ade8bf
feat: add secure remote desktop foundation
codepilotios Jun 28, 2026
bbe8fa6
feat: follow remote cursor while zoomed
codepilotios Jun 28, 2026
7433da3
feat: stream display frames with ScreenCaptureKit
codepilotios Jun 28, 2026
1eab830
feat: negotiate Mac WebRTC remote desktop peer
codepilotios Jun 28, 2026
29db0ff
feat: render WebRTC remote desktop video on iOS
codepilotios Jun 28, 2026
d21b07b
feat: send remote desktop input over WebRTC
codepilotios Jun 28, 2026
f7f6868
fix: smooth remote desktop cursor overlay
codepilotios Jun 28, 2026
f073ee4
fix: align remote cursor coordinate space
codepilotios Jun 28, 2026
97ae3fe
fix: sync remote cursor from host
codepilotios Jun 28, 2026
1b60864
fix: align remote cursor hotspot
codepilotios Jun 28, 2026
1cf93c0
fix: scale remote pointer deltas by viewport
codepilotios Jun 28, 2026
0901b33
fix: use host cursor for remote desktop video
codepilotios Jun 28, 2026
b71c802
fix: remove remote desktop cursor overlay
codepilotios Jun 28, 2026
1e26028
fix: show host cursor marker
codepilotios Jun 28, 2026
635154a
fix: preserve remote input ordering
codepilotios Jun 28, 2026
453ccb0
fix: click at remote cursor marker
codepilotios Jun 28, 2026
e431a17
fix: align click injection with remote cursor
codepilotios Jun 28, 2026
30edfee
fix: match WebRTC video layout to viewport
codepilotios Jun 28, 2026
d1ed054
fix: align remote desktop cursor marker
codepilotios Jun 28, 2026
42711c2
fix: lower remote desktop cursor marker
codepilotios Jun 28, 2026
e7490f9
fix: preserve chat message line breaks
codepilotios Jun 28, 2026
7474bb5
feat: open Mac localhost URLs in iOS app
codepilotios Jun 28, 2026
c6b2969
docs: specify CodePilot production readiness
codepilotios Jun 28, 2026
85939e9
docs: plan CodePilot production readiness work
codepilotios Jun 28, 2026
fee4f5e
feat: expose stable gateway health and errors
codepilotios Jun 28, 2026
7661c80
feat: clarify CodePilot setup and gateway operations
codepilotios Jun 28, 2026
24bd6a6
feat: add CodePilot iOS connection setup
codepilotios Jun 28, 2026
52951f6
docs: prepare CodePilot public beta guides
codepilotios Jun 28, 2026
42fa143
test: isolate gateway tests from local account state
codepilotios Jun 28, 2026
5735508
docs: specify Cloudflare setup wizard
codepilotios Jun 29, 2026
be5e971
docs: plan Cloudflare setup wizard
codepilotios Jun 30, 2026
89949da
feat: add Cloudflare setup automation
codepilotios Jul 1, 2026
a575e4b
feat: model Cloudflare setup state
codepilotios Jul 1, 2026
b181f77
feat: add Cloudflare setup wizard
codepilotios Jul 1, 2026
1ef553b
docs: document Cloudflare setup wizard
codepilotios Jul 1, 2026
c783dff
docs: plan launch agent system
codepilotios Jul 1, 2026
85c6bcb
fix: open markdown file links in codepilot app
codepilotios Jul 1, 2026
a36684e
fix: add dismiss controls to file preview
codepilotios Jul 1, 2026
5b3346b
docs: record codepilot launch decisions
codepilotios Jul 1, 2026
1fdcb7a
chore: add codepilot agent fallback runner
codepilotios Jul 1, 2026
ae44193
chore: enable thread escalations for codepilot agents
codepilotios Jul 1, 2026
3ed1a30
Fix setup audit friction
codepilotios Jul 1, 2026
4828ccc
Merge remote-tracking branch 'origin/main' into agent/setup-audit
codepilotios Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/INSTALL_IOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Success means the app shows the active account from the Mac gateway.

The app stores these values locally on the device.

For the default Mac setup, choose **Cloudflare** and enter the Cloudflare Tunnel URL. The Mac gateway listens on `127.0.0.1:18790` by default, which is only reachable from the Mac itself. Use **Same Network** only after configuring the gateway to listen on a Mac LAN address.

The gateway URL must include `http://` or `https://`.

## App Store Connect Setup

The local Fastlane credentials live in the ignored file:
Expand Down Expand Up @@ -51,3 +55,7 @@ The iOS app can register for turn-finished notifications. APNs certificates or k
## File Uploads

Files selected in the iOS app are uploaded to the Mac gateway and saved under the CodePilot state directory. Image attachments can be passed into Codex turns when supported by the current runner.

## Remote Desktop

Remote Desktop requires the Mac app to show Screen Recording as granted before viewing and Accessibility as granted before control. If pairing reports missing permissions, grant them in macOS System Settings, restart CodePilot on the Mac, then retry pairing from the iOS app.
6 changes: 6 additions & 0 deletions docs/INSTALL_MAC.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ The gateway listens on:
http://127.0.0.1:18790
```

That local address is for CodePilot on the Mac and for Cloudflare Tunnel. A physical iPhone cannot reach `127.0.0.1` on the Mac directly; use Cloudflare remote access for the default setup, or deliberately start the gateway on a LAN address before using the iOS **Same Network** option.

The bearer token is stored at:

```text
Expand All @@ -68,3 +70,7 @@ Copy this token into the iOS app connection screen. Do not share it in issue rep
## Remote iPhone Access

For access away from the local network, open **Setup CodePilot...** in the Mac menu bar app and use **Cloudflare Remote Access**. The setup wizard can install `cloudflared`, sign in to Cloudflare, configure a permanent hostname, or start a temporary TryCloudflare URL for testing.

## Remote Desktop Permissions

Open **Remote Desktop...** from the Mac menu bar app to check Screen Recording and Accessibility. Screen Recording is required to view the Mac, and Accessibility is required for pointer and keyboard control. Restart CodePilot after granting either macOS privacy permission.
8 changes: 4 additions & 4 deletions gateway/codex_phone_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -4087,14 +4087,14 @@ def do_GET(self):
)
return

if path == "/health" or path == "/api/health":
json_response(self, 200, self.state().public_health())
return

if not self.authenticate():
return

try:
if path == "/health" or path == "/api/health":
json_response(self, 200, self.state().public_health())
return

if path == "/api/threads":
json_response(self, 200, {
"activeAccount": self.state().active_account_name(),
Expand Down
35 changes: 35 additions & 0 deletions gateway/test_codex_phone_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import io
import subprocess
import tempfile
import threading
import tomllib
import urllib.request
import unittest
from pathlib import Path

Expand Down Expand Up @@ -836,6 +838,39 @@ def test_public_health_exposes_safe_gateway_status(self):
self.assertIn("localWeb", health)
self.assertNotIn("secret-token", json.dumps(health))

def test_health_endpoint_is_available_without_bearer_token(self):
with tempfile.TemporaryDirectory() as tmp:
original_switcher_home = gateway.DEFAULT_SWITCHER_HOME
switcher_home = Path(tmp) / "switcher"
codex_home = Path(tmp) / "codex"
server = None
thread = None
try:
gateway.DEFAULT_SWITCHER_HOME = switcher_home
switcher_home.mkdir()
codex_home.mkdir()
(switcher_home / "active-account.txt").write_text("main", encoding="utf-8")
state = GatewayState(codex_home, "secret-token", Path("/missing-codex"), False)
server = gateway.GatewayServer(("127.0.0.1", 0), state)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()

url = f"http://127.0.0.1:{server.server_address[1]}/api/health"
with urllib.request.urlopen(url, timeout=2) as response:
status = response.status
payload = json.loads(response.read().decode("utf-8"))
finally:
if server is not None:
server.shutdown()
server.server_close()
if thread is not None:
thread.join(timeout=2)
gateway.DEFAULT_SWITCHER_HOME = original_switcher_home

self.assertEqual(status, 200)
self.assertTrue(payload["gateway"]["running"])
self.assertNotIn("secret-token", json.dumps(payload))

def test_error_payload_has_stable_code_message_and_recovery(self):
payload = gateway.error_payload(
"local_web_invalid_target",
Expand Down
24 changes: 16 additions & 8 deletions ios/CodexPhone/CodexPhone/CodexPhoneApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ enum GatewayConnectionKind: String, CaseIterable, Identifiable {
var helpText: String {
switch self {
case .local:
"Use the Mac gateway URL while your iPhone is on the same Wi-Fi network."
"Use only after configuring the Mac gateway to listen on a LAN address."
case .cloudflare:
"Use your Cloudflare Tunnel hostname for access when you are away from the Mac."
}
Expand Down Expand Up @@ -1726,8 +1726,20 @@ func isMacLocalWebURL(_ url: URL) -> Bool {
return host == "localhost" || host == "127.0.0.1" || host == "::1"
}

func gatewayRootURL(from rawValue: String) throws -> URL {
let trimmed = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard let url = URL(string: trimmed),
let scheme = url.scheme?.lowercased(),
["http", "https"].contains(scheme),
let host = url.host,
!host.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
throw GatewayError.invalidURL
}
return url
}

func localWebSessionURL(path: String, baseURL: String) -> URL? {
guard let root = URL(string: baseURL.trimmingCharacters(in: .whitespacesAndNewlines)),
guard let root = try? gatewayRootURL(from: baseURL),
var components = URLComponents(url: root, resolvingAgainstBaseURL: false) else {
return nil
}
Expand Down Expand Up @@ -4633,9 +4645,7 @@ struct CodexGatewayClient {
}

func downloadRemoteFile(path: String, baseURL: String, token: String) async throws -> URL {
guard let root = URL(string: baseURL.trimmingCharacters(in: .whitespacesAndNewlines)) else {
throw GatewayError.invalidURL
}
let root = try gatewayRootURL(from: baseURL)
guard var components = URLComponents(url: root, resolvingAgainstBaseURL: false) else {
throw GatewayError.invalidURL
}
Expand Down Expand Up @@ -4696,9 +4706,7 @@ struct CodexGatewayClient {
baseURL: String,
token: String
) async throws -> T {
guard let root = URL(string: baseURL.trimmingCharacters(in: .whitespacesAndNewlines)) else {
throw GatewayError.invalidURL
}
let root = try gatewayRootURL(from: baseURL)
guard let url = URL(string: path, relativeTo: root)?.absoluteURL else {
throw GatewayError.invalidURL
}
Expand Down
25 changes: 24 additions & 1 deletion ios/CodexPhone/CodexPhoneTests/RemoteDesktopTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ final class RemoteDesktopTests: XCTestCase {
func testGatewayConnectionKindUsesUserFacingTitles() {
XCTAssertEqual(GatewayConnectionKind.local.title, "Same Network")
XCTAssertEqual(GatewayConnectionKind.cloudflare.title, "Cloudflare")
XCTAssertTrue(GatewayConnectionKind.local.helpText.contains("same Wi-Fi"))
XCTAssertTrue(GatewayConnectionKind.local.helpText.contains("LAN address"))
XCTAssertTrue(GatewayConnectionKind.cloudflare.helpText.contains("Cloudflare Tunnel"))
}

func testGatewayRootURLRequiresHTTPOrHTTPSWithHost() throws {
let url = try gatewayRootURL(from: " https://codepilot.example.com ")

XCTAssertEqual(url.absoluteString, "https://codepilot.example.com")
assertInvalidGatewayRootURL("codepilot.example.com")
assertInvalidGatewayRootURL("file:///tmp/codepilot")
assertInvalidGatewayRootURL("http:///missing-host")
}

func testMacLocalWebURLDetectionOnlyAcceptsLoopbackHTTPURLs() throws {
Expand Down Expand Up @@ -451,4 +461,17 @@ final class RemoteDesktopTests: XCTestCase {
decoder.dataDecodingStrategy = .base64
return decoder
}

private func assertInvalidGatewayRootURL(
_ value: String,
file: StaticString = #filePath,
line: UInt = #line
) {
XCTAssertThrowsError(try gatewayRootURL(from: value), file: file, line: line) { error in
guard case GatewayError.invalidURL = error else {
XCTFail("Expected GatewayError.invalidURL, got \(error)", file: file, line: line)
return
}
}
}
}
5 changes: 5 additions & 0 deletions ops/agents/escalations/setup-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Setup Audit Escalation

Date: 2026-07-01

Remote Desktop pairing needs human review before public beta. The iOS pairing screen shows a pairing code field, but the current flow signs the challenge and the Mac approves the device immediately after `pairing.complete`; the Mac-side Approve/Reject UI is not part of the happy path. Because Remote Desktop controls the Mac, decide whether bearer-token possession is enough for pairing or whether pairing must require explicit Mac-side approval and clearer user copy.
33 changes: 33 additions & 0 deletions scripts/codepilot-agent-escalate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env zsh
set -euo pipefail

ROOT="${CODEPILOT_REPO_ROOT:-/Users/homeserver/Developer/CodexAccountSwitcher}"
STATE_DIR="${CODEPILOT_AGENT_STATE_DIR:-$HOME/.codex-account-switcher/agents}"
THREAD_ID_FILE="$STATE_DIR/thread-id"
CODEX_BIN="${CODEPILOT_CODEX_BIN:-codex}"
JOB="${1:-manual}"
MESSAGE="${2:-}"

mkdir -p "$STATE_DIR"

THREAD_ID="${CODEPILOT_AGENT_THREAD_ID:-}"
if [[ -z "$THREAD_ID" && -f "$THREAD_ID_FILE" ]]; then
THREAD_ID="$(tr -d '[:space:]' < "$THREAD_ID_FILE")"
fi

if [[ -z "$THREAD_ID" ]]; then
echo "No CodePilot agent thread id configured." >&2
exit 66
fi

if [[ -z "$MESSAGE" ]]; then
MESSAGE="$(cat)"
fi

{
echo "A CodePilot continuous agent needs intervention."
echo
echo "Agent: $JOB"
echo
echo "$MESSAGE"
} | "$CODEX_BIN" exec resume "$THREAD_ID" -
Loading