feat: route all commands through the warm server — thin clients on shared ops (#45, Step 1)#48
Merged
Merged
Conversation
Make every Telegram and archive CLI command a thin client over the always-on control server. Each handler validates args, calls runOperation(op, args), and renders the result; the server executes the operation against its warm MTProto connection and open database as the single writer. - Simplify runOperation to ensureServer (auto-start) then invoke; remove the local-execution fallback and the need/lock/requireAuth params. The server owns services, auth, and locking. withCommand stays for the server's own startup, auth, and the remaining legacy sync-jobs/doctor commands. - Expand OPERATIONS to the full surface (channels, messages, send, media, topics, tags, metadata, contacts, groups, folders) and migrate the MCP tools onto the same handlers, so one implementation serves CLI and MCP. - Route send text/photo/file through the server, wrapping the existing send-utils retry / FLOOD_WAIT logic server-side. The control server relays a failed send's structured details so the CLI renders the same human/JSON error; the 30s default send timeout bounds the CLI's wait on the invoke. - Keep config, service, doctor, and auth local. Tests updated for the server-routed model and new ops; full suite green.
A fresh command after the server is down could fail with "Timed out waiting for the control server to start" even though the server came up, because startup blocked on the full Telegram connect + dialog refresh (tens of seconds on a large account) before the control listener bound and control.json was written. The client polls only ~10s for a ping, so it gave up first. Start the listeners first and connect lazily: - Bind the MCP/control listeners and write control.json before any Telegram work, so GET /control/ping succeeds within ~1s of spawn. - Connect the warm client in the background via a single shared promise (ensureTelegramConnected). The ensureLogin hook the control handlers and MCP tools run awaits that same promise, so the first operation — not the readiness probe — pays the connect cost, and concurrent callers share one connect. - Take the full dialog refresh (refreshChannelsFromDialogs over every dialog) off the readiness path: it, realtime sync, and pending-job resume now run in the background after connect, so a fresh connect is only login + the updates loop. Read ops do their own live fetch and no longer wait on a global dialog seed. The ping window stays at 10s and the cold read invoke budget stays at 30s, both comfortable now that the server is pingable immediately and a fresh connect is a few seconds. Add an integration test proving control.json is written and ping succeeds while the connect is still pending, and that an op blocks until it resolves.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Variant B, Step 1 of #45 — all Telegram/archive CLI commands are now thin clients over the warm server (auto-started), executing operation handlers shared with the MCP tools. The local execution fallback is removed.
Core
runOperationsimplified toensureServer(idleExit '60s') → invoke(op, args). No local fallback, noneed/lock/requireAuth— the server owns services, auth, and locking. (withCommandis retained for the server's own startup,auth, and the legacy sync handlers.)OPERATIONS— 44 ops covering the full surface (channels, messages, send, media, topics, tags, metadata, contacts, groups, folders). Message ops own the sharedsource: archive|live|bothlogic + archive→live fallback; the message-formatting helpers now live once inoperations.js(removed fromcli.jsandmcp-server.js).Send (single writer)
send text/photo/filerun server-side: the ops wrap the existingexecuteSendWithRetries(retry / FLOOD_WAIT / rate-limit) against the warm client. On failure,/control/invokeserializes theSendCommandError.detailsassendError, which the control-client re-throws so the CLI renders the identical human/--jsonerror. The 30s default bounds the client's wait on the invoke. (The--quiet"Connecting…" status is dropped — the client no longer connects;--quietstays honored.)Kept local
config,service,doctor, andauth(interactive login is the bootstrap: it writes the session locally; the warm server uses it).Scale
cli.jshandlers collapse to validate →runOperation→ format;mcp-server.js−483;operations.js+983. Net +330 with major consolidation.Known follow-ups (not blocking; mostly Step 2)
sync/backfill --once/--follow,backfill jobs ...) still runs locally — Step 2 retires it (server as sole writer,sync→shim) and closes Unify on a warm backend: route CLI commands through the server (thin clients); withCommand as the seam #45.logSendRetryis now unused (retries are server-side) — a small follow-up cleanup.authwhile a server is already running needs a server restart to pick up the new session (pre-existing; auth was always local).Tests
npm test→ 325 passing (312 + 13): the simplified seam (ensureServer/invoke, no fallback), the new ops registry, the send op's server-side retry/flood + thesendErrorrelay,/control/invokefor the new ops, and the migrated commands. Nonode_modulesin the commit; comments are behavioral (no history).