This document describes a transport-agnostic mobile notification and steering layer for TermAl, replacing the earlier WhatsApp-only proposal.
Backlog source: docs/bugs.md
Related briefs:
telegram-ui-integration.md — Phase 1
Telegram relay specifics: UI configuration panel, in-process tokio
supervisor, code-based linking flow, REST API surface, build sequence,
open questions.Phase 1 (Telegram bot adapter) runs in-process inside the TermAl backend and is
configured from Settings -> Telegram. The multi-bot target model is tracked in
telegram-ui-integration.md. Phase 0 (PWA
push), Phase 2 (deterministic digest builder beyond the current hand-rolled
rules), and Phase 3 (WhatsApp) are not implemented.
TermAl’s current UI is rich, desktop-oriented, and optimized for supervising agent work with typed cards, split panes, diffs, files, and git state. A raw mirror of that UI into a mobile notification channel would be noisy and hard to act on.
We need a mobile surface that helps a user stay in the loop without forcing them back to the full workspace for every update.
Treat the mobile channel as a project status relay, not as a full TermAl client.
For each linked project, TermAl should send a short digest that answers three questions:
The digest should include a small set of proposed actions based on the current project state. Rich review still happens in TermAl itself.
The digest model and status synthesis rules are transport-agnostic. The transport layer is the delivery mechanism. Here is a comparison of options ordered by fit for TermAl’s single-user local-first Phase 1:
| Dimension | Assessment |
|---|---|
| Cost | Free — no external service |
| Setup | Add a service worker + VAPID keys to the existing TermAl web frontend |
| Approval | None — browser permission grant only |
| Actions | Notification action buttons (Approve / Open / Stop) |
| Bidirectional | No — one-way push only, tap opens TermAl |
| Privacy | No third-party involvement at all |
| Limitation | Requires the browser to be installed and permission granted; no conversation thread |
Since TermAl is already a web app, this is the lowest-friction option. The backend emits a Web Push message when project state changes meaningfully, and the browser shows a native notification with action buttons. iOS supports PWA push since 16.4.
| Dimension | Assessment |
|---|---|
| Cost | Free — no per-message fees, no BSP |
| Setup | Create bot via @BotFather, get token — done in 60 seconds |
| Approval | None — no template review, no business verification |
| API | Simple HTTP REST; polling or webhooks; inline keyboard buttons built-in |
| Actions | Inline keyboard buttons map directly to Approve / Reject / Continue |
| Bidirectional | Yes — full conversation thread with free-text and button replies |
| Deep links | t.me/yourbot?start=project-xyz works natively |
| Rate limits | 30 msgs/sec to different users — more than enough for a single developer |
| Gateway | Not needed — Telegram handles delivery; TermAl just POSTs to the Bot API |
The entire gateway described in the original WhatsApp doc can be replaced by a ~200-line adapter that polls the Telegram Bot API or listens for webhooks via an ngrok/cloudflare tunnel. No Meta business verification, no template approval, no per-message cost.
| Dimension | Assessment |
|---|---|
| Cost | Free — self-hosted or use ntfy.sh |
| Setup | One HTTP call: curl -d "message" ntfy.sh/your-topic |
| Actions | Supports action buttons in notifications (open URL, HTTP request) |
| Bidirectional | No — one-way push only |
| Privacy | Fully self-hosted option, no third-party accounts needed |
| Limitation | No conversation thread; better for “tap to open TermAl” than steering |
Good for users who want self-hosted notifications without any external accounts. Can coexist with Telegram or PWA push.
| Dimension | Assessment |
|---|---|
| Cost | $0.01–$0.13 per message depending on country + BSP markup |
| Setup | Meta Business verification (days/weeks), BSP account, phone number lockdown |
| Approval | Every outbound digest template must be pre-approved by Meta |
| 24-hour window | After 24h without user reply, only pre-approved templates can be sent |
| Gateway | Public webhook endpoint + signature validation + tunnel to local TermAl |
| Bidirectional | Yes — but constrained by templates and 24-hour windows |
| Advantage | Ubiquitous; natural fit for teams already using WhatsApp for coordination |
WhatsApp is the right choice when TermAl moves to multi-user or team scenarios where WhatsApp is the existing coordination channel. The overhead is not justified for single-user local-first use.
Each notification channel is linked to one TermAl project by default.
That channel becomes the user’s mobile control surface for the project, not for an arbitrary session. Internally, the relay can target the most relevant session in that project or create one when needed.
A digest is sent when one of these happens:
The default digest shape is:
Project: termal
Status: waiting on your decision
Done: fixed the queued prompt bug, updated tests, and left the repo clean
Next: review the diff, approve the pending command, or ask the agent to commit
This should be short enough to read in a notification preview.
Each digest includes up to three suggested actions.
Examples:
ContinueReview in TermAlApproveRejectStopAsk agent to commitKeep iteratingThe action list should be state-driven rather than fixed.
The relay should support:
status, stop, continue, and openFree-text replies should remain available, but the happy path should be action selection from the digest.
The relay should synthesize a project digest from the latest TermAl state using something like:
type ProjectDigest = {
projectId: string;
primarySessionId?: string | null;
headline: string;
doneSummary: string;
currentStatus: string;
proposedActions: ProposedAction[];
deepLink?: string | null;
sourceMessageIds: string[];
};
type ProposedAction = {
id: string;
label: string;
prompt?: string;
requiresConfirmation?: boolean;
};
The output should be concise and deterministic enough that the relay can send it automatically without another LLM step in the loop.
Action suggestions should be derived from project state in priority order.
If any session in the project is waiting for approval:
Approve, Reject, and Review in TermAlIf the most recent turn ended in error or tests failed:
Fix it, Open in TermAl, or PauseIf the project has fresh edits and no approval is pending:
Review in TermAl, Ask agent to commit, or
Keep iteratingIf an agent is still running:
Stop, Open in TermAl, or WaitIf the project is idle and unblocked:
Continue, Ask a question, or Open in TermAl┌──────────────┐ SSE ┌──────────────────┐ HTTP ┌─────────────┐
│ TermAl │ ──────────────>│ Transport │ ────────>│ Telegram │
│ Backend │<───── REST ────│ Adapter │<─────────│ Bot API │
│ :6543 │ │ (small process) │ │ (or PWA / │
└──────────────┘ └──────────────────┘ │ ntfy) │
│ └─────────────┘
│ │
│ push digests │
│ inline buttons │
└──────────────────────────┘
│
┌──────▼──────┐
│ Your Phone │
└─────────────┘
The adapter subscribes to TermAl’s SSE stream (/api/events) for state
changes, builds a digest when something meaningful happens, sends it to the
transport with action buttons, and maps inbound replies back to TermAl REST
calls (approve, send prompt, stop).
The TermAl backend needs a digest builder that collapses rich session state into:
The digest builder should prefer deterministic rules over a second LLM call. This logic lives in the backend regardless of transport.
A thin adapter per transport that:
Suggested actions should resolve to one of:
This keeps mobile interactions shallow while still allowing user control.
Many states should end with a link back to the full app.
Examples:
The mobile channel should accelerate awareness and steering, not replace the main review surface.
The first pass can be implemented by a transport adapter using existing APIs, but a cleaner backend contract would be:
GET /api/projects/{id}/digestReturns a compact project summary with:
POST /api/projects/{id}/actions/{action_id}Executes a suggested action such as:
This avoids pushing too much state interpretation into the transport adapter.
GET /api/projects/{id}/eventsOptional later endpoint for project-scoped event aggregation if session-level SSE becomes too low-level for the relay.
done, status, and next from project state| Dimension | PWA Push | Telegram Bot | ntfy | WhatsApp Business |
|---|---|---|---|---|
| Cost | Free | Free | Free | $0.01–$0.13/msg + BSP |
| Setup time | Minutes | 60 seconds | Minutes | Days–weeks |
| Approval / verification | Browser permission | None | None | Meta Business verification + template approval |
| Action buttons | Notification actions | Inline keyboard | URL / HTTP actions | Quick replies only |
| Bidirectional | No (tap opens app) | Yes (full thread) | No (push only) | Yes (24h window) |
| Edit sent messages | No | Yes | No | No |
| Free-text replies | No | Yes | No | Only within 24h |
| External gateway | No | No (long polling) | No | Yes (public HTTPS) |
| Change message format | Instant | Instant | Instant | Resubmit template |
| Best for | Phase 0 — zero deps | Phase 1 — full steering | Self-hosted push | Teams / multi-user |