Skip to content

Add terminal + retro file explorer to webview (localhost + Android) #83

Description

@mega123-art

Goal

Add two workspace tools to the AgentNet UI: a terminal and a retro-style file explorer. Both must work on every surface that renders the webview (localhost browser + Android), with vscode/cli out of scope for v1.

Why this is cheaper than it looks

The hard parts are already solved by the existing architecture:

  • Shared UI. surfaces/webview is the single React UI rendered everywhere. Build a screen once → it appears on desktop and Android.
  • Shared wire. One transport (POST /rpc for commands, GET /events SSE for streams), defined in webview/src/transport/protocol.ts and served by surfaces/localhost/src/index.ts.
  • Real runtime already on device. Android runs the same localhost node server inside a proot Ubuntu guest (surfaces/android ships proot-arm64; ServerService.kt keeps "the node server" alive). So a real shell + a real glibc filesystem already exist on the phone. We are not porting a shell — it is already there.

Net: implement RPC handlers in localhost/index.ts + screens in webviewAndroid inherits both for free (it reuses the localhost surface verbatim). vscode would need its own handlers later (or just reuse its native terminal/explorer).

UI placement (decided)

The bottom nav is a fixed 4-domain pager — Chat · Skills · Agent · Market — with hard-coded tab-width math (--an-tab-w) and LAST = 3 in App.tsx. Adding terminal/files as 5th/6th tabs breaks that math and crowds the mobile bar, and they are not top-level domains.

Terminal and Files are tools of the active session's workspace (the cwd the agent is operating in). They belong attached to Chat, which already owns activeSessionId.

Placement: two icon buttons in the Chat header (chat/ChatScreen.tsx, the right-side <div className="flex items-center gap-1 shrink-0"> group, before StatusBadge). Tapping opens a full-screen overlay scoped to the active session's cwd — back-swipe / X to close. Same overlay reads as a side panel on a wide desktop browser. No change to the pager or tab bar.

Chat header:  [☰ chats]   Title / addr   …   [▮ term] [▤ files] [status]
                                                  └ open full-screen overlay (active session cwd)

Phase 1 — Retro file explorer (EASY, ~1 day, low risk)

  1. protocol.ts — new messages:
    • client: { type: "fs.list"; path: string }, { type: "fs.read"; path: string }
    • server: { type: "fs.listed"; path; entries: { name; kind: "dir"|"file"; size }[] }, { type: "fs.file"; path; content; truncated }
  2. localhost/index.ts — handler: fs.readdir(path, { withFileTypes }) + stat; readFile capped (~256 KB). Normalize + scope every path to the runtime workspace root; reject .. escapes.
  3. webview/src/files/FilesOverlay.tsx — lazy tree (load a dir's children only on expand — proot fs is slow, no recursive walk). Retro look = pure CSS (monospace, box-drawing chars, green-on-black). Read-only in v1.
  4. ChatScreen.tsx — Files icon button → open overlay.

Phase 2 — Terminal (MEDIUM, ~2–3 days)

  1. protocol.ts — new messages:
    • client: {type:"term.open"; cols; rows}, {type:"term.input"; id; data}, {type:"term.resize"; id; cols; rows}, {type:"term.close"; id}
    • server: {type:"term.data"; id; data}, {type:"term.exit"; id; code} (data streams over the existing SSE channel; input arrives via POST)
  2. localhost/index.ts — spawn a shell:
    • v1 (lazy): child_process.spawn(shell, { stdio: pipe }) — pipe stdin/stdout. Works for line commands; no full-screen TUIs (vim/htop). // ponytail: pipe shell, swap to node-pty if a real PTY is needed.
    • v2: node-pty for a true PTY. Risk: native arm64 build inside proot (the "mass file churn" tax noted in guest/AGENTS.md). Ship v1 first.
  3. webview/src/term/TermOverlay.tsx@xterm/xterm + fit addon (one new dep). Wire to term.* messages.
  4. ChatScreen.tsx — Terminal icon button → open overlay.

Risks / decisions

  • node-pty on arm64/proot → ship the pipe-shell terminal first; add real PTY only if TUIs are needed.
  • proot fs is slow → explorer lazy-loads per directory, never recurses the whole tree.
  • Security → both handlers run inside the existing loopback-only server ("nothing leaves the device"). File explorer path-scopes to the workspace root. Terminal = full shell, but that is the same trust boundary the agent already operates under in proot; keep it loopback-only and behind the existing wallet-gated session.
  • Scope → localhost + Android only for v1. vscode/cli later (likely reuse native terminal/explorer instead of these overlays).

Acceptance

  • Files button in Chat header opens a retro tree of the active session's cwd; expanding a dir lazy-loads; clicking a file shows its contents (read-only); paths cannot escape the workspace root.
  • Terminal button opens an xterm overlay running a real shell in the session's cwd; typing a command returns output; closing the overlay kills the process.
  • Both verified on localhost browser and Android (same build, no surface-specific UI code).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions