feat(telegram-remote): v0 operator gateway over Coordinator MCP (#681)#699
feat(telegram-remote): v0 operator gateway over Coordinator MCP (#681)#699Yeachan-Heo wants to merge 2 commits into
Conversation
Add @gajae-code/telegram-remote, a tiny, safe command + bounded-read
operator surface for gjc session lifecycle/observation from Telegram. It
maps a fixed five-command vocabulary onto Coordinator MCP tool calls and
introduces no second control protocol.
- Default-deny auth; identical boring refusal for unlisted senders.
- Preset-only creation (fixed workdir + session command + single
length-capped, control-char-stripped {{task}} slot); no chat-derived
workdir/command.
- Fail-closed mutations forced to the smallest set (sessions[,reports]);
questions never enabled; allow_mutation only on the mutating call.
- Redaction by construction: typed field-by-field projection only; ISO
timestamp validation; never raw tail/transcripts/tool IO/secrets/paths.
- /stop arms then requires '/stop <id> confirm', records coordinator
cancelled status, and fails closed on offline sessions.
- MCP stdio coordinator client, Bot API long-poll transport, env config
loader, runnable service entrypoint, and a safety smoke example.
Implements the v0 contract in docs/telegram-remote.md (cross-linked).
Refs #681.
…cks) (#681) Add optional rich messaging as a presentation + alternate-entry layer over the same Coordinator MCP surface — no second control protocol, no Telegram-side event/notification system. Implements the ralplan consensus-approved plan. - Reply contract evolves from Promise<string> to OutgoingReply (ChatReply | CallbackAnswerOnlyReply) over IncomingUpdate (text + callback). - Inline keyboards: Observe / Stop / Refresh on /sessions and /observe; Confirm stop / Cancel on /stop. HTML formatting with render-boundary escaping. - Callback queries reuse the same default-deny auth and the same CoordinatorClient -> Coordinator MCP calls as text commands. callback_data is an opaque gtr:v1:<token> (<=64 bytes, never the session id); the exact raw session id lives in TTL-bound, chat/user-bound, single-use server-side token metadata. - Every callback is answered (answerCallbackQuery, finally-style, no reply-text string matching); unauthorized/expired/malformed/missing-chat/replayed/cancel callbacks are answer-only (no chat message, no backend call). Cancel revokes the paired stop_confirm token so Cancel-then-Confirm cannot mutate. - setMyCommands menu (sessions/observe/stop/help/start; excludes hyphenated /start-session); /start onboarding; optional editMessageText (default off, safe fallback). New config knobs with safe defaults; plain-text mode preserved. - Push notifications deferred: rich UI does not proactively notify; future push must reuse gjc_coordinator_watch_events, not a Telegram-side poller. - Tests: telegram.test.ts (transport, injected fetch) + gateway/projection/config callback + redaction + exact-raw-id coverage; safety-smoke extended with callback adversarial invariants. README/CHANGELOG/.env.example/docs updated. Refs #681.
Merge-gate red-team review — PR #699Verdict: OWNER_CONFIRMATION_REQUIRED No blocking security defect was found, but this PR introduces a brand-new, user-facing remote session-control surface over a public chat platform (Telegram), it is still a draft, and the green CI does not actually exercise this package. Those are owner-decision factors, so I am not declaring a flat What I verified (holds up)The core safety boundaries are correctly implemented and genuinely enforced coordinator-side, not just asserted:
Why owner confirmation (not auto-merge)
Minor / non-blocking (no fix required to proceed)
Net: high-quality, careful security design with no defect that lets an unauthorized party gain capability. The merge decision is yours given the draft status, the missing package CI gate, and the user-facing/security/product-contract items (3) and (4). — |
Summary
Implements the v0 Telegram Remote for Gajae-Code sessions (issue #681), per the
contract fixed in
docs/telegram-remote.md. Adds a new dependency-light companionpackage
@gajae-code/telegram-remote— a tiny, safe command + bounded-read operatorgateway over the Coordinator MCP for session lifecycle and observation. It is not
a core
gjcmode, not a remote shell/RPC cockpit, and introduces no secondcontrol protocol (it speaks MCP JSON-RPC to a
gjc mcp-serve coordinatorsubprocess).Opened as a draft for review of the v0 surface before wiring a live bot.
Command contract
/sessions,/observe <sessionId>,/start-session <presetId> [task],/stop <sessionId>,/help. Everything else is rejected as unknown.Safety properties (all enforced + tested)
senders get an identical, boring refusal (no hints, no preset enumeration).
template with a single length-capped, control-char-stripped
{{task}}slot. Noworkdir/command/branch/repo/shell/raw-RPC ever comes from chat.
sessions, plusreportsonly when/stopis enabled);questionsis never enabled;allow_mutationis passed only on the specific mutating call.
name, bounded status enum, branch, ISO-validated timestamps, bounded turn/lifecycle enum,
short sanitized blocker). Never raw tmux tail/scrollback, transcripts, tool IO, diffs,
file contents, env, system prompt, tokens/secrets, or absolute paths.
/stopconfirmation + fail-closed offline./stop <id>arms;/stop <id> confirmrecords the coordinator terminal
cancelledstatus (not a process kill). Offlinesessions are refused before any arm/report.
What's included
CoordinatorClientport +McpStdioCoordinatorClient(spawnsgjc mcp-serve coordinator).(
gjc-telegram-remote), README, CHANGELOG,.env.example.docs/telegram-remote.md.Verification
bun testin the package: 61 pass / 0 fail (default-deny, command parsing/unknownrejection, preset resolution + workdir/command/task injection rejection, task cap +
control-char stripping, redaction allowlist incl. hostile timestamps,
/stopconfirmation + offline fail-closed, failure-state mapping, real-subprocess MCP JSON-RPC,
and an end-to-end loop through a fake transport + fake coordinator).
tsgotypecheck andbiomeclean for the package.examples/safety-smoke.tsasserts the adversarial invariants with no bottoken/network and prints a deterministic success line.
Non-goals (v0)
No submit surface, no remote process teardown, no transcript/tail/secret dumping, no shell
or config editor, no answering of PC-side gates. See
docs/telegram-remote.mdfor deferreddecisions.
Refs #681.