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
Bashcommands 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:
- Add the LINE OA — scan the QR code at
thclaws.ai/line(or search for@thClawsin LINE). - In thClaws, open Settings → LINE → Pair phone. The
modal shows a 6-character code (e.g.
KJ4-9P2). - Send that code to the LINE OA. It replies “Paired ✓ as
”. - 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,/unpairand/statusare 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
/chatin 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— overrideslinegated; mutating tools then run without prompting anywhere. Persists tosettings.jsonand survives LINE disconnect / reconnect.- Disconnect LINE from Settings → LINE Connect — restores your
pre-LINE mode (typically
autoorask) 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 workspacedev-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
/pairand 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.mdand 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’sprovider-thclaws-gateway.mdfor the wire shape.