Skip to content

feat(runtime): restore mobile control page#1968

Merged
Hmbown merged 9 commits into
Hmbown:mainfrom
axobase001:feat/mobile-remote-control
May 31, 2026
Merged

feat(runtime): restore mobile control page#1968
Hmbown merged 9 commits into
Hmbown:mainfrom
axobase001:feat/mobile-remote-control

Conversation

@axobase001

@axobase001 axobase001 commented May 24, 2026

Copy link
Copy Markdown
Contributor

Rebased replacement for #852 after the v0.8.41 CodeWhale rebrand.

What changed:

  • adds codewhale serve --mobile as a thin mobile/LAN entry point over the existing runtime HTTP/SSE API
  • serves a built-in /mobile control page from a separate runtime_mobile.html file via include_str!
  • uses the current /v1/approvals/{approval_id} bridge instead of restoring the old per-turn approval routes
  • keeps the already-merged runtime token guard intact rather than reintroducing the old feat(runtime): add mobile remote control #852 auth implementation
  • updates runtime API docs plus README command listings

Validation:

  • cargo fmt --all
  • git diff --check
  • cargo check -p codewhale-tui --locked
  • cargo test -p codewhale-tui --bin codewhale-tui --locked runtime_api::tests::
  • local smoke: codewhale-tui serve --mobile --port 7879 --insecure, GET /mobile returned HTTP 200 with title CodeWhale Mobile

Note: cargo test -p codewhale-tui --locked runtime_api::tests:: also passed the runtime API module tests, but the full cargo invocation then tried to launch the filtered skill_install integration binary on Windows and hit OS error 740 (requires elevation). The bin-scoped command above avoids that unrelated Windows integration-test launcher issue.

Greptile Summary

This PR adds codewhale serve --mobile as a thin LAN entry point: it starts the existing HTTP/SSE runtime API and serves a compiled-in mobile control page at /mobile. The approach reuses the current /v1/approvals/{id} bridge and the existing token-guard middleware rather than restoring any old auth code.

  • New --mobile flag (mutually exclusive with --http) defaults the bind host to 0.0.0.0 with an explicit CLI warning, and passes mobile: bool through to RuntimeApiOptions/RuntimeApiState so the /mobile route returns 404 when the flag is absent.
  • runtime_mobile.html (embedded via include_str!) is a single-file mobile page with thread listing, SSE live-stream, prompt/steer/interrupt controls, and token-gated approval buttons; all server-supplied text is HTML-escaped before insertion.
  • Token URL round-trip is now percent-decoded server-side via a new percent_decode_query_component helper so tokens with special characters survive the ?token= query parameter path.

Confidence Score: 5/5

Safe to merge. The mobile feature is well-contained, reuses the existing auth middleware correctly, and the new token percent-decode path is covered by tests.

The auth logic for /mobile (inline check using the same request_has_runtime_token helper as the middleware) is consistent and correct. XSS is addressed throughout the HTML page. The percent-decode/encode round-trip for special-character tokens is tested end-to-end. The only issues found are display-only: the LAN IP detection probe address and a JSON-vs-HTML auth error for direct browser navigation.

No files require special attention. The two suggestions are in runtime_api.rs but neither affects correctness or security.

Important Files Changed

Filename Overview
crates/tui/src/runtime_api.rs Adds mobile_enabled flag to state, /mobile route with inline auth check (consistent with /health pattern), percent-decoding for ?token= query params, detect_lan_ip() helper, and two new SSE compat events (approval.decided/timeout). Logic is sound; new helpers are unit-tested.
crates/tui/src/runtime_mobile.html New single-file mobile control page embedded at compile time. All server-sourced strings pass through escapeHtml(); approval decision uses result?.decision ?? decision which correctly handles 204 null; token is read from input value without trim(). No XSS vectors identified.
crates/tui/src/main.rs Adds --mobile flag, changes --host to Option, extracts validate_serve_mode_selection (with --http/--mobile mutual exclusion) and resolve_serve_bind_host into testable functions, prints 0.0.0.0 rebind warning. All paths covered by new unit tests.
docs/RUNTIME_API.md Documents --mobile flag, /mobile endpoint auth behaviour, approval bridge, new SSE event names, and updated security-boundary section. Accurate reflection of the implementation.
README.md Adds codewhale serve --mobile entry to command listing and updates RUNTIME_API.md table caption. Minor doc change, no issues.
README.zh-CN.md Chinese README mirrors the same one-line addition and table caption update as README.md.

Sequence Diagram

sequenceDiagram
    participant User as Mobile Browser
    participant Server as codewhale serve --mobile

    Note over Server: binds 0.0.0.0 (default)<br/>prints Local + LAN URLs with ?token=

    User->>Server: "GET /mobile?token=XYZ"
    alt "mobile_enabled && token valid"
        Server-->>User: 200 HTML (MOBILE_HTML)
    else token missing/invalid
        Server-->>User: 401 JSON error
    end

    Note over User: JS stores token to localStorage<br/>removes from URL (history.replaceState)

    User->>Server: GET /v1/threads/summary (Bearer token)
    Server-->>User: JSON thread list

    User->>Server: POST /v1/threads (Bearer token)
    Server-->>User: 201 thread object

    User->>Server: "GET /v1/threads/{id}/events?since_seq=0&token=XYZ (EventSource)"
    loop SSE stream
        Server-->>User: approval.required event
        User->>Server: "POST /v1/approvals/{id} {decision, remember}"
        Server-->>User: "200 {decision}"
        Server-->>User: approval.decided event
    end

    User->>Server: "POST /v1/threads/{id}/turns/{tid}/interrupt"
    Server-->>User: 200
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (4): Last reviewed commit: "fix(runtime): restore mobile retry guard..." | Re-trigger Greptile

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a built-in mobile control page for the codewhale serve command, accessible via a new --mobile flag. This feature enables thread management and tool approval resolution from mobile devices on the same LAN. Key changes include a new single-page HTML/JS interface, CLI updates to handle network binding, and expanded SSE event support. Reviewers recommended URL-encoding the runtime token in the displayed URLs and refining the LAN IP detection logic to avoid dependencies on hardcoded external DNS servers like 8.8.8.8.

token
.filter(|token| !token.trim().is_empty())
.map(|token| format!("?token={token}"))
.unwrap_or_default()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The token is appended to the URL without percent-encoding. If a manually configured token contains characters like &, #, or +, it will corrupt the query string or lead to authentication failures, as the server-side token_from_query (and the mobile page's own JS) expects correctly formatted parameters. Consider URL-encoding the token here for robustness.

Comment thread crates/tui/src/runtime_api.rs Outdated

fn detect_lan_ip() -> Option<String> {
let socket = UdpSocket::bind("0.0.0.0:0").ok()?;
socket.connect("8.8.8.8:80").ok()?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Hardcoding 8.8.8.8 for LAN IP detection might not work for users in regions where Google services are blocked (e.g., China), which is a key demographic for this project. Consider using a more neutral target or a non-routable IP like 10.255.255.255:1 to determine the default interface's local address without sending actual packets.

@Hmbown

Hmbown commented May 27, 2026

Copy link
Copy Markdown
Owner

Independent review:

Security model — main concern

  • --mobile silently rebinds 127.0.0.1 to 0.0.0.0 (main.rs:850) without an extra opt-in. A user passing --mobile to a previously loopback-only serve config gets LAN exposure. Recommend logging the rebind loudly and/or requiring explicit --host 0.0.0.0.
  • /mobile is mounted OUTSIDE require_runtime_token (runtime_api.rs:545). The static HTML carries no secrets so this is defensible, but document it — anyone on the LAN can fingerprint codewhale-serve via GET /mobile.
  • Token is correctly percent-encoded (dcc28df), URL-scrubbed into localStorage, sent via Bearer for fetch and ?token= for SSE (EventSource limitation). Reasonable.

Code quality

  • detect_lan_ip (f079759) using 10.255.255.255:1 UDP-connect is the right call after dropping 8.8.8.8.
  • XSS surface: all event payload fields routed through escapeHtml before innerHTML. Clean.
  • Test coverage: one new test (mobile_page_is_available_only_when_enabled) plus token-encoding unit test. No coverage for the 0.0.0.0 auto-rebind or auth-disabled --mobile --insecure path.

v0.8.48 conflict: only README.md / README.zh-CN.md conflict against feature/v0.8.48-crate-consolidation; runtime_api.rs and main.rs auto-merge cleanly. Low risk.

Suggest: warn-log the host rebind, add a test for --mobile --insecure, and decide whether /mobile should 401 (vs 404) when token is set, to avoid encouraging unauth probes.

@axobase001 axobase001 force-pushed the feat/mobile-remote-control branch from dcc28df to c8c5e52 Compare May 27, 2026 22:42
Comment thread crates/tui/src/main.rs Outdated
Comment thread crates/tui/src/runtime_mobile.html Outdated
Comment thread crates/tui/src/runtime_mobile.html
@axobase001

Copy link
Copy Markdown
Contributor Author

Addressed the review in c8c5e521:

  • Rebased onto current main; README / README.zh-CN conflicts are resolved.
  • --mobile now warns when the omitted host defaults to 0.0.0.0, and explicit --host 127.0.0.1 keeps the mobile server loopback-only.
  • /mobile is now token-gated when runtime auth is enabled: disabled mobile still returns 404, enabled mobile without a valid token returns 401.
  • Added coverage for mobile host resolution, token-gated /mobile, percent-decoded query tokens, and the --mobile --insecure no-token path.
  • Updated README and runtime API docs to describe the LAN binding and /mobile auth boundary.

Local validation:

  • cargo fmt --all
  • cargo test -p codewhale-tui --bin codewhale-tui --locked serve_bind_host_tests
  • cargo test -p codewhale-tui --bin codewhale-tui --locked runtime_api::tests::mobile
  • cargo test -p codewhale-tui --bin codewhale-tui --locked runtime_api::tests::token
  • cargo test -p codewhale-tui --bin codewhale-tui --locked runtime_api::tests::url_query_component_percent_encodes_token
  • cargo test -p codewhale-tui --bin codewhale-tui --locked runtime_api::tests::runtime_token_guard_protects_v1_routes
  • cargo check -p codewhale-tui --locked

GitHub checks are green on the updated PR head.

@axobase001

Copy link
Copy Markdown
Contributor Author

Handled the Greptile P2 follow-ups in 39adb536:

  • --http --mobile now fails early as mutually exclusive, with a focused unit test.
  • The mobile approval UI now handles 204/null approval responses by falling back to the submitted decision.
  • The mobile token field no longer trims stored/query tokens before sending them.

Validation:

  • cargo fmt --all
  • cargo test -p codewhale-tui --bin codewhale-tui --locked serve_bind_host_tests
  • cargo check -p codewhale-tui --locked

GitHub checks are green again on the latest head.

Hmbown commented May 28, 2026

Copy link
Copy Markdown
Owner

wow! this is amazing! thank you for building this. will work on getting this in asap

@Hmbown

Hmbown commented May 31, 2026

Copy link
Copy Markdown
Owner

This mobile-control-page work is still interesting, thank you for putting the branch together. I am leaving it open because it is currently conflicting with main, and the runtime/LAN/approval surface needs fresh CI and smoke coverage before we expose it again.

Please rebase and rerun the full matrix, then smoke /mobile, auth, approval retry, --mobile --insecure, and LAN binding behavior. Once the branch is current, this can get a much more decisive review.

@Hmbown

Hmbown commented May 31, 2026

Copy link
Copy Markdown
Owner

I rebased this onto current main and resolved the one conflict in crates/tui/src/main.rs: kept main's block_in_place MCP server path and the PR's shared --http/--mobile HTTP runtime path.\n\nVerified locally on 332e2d64:\n- cargo fmt --all -- --check\n- git diff --check\n- python3 scripts/check-provider-registry.py\n- cargo test -p codewhale-tui runtime_api::tests::mobile -- --nocapture\n- cargo test -p codewhale-tui serve_bind_host_tests -- --nocapture\n- cargo check -p codewhale-tui --all-features --locked\n\nThanks @axobase001. This is a nice restoration of the mobile control page without reopening the old auth surface from #852.

@Hmbown

Hmbown commented May 31, 2026

Copy link
Copy Markdown
Owner

Thank you again @axobase001. I refreshed this onto current main after the skills API and Baidu search merges moved nearby runtime API imports. The conflict stayed tiny and the mobile page behavior is still intact.

Maintainer verification on the updated head (e8bcf9f):

  • cargo fmt --all -- --check
  • git diff --check HEAD~1..HEAD
  • python3 scripts/check-provider-registry.py
  • cargo test -p codewhale-tui runtime_api::tests::mobile -- --nocapture
  • cargo check -p codewhale-tui --all-features --locked

CI is running again now; if the matrix stays green I think this one is ready to land.

@Hmbown Hmbown merged commit 9716619 into Hmbown:main May 31, 2026
15 of 16 checks passed
@Hmbown

Hmbown commented May 31, 2026

Copy link
Copy Markdown
Owner

Merged at 9716619. Thank you @axobase001 — this was a substantial runtime/UI contribution, and the extra retries/null-response hardening made it much safer to land. The final rerun passed Windows too, so the mobile control page is now in main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants