Chapter 21

Chapter 21 — LINE chat & web browser bridge

Drive thClaws from your phone. The @thClaws LINE OA identifies you and hands you a link; the chat itself happens in any web browser, driving the same Rust agent loop on your desktop. Only the surface changes. Added in v0.9.0+ across the plan-07 / plan-08 / plan-10 series.

Why bother

  • Approve Bash commands from your phone while the desktop runs unattended.
  • Continue a chat away from your laptop — type in your phone’s browser, and the desktop’s full tool registry (Bash, Edit, KMS, MCP, skills) executes locally.
  • Drive long-running tasks without leaving your machine docked at the desk.

The desktop never goes away — your code, secrets, and tools stay local. The phone / browser surfaces are read+input bridges only.

How it works (one paragraph)

A small Axum service at line.thclaws.ai (and chat.thclaws.ai for the browser variant) holds a WebSocket connection from your desktop and routes browser keystrokes to it. The desktop runs the agent unchanged and fans every assistant delta, tool call, and approval prompt back through the same WS so the browser sees the conversation as it streams.

The LINE OA is a launcher, not a chat surface. That is the part people get wrong. Talking to @thClaws does not talk to your agent: the OA identifies you, hands you a pairing code, tells you whether your desktop is up, and mints the one-time link that opens the real chat at chat.thclaws.ai. The conversation itself lives in the browser. LINE keeps one job in the conversation loop — delivering approval prompts when no browser session is open.

Pairing your phone (LINE)

One-time setup:

  1. Add the LINE OA — scan the QR code at thclaws.ai/line (or search for @thClaws in LINE).
  2. In thClaws, open Settings → LINE → Pair phone. The modal shows a 6-character code (e.g. KJ4-9P2).
  3. Send that code to the LINE OA. It replies “Paired ✓ as ”.
  4. The sidebar’s LINE chip lights up green. You’re connected.

After pairing, send the OA anything at all and it replies with a fresh link into the browser chat — that is where you actually talk to the agent. Tool calls that need approval (Bash, Edit, Write) come back to LINE as Quick Reply chips whenever no browser session is open — tap [Approve] or [Deny] from the phone.

What the OA replies with

The OA is not a command console. Whatever you type, it looks at two facts — are you paired, and is your desktop online right now — and answers with one of three things:

Your state The reply
Not paired yet A welcome message and a fresh pairing code
Paired, desktop offline “thClaws not active”, with a pointer to /pair if the machine is gone for good
Paired, desktop online A single-use magic link into the browser chat

That third row is the important one: when you’re paired and online, every message gets a link back — hello, /chat, a sticker, or a paragraph you meant for the agent. Nothing you type in LINE reaches your agent, and nothing is queued for it. If you typed a prompt into LINE by mistake, open the link and retype it in the browser.

The one real command is /pair, which forces a fresh pairing code no matter what state you’re in — the escape hatch when the thClaws install a code was bound to is gone and you need to register a new machine.

/chat, /unpair and /status are not things you type at the OA. They are HTTP routes on the relay that the desktop and the browser call; typing them into LINE just gets you the ordinary link reply. Earlier releases did treat LINE as a chat surface, and older notes still describe it that way.

The rich menu’s Chat button works by sending /chat as a message on your behalf — which lands in that same “paired and online → link” branch. It is a shortcut for one tap, not a distinct command.

Non-text messages (stickers, photos, files) are not understood: they arrive as the literal text (non-text message) and get the same reply as anything else.

Browser chat (the /chat path)

LINE bubbles are great for short approvals and quick prompts but get awkward for code blocks, long responses, and markdown rendering. Send /chat to the OA and you get back a magic link:

https://chat.thclaws.ai/launch?token=...

The link is single-use with a 5-minute TTL — mint it when you are ready to open it, not in advance. (The 10-minute figure you may have seen is the browser session’s idle timeout, a different clock that starts once you are in.)

Open it in any browser — the link auto-redirects through a splash page (which exists to dodge LINE’s URL-preview crawler that would otherwise burn the token before you tapped). After the redirect you land on a full-fidelity chat surface:

  • Sidebar shows your session id, sign-out button, and a live “browser connected” indicator on the desktop side.
  • Assistant responses render as markdown with syntax-highlighted code blocks (via vendored marked.js + DOMPurify — all rendering stays in the browser; no remote loaders, no eval).
  • History replays automatically on connect — even mid-session reconnects pick up where you left off (last ~50 messages, served from a Redis stream on the relay).
  • Tool approvals open an inline modal with [Approve] [Deny] buttons instead of routing to LINE Quick Replies.
  • Sessions expire after 10 minutes of idle — three reconnect failures in a row trigger a “session expired” splash that points you back to /chat in LINE for a fresh link.

The browser link is per-session, single-use, HTTPS only, HttpOnly cookie. Sharing it is identical to handing someone your desktop session — don’t.

Rich-menu shortcut (v0.9.3+)

If your phone shows the LINE OA’s rich menu (the bottom toolbar with custom buttons), it has two pinned buttons:

  • Chat — equivalent to typing /chat. One tap to get a magic link to the browser chat.
  • Pair — equivalent to typing /pair. Quick re-issue of a pairing code if you disconnected.

Operators who deploy their own LINE OA can install the rich menu with the dev-plan/08-line-server-k3s/rich-menu-setup.sh script — see docs/line-rich-menu-setup.md for the full setup walk-through.

Approvals from the phone or browser

When LINE is bridged, the runtime permission mode is linegated (see Chapter 5) and every approval request routes through LINE regardless of which surface you typed the original request on — Terminal tab, Chat tab, REPL, or LINE itself. The approver is a process-wide singleton with no awareness of the originating surface; while paired, your phone is the single approval inbox.

  • Browser chat (/chat) open: the approval modal pops up in the browser with the tool name, a full argument preview, and [Approve] [Deny] buttons — better UX than LINE Quick Reply chips for long arg previews. Approving in either surface dismisses both.
  • Browser chat closed (or never minted): falls back to LINE OA Quick Reply. The bot pushes a bubble like:

``` thClaws wants to run: bash -c “ls -la ~/Downloads”

[Approve] [Deny] ```

Tap a chip; the answer flows back to the desktop within ~1 s.

Bypass while paired, if you don’t want approvals routing to your phone:

  • /permissions auto — overrides linegated; mutating tools then run without prompting anywhere. Persists to settings.json and survives LINE disconnect / reconnect.
  • Disconnect LINE from Settings → LINE Connect — restores your pre-LINE mode (typically auto or ask) immediately.

When LINE is not paired, the desktop’s own approval modal pops up as usual — phone/browser routing is only active while the bridge is connected.

Uploading files from the phone or browser

You can attach files from either surface — the desktop saves them into <workspace>/_uploads/ and an AGENT.md in that directory tells the agent what to do with the file. Added in v0.9.6.

Caps: - 25 MB per file (UPLOAD_MAX_BYTES). - 5 files per message (UPLOAD_MAX_FILES). - Any MIME type — text, image, PDF, archive. The desktop doesn’t unpack or transform; it just lands the bytes in the workspace.

Filename collisions are resolved by appending _n before the extension. If you upload notes.md and notes.md already exists, the second lands as notes_1.md; a third as notes_2.md. Original filenames are preserved (modulo path-traversal sanitisation — ../../etc/passwd lands as passwd).

From the browser chat (chat.thclaws.ai): drag-and-drop files onto the chat surface, or click the paperclip icon next to the composer. The desktop sends a synthetic chat message describing the upload (filename + size) followed by a Read the file and respond. directive line, so the agent treats the drop as a request to act on the contents — not just an FYI. (Pre-v0.9.7, the synth was purely informational and some models would reply “what would you like me to do with this?”. Project-level AGENT.md / CLAUDE.md can override the directive if that behavior was actually what you wanted.)

Not from LINE. Attaching a photo or a file in the LINE chat does nothing useful: the relay collapses every non-text message to the literal string (non-text message), so you get the ordinary launcher reply and the file is never fetched. Open the browser chat and drop it there instead. (Image and voice support through LINE is planned, not shipped.)

Where to control behavior: drop an AGENT.md at <workspace>/_uploads/AGENT.md (or at the workspace root if you prefer one rule for everything). The agent reads it as part of the standard CLAUDE.md / AGENT.md cascade and applies whatever directives it contains: “OCR every uploaded PDF and stash the text under kms/sources/”, “auto-rename screenshots from Photo 2026-… to a slug”, etc. Without an AGENT.md, the file just sits there waiting for you to tell the agent what to do next.

Privacy and trust boundary

  • Desktop never proxies upstream LLM calls through the relay. Your prompts go from the desktop straight to Anthropic / OpenAI / etc. The relay only carries the user-facing messages between the surfaces and the desktop.
  • The relay can see message content in transit (it has to route it). Host it yourself if you don’t want a third party reading your prompts — the relay binary is crates/line-server/ in the workspace fork; the public OSS distribution doesn’t ship it. See plan-08 in the workspace dev-plan/ for the k3s deployment shape.
  • Tokens / API keys never leave the desktop. The relay holds one LINE channel secret (for signature verification) and a Postgres-stored user profile cache (name + LINE user id) per paired user — nothing more.
  • LINE pairing tokens are single-use, 10-min TTL, hashed server-side. A stolen pairing code is useless once the OA has emitted the “Paired ✓” reply.
  • A pairing lasts 30 days. The binding token expires on its own after that, so a phone you paired last month and haven’t used since falls back to the welcome-and-code reply. That is not a fault — send /pair and redeem a fresh code.

Troubleshooting

Symptom Likely cause Fix
/chat link shows “expired” on first tap LINE’s URL preview crawler consumed the token Open the link from the LINE chat directly, not by tapping a forwarded copy
LINE bot replies “thClaws not active” Desktop’s WS disconnected (sleep, network) Bring the desktop online; pairing persists
Typed a prompt into LINE and got a link back Expected — the OA is a launcher, not a chat surface Open the link and retype it in the browser
Browser chat freezes “Opening thClaws Chat…” Browser blocked the inline auto-submit script Confirm the CSP allows script-src 'self' 'unsafe-inline' on /launch
LINE Quick Reply buttons don’t appear on approval Browser chat is also open — approval went there instead Either approve in the browser or close the browser tab and the next approval falls back to LINE
Pairing code stays “(none)” after typing it Code expired or was already used Open the Pair modal again to mint a fresh code
/chat link expired before you opened it The magic link’s TTL is 5 minutes, not 10 Send anything to the OA again for a fresh one
“browser connected” pill doesn’t appear on the desktop Magic link token TTL elapsed before you opened it Send /chat again from LINE for a fresh link

Status command on the desktop

make line-status (from the workspace root) prints a per-user status table joining Postgres profiles with Redis presence flags — useful for operators running their own LINE relay:

$ make line-status
user_id            paired  present  browser  last_seen
U1a2b3...          ✓       ✓        -        2 min ago
U9z8y7...          ✓       -        -        3 days ago

paired = ever-paired, present = WS connected right now, browser = /chat browser session active, last_seen = most recent webhook activity.

What’s NOT in this chapter

  • Internal architecture (broker channel multiplex, WS protocol, Redis stream layout) — see the technical manual’s line-bridge.md.
  • LINE OA setup from scratch (channel secret, webhook URL, rich menu install) — operator-side work documented in docs/line-rich-menu-setup.md and the plan-08 workspace docs.
  • Cloud gateway (paid SaaS proxy) — shipped in v0.9.6 as gateway.thclaws.ai. See Chapter 6 for the user-facing sign-in + per-provider toggle, and the technical manual’s provider-thclaws-gateway.md for the wire shape.