---
name: chat-web
description: Reply to the user when they message you from the JHT web chat. The user reaches you with the prefix `[@utente -> @capitano] [CHAT] <body>`; reply ONLY with `jht-send` — never write to `chat.jsonl` by hand (shell quoting breaks the JSON line and the frontend silently drops the message, the user sees nothing while you think you have answered). Use this skill on every `[CHAT]` message; do NOT use it for inter-agent traffic (that's `tmux-send`).
allowed-tools: Bash(jht-send *)
---

# chat-web — user ↔ Captain protocol

The user does **not** sit in a tmux session. They write from the web UI. The frontend tags the message and drops it into your tmux pane. To reply, you write a single JSON line into `$JHT_AGENT_DIR/chat.jsonl`; the frontend tails that file and renders bubbles in the chat panel.

You don't write the JSON. The wrapper `jht-send` does it for you, with timestamp + `done` flag + post-write validation. Use it. Always.

## How to recognize an incoming `[CHAT]`

```
[@utente -> @capitano] [CHAT] <whatever the user typed>
```

- The envelope is identical to inter-agent messages (same `[@from -> @to]` shape) but the `[CHAT]` type and the `@utente` author make it unambiguous.
- The user is **a human, the profile owner** — not an agent. There is no `tmux send-keys` you can use to reply: their session does not exist.
- Reply to **the body**, not to the envelope. The user did not type the prefix; the frontend added it.

> ⚠️ Common failure mode the first time you see this: you read the prefix and think "let me reply via `jht-tmux-send` to the user". `jht-tmux-send UTENTE ...` returns `exit 2` (no such session). Do not start debugging — just remember that `[CHAT]` ⇒ `jht-send`. Always.

## ⚠️ The user is NON-TECHNICAL — no terminal, no CLI, no slash commands

A `[CHAT]` message comes from the **desktop app** (or Telegram). The user does
**not** have a terminal, the Claude Code CLI, `/mcp`, slash commands, or file
access. They are not a developer. **NEVER** tell them to:

- run `/mcp`, `/config`, or any slash command,
- open a terminal / run a CLI command / edit a file / "authorize a tool",
- do anything you could do yourself.

Telling a desktop user "type `/mcp` in the terminal" is a dead end — they can't.
If a task needs a capability you don't have wired up, **solve it yourself with
code** instead of bouncing the work back to the user. You are the team; the user
is the client. Figure it out.

### Example: sending an email — you do NOT need an MCP/Gmail tool
The team already has email credentials at
`$JHT_HOME/credentials/email_monitor.json` (a Gmail app-password — valid for SMTP
too). Write a tiny Python script and send it:

```python
import os, json, smtplib, ssl
from email.message import EmailMessage
c = json.load(open(os.path.join(os.environ["JHT_HOME"], "credentials", "email_monitor.json")))
m = EmailMessage()
m["From"], m["To"], m["Subject"] = c["user"], "<recipient>", "<subject>"
m.set_content("<body>")
# Gmail: smtp.gmail.com:465. For another provider derive smtp.<domain> or map it.
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=ssl.create_default_context()) as s:
    s.login(c["user"], c["password"]); s.send_message(m)
```

Then reply "Done, sent ✅" — never "go authorize Gmail".

### If you genuinely need a slash command / tool in YOUR session
You can drive your own session — don't ask the user:

- **Self-inject** it into your own tmux session:
  `jht-tmux-send <YOUR_SESSION> '/mcp'` (then a separate Enter), or
- **ask another agent** to inject it for you: e.g. the Assistant asks the Captain
  (`jht-tmux-send CAPITANO '...inject /mcp into ASSISTENTE...'`) and the Captain
  runs `jht-tmux-send ASSISTENTE '/mcp'`. The user is never involved.

## Reply commands

```bash
jht-send 'Final reply that closes the turn.'
jht-send --partial 'Working on it…'   # mid-turn checkpoint, keeps the turn open
```

Rules:
- **One `[CHAT]` ⇒ at least one `jht-send`. No exceptions.** Writing nothing leaves the user staring at a frozen-looking chat.
- **The closing message of the turn has NO `--partial`.** If you forget, the frontend keeps the typing dots on forever (until a fallback timeout ~10 min later).
- **Quotes**: pass the body as a single positional arg. Single quotes preserve `$`, `"`, emoji, accents verbatim. For a body containing a literal `'`, use double quotes (`jht-send "non c'è problema"`) — but inside `"..."` shell will expand `$var`, so be careful.
- **Multi-line**: bash `$'riga1\nriga2'`, or use `\n` inside the string and let Python preserve it.

## When to use `--partial`

Use it whenever a user-facing operation will take more than ~3 seconds and you do not have the answer yet. Without `--partial` between user message and final reply, the frontend hides the typing dots and the chat looks dead.

Pattern:
```
[CHAT] arrives
   ↓
jht-send --partial 'Looking into it — give me a moment…'
   ↓
(do the work: db_query, capture-pane, analysis, …)
   ↓
jht-send 'Here is what I found: …'   ← no --partial = closes the turn
```

If a single operation goes past ~30-45s without a signal, send another `--partial` checkpoint. The user must never sit silent for longer than that.

## Examples (Captain ↔ user)

```bash
# Answer a question about pipeline state — fast, single shot
jht-send 'Pipeline at 132 positions: 18 new, 47 checked, 31 scored, 28 ready. Two writers active.'

# Long-running analysis — checkpoint, then close
jht-send --partial 'Pulling stats and the last 50 reviews — one moment…'
# (run db_query.py stats, db_query.py applications --critic-score-max 5)
jht-send $'Here is the picture:\n\n• Pipeline healthy on the discovery side.\n• Writers stuck on 4 positions averaging score 3.2 → I am pausing them and reopening triage.'

# Closing the turn after applying a user request
jht-send 'Done. Spawned an extra Analyst, throttle config dumped to the log.'
```

## Anti-patterns (what NOT to do)

- ❌ `echo '{"text":"...","ts":'$(date +%s.%N)'}' >> $JHT_AGENT_DIR/chat.jsonl` — explodes on quotes/`$`/emoji, produces invalid JSON, frontend silently drops the line.
- ❌ `cat << 'EOF' >> chat.jsonl ... EOF` — disables `$` interpolation, timestamp ends up as a literal string.
- ❌ `python3 -c "import json; ..."` ad-hoc — same fragility as the shell heredoc.
- ❌ Replying via `jht-tmux-send UTENTE ...` — there is no `UTENTE` session. The user lives in the web frontend.
- ❌ Answering the `[CHAT]` with `jht-send` **and** re-sending the same content with `jht-notify-user`. Since the chat lane was unified they write into the SAME conversation: the user reads your reply twice, and nothing downstream removes the second one — the lane cannot tell a duplicate from two turns that happen to match. One message, one tool.
- ❌ Sending a final reply with `--partial` — typing dots stuck on the user's screen.
- ❌ Multiple `jht-send` calls (without `--partial`) for what should be one message — each non-partial call appears as a separate bubble.

## Sending to a non-default channel (rare)

```bash
jht-send --agent capitano 'system-level note routed via my channel'
```

Useful when you want to log a system message into your own chat channel (e.g. an automation noting it has acted on the user's behalf). For day-to-day replies you never need this flag.

## Why `jht-send` and not raw shell

History (do not repeat): agents tried `echo`-into-jsonl and `cat <<EOF` heredocs. Both finished in fragile modes — the first explodes on quotes/`$`, the second freezes the timestamp as a literal string. Result: invalid JSON the frontend skips. The user sees nothing; you think you have replied. `jht-send` removes the failure mode entirely — the body never re-enters a shell parser after the first level of quoting.

## See also

- `game-reply-options` — optional, context-generated clickable answers for a
  small bounded decision in the game; never a fixed onboarding script.
- `tmux-send` — for messages to **other agents** (different protocol, different channel).
- `agents/assistente/assistente.md` — the Assistant has the deepest version of this protocol (multi-step onboarding flow with mandatory checkpoints); read only if you ever inherit Assistant duties.
