@sojaner/telex 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { loadConfig, pickBot } from "./config.js";
6
+ import { sessionFor } from "./telegram.js";
7
+ import { ask, receipt, markExpired, refuse, heartbeat, deliver } from "./ask.js";
8
+ import { touch, release } from "./registry.js";
9
+ const config = loadConfig();
10
+ const botNames = Object.keys(config.bots);
11
+ // A project pins its bot with TELEX_BOT in the MCP registration; the agent can still override per call.
12
+ const defaultBot = process.env.TELEX_BOT || config.defaultBot;
13
+ /** Set once the agent has identified itself; until then telex has no interval and no owner. */
14
+ let checkedIn = false;
15
+ /** Listen to a bot's chat for messages nobody asked for, and acknowledge each one to the user. */
16
+ function watch(bot) {
17
+ const session = sessionFor(bot.token);
18
+ session.watch(bot.chatId, {
19
+ allowFrom: bot.allowFrom,
20
+ accept: () => checkedIn,
21
+ onRefused: (message) => void refuse(session, bot.chatId, message),
22
+ onQueued: (message) => void receipt(session, bot.chatId, message),
23
+ onExpired: (messages) => markExpired(session, bot.chatId, messages),
24
+ });
25
+ return session;
26
+ }
27
+ const warned = new Set();
28
+ /**
29
+ * Every call says who is calling. That fixes the expiry clock from the very first interaction and,
30
+ * through the shared registry, lets separate telex processes notice they share a bot.
31
+ */
32
+ function checkIn(botName, bot, caller) {
33
+ const session = watch(bot);
34
+ session.setInboxTtl(bot.chatId, caller.interval_seconds * MISSED_BEATS * 1000);
35
+ checkedIn = true;
36
+ const others = touch({
37
+ bot: botName,
38
+ project: caller.project_path,
39
+ agent: caller.agent,
40
+ pid: process.pid,
41
+ interval_seconds: caller.interval_seconds,
42
+ });
43
+ for (const other of others) {
44
+ const key = `${other.pid}:${other.project}`;
45
+ if (warned.has(key))
46
+ continue;
47
+ warned.add(key);
48
+ void conflictWarning(session, bot, caller, other);
49
+ }
50
+ return session;
51
+ }
52
+ /** Telegram hands each update to one poller only, so a shared bot silently loses half the traffic. */
53
+ function conflictWarning(session, bot, mine, other) {
54
+ return session.api("sendMessage", {
55
+ chat_id: bot.chatId,
56
+ parse_mode: "HTML",
57
+ text: [
58
+ "⚠️ <b>Two agents are using this bot at once</b>",
59
+ "",
60
+ `<code>${mine.agent}</code> — <code>${mine.project_path}</code>`,
61
+ `<code>${other.agent}</code> — <code>${other.project}</code>`,
62
+ "",
63
+ "Telegram gives each message to only one of them, so answers will go missing.",
64
+ "Give each project its own bot: <code>telex add &lt;name&gt;</code>.",
65
+ ].join("\n"),
66
+ }).catch(() => { });
67
+ }
68
+ const server = new McpServer({ name: "telex", version: "0.1.0" });
69
+ /** Heartbeats a message may sit through before telex gives up on the agent's behalf. */
70
+ const MISSED_BEATS = 3;
71
+ /** Identity every call carries, so telex knows who is on the other end and how often to expect them. */
72
+ const identity = {
73
+ project_path: z.string().min(1).describe("Absolute path of the project you are working in."),
74
+ agent: z.string().min(1).describe(`Your name, e.g. "claude-code", "codex", "gemini-cli".`),
75
+ interval_seconds: z.number().int().min(10).max(3600).default(60)
76
+ .describe(`How often you call heartbeat. Messages expire after ${3} missed intervals, so be honest.`),
77
+ };
78
+ const json = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }] });
79
+ server.registerTool("send_to_user", {
80
+ title: "Message the user on Telegram",
81
+ description: [
82
+ "Send a message to the user via Telegram and optionally wait for their answer.",
83
+ "Use it to ask for a decision, get missing information, or report that long-running work finished.",
84
+ "",
85
+ `Formatting: 'message' is rendered with Telegram's HTML subset — <b>, <i>, <u>, <s>, <code>, <pre>, <a href="">, <blockquote>.`,
86
+ "Escape any literal &, < and > as &amp; &lt; &gt;. Markdown is NOT rendered.",
87
+ "",
88
+ "Pass 'options' for a multiple-choice question (buttons), or 'expect_text' for a free-text answer.",
89
+ "With neither, the message is a one-way notification and returns immediately.",
90
+ "",
91
+ `If the user does not answer within 'timeout_seconds' the result is {"status":"timeout"}, the buttons`,
92
+ "are removed and the message is marked stale. That means the user is unavailable — decide for yourself",
93
+ "whether to continue, retry or stop.",
94
+ "",
95
+ "If the user messaged the bot while you were not asking anything, the result also carries a 'pending'",
96
+ "array of what they said, delivered the same way a heartbeat would. Read it before you carry on.",
97
+ "",
98
+ "'project_path', 'agent' and 'interval_seconds' identify you. Until a call carries them telex has",
99
+ "no owner for this bot, and anything the user sends is refused rather than held.",
100
+ `Bots: ${botNames.join(", ")} (default for this project: ${defaultBot}).`,
101
+ ].join("\n"),
102
+ inputSchema: {
103
+ ...identity,
104
+ project: z.string().min(1).describe("Project or task name, shown as the message heading."),
105
+ message: z.string().min(1).describe("The copy to show the user, in Telegram HTML."),
106
+ options: z.array(z.string().min(1)).min(1).max(10).optional()
107
+ .describe("Single-choice answers, rendered as buttons. Mutually exclusive with expect_text."),
108
+ expect_text: z.boolean().optional()
109
+ .describe("Show a Reply button; the user's next message is returned as the answer."),
110
+ timeout_seconds: z.number().int().min(5).max(86400).default(300)
111
+ .describe("How long to wait for an answer before giving up."),
112
+ bot: z.enum(botNames).optional()
113
+ .describe(`Which configured bot to send through. Defaults to "${defaultBot}".`),
114
+ },
115
+ }, async ({ project, message, options, expect_text, timeout_seconds, bot, ...caller }) => {
116
+ const { name: targetName, bot: target } = pickBot(config, bot);
117
+ const session = checkIn(targetName, target, caller);
118
+ const result = await ask(session, target.chatId, {
119
+ project,
120
+ message,
121
+ options,
122
+ expectText: expect_text,
123
+ timeoutSeconds: timeout_seconds,
124
+ allowFrom: target.allowFrom,
125
+ });
126
+ const pending = deliver(session, target.chatId);
127
+ return json(pending.length ? { ...result, pending } : result);
128
+ });
129
+ server.registerTool("heartbeat", {
130
+ title: "Check in and collect anything the user said",
131
+ description: [
132
+ "Call this on a fixed interval for the whole time you are working, and pass that interval as",
133
+ "'interval_seconds'. It returns immediately with whatever the user has messaged the bot since",
134
+ "your last check-in — it never blocks and never waits for them.",
135
+ "",
136
+ "Two jobs in one call. It delivers unprompted messages, and it is your liveness signal: the",
137
+ "user sees each of their messages marked as held, then delivered once a heartbeat collects it.",
138
+ `Miss ${MISSED_BEATS} intervals in a row and anything waiting is marked expired and dropped, which is how`,
139
+ "the user learns you were not listening rather than being ignored in silence.",
140
+ "",
141
+ `The result is {"messages":[...],"interval_seconds":n}. An empty array means nothing was said —`,
142
+ "that is the normal case, keep working and check in again next interval.",
143
+ "",
144
+ "'project_path' and 'agent' identify you. telex records them so it can warn the user when two",
145
+ "projects end up sharing one bot, which silently costs them half their messages.",
146
+ `Bots: ${botNames.join(", ")} (default for this project: ${defaultBot}).`,
147
+ ].join("\n"),
148
+ inputSchema: {
149
+ ...identity,
150
+ bot: z.enum(botNames).optional()
151
+ .describe(`Which configured bot to listen on. Defaults to "${defaultBot}".`),
152
+ },
153
+ }, async ({ bot, ...caller }) => {
154
+ const { name: targetName, bot: target } = pickBot(config, bot);
155
+ const messages = heartbeat(checkIn(targetName, target, caller), target.chatId);
156
+ return json({ messages, interval_seconds: caller.interval_seconds });
157
+ });
158
+ // Start listening before any agent checks in, so early messages get refused rather than ignored.
159
+ watch(pickBot(config).bot);
160
+ const transport = new StdioServerTransport();
161
+ // Polling now outlives any single request, so the process has to be told when the agent is gone.
162
+ transport.onclose = () => {
163
+ release();
164
+ process.exit(0);
165
+ };
166
+ process.on("exit", () => release());
167
+ await server.connect(transport);
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Which agents are alive, on disk, so separate telex processes can see each other.
3
+ * Each project's agent runs its own server; only a shared file can tell them apart.
4
+ */
5
+ import { readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, join } from "node:path";
8
+ export const statePath = () => process.env.TELEX_STATE ??
9
+ join(process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state"), "telex", "sessions.json");
10
+ const read = () => {
11
+ try {
12
+ const parsed = JSON.parse(readFileSync(statePath(), "utf8"));
13
+ return Array.isArray(parsed?.sessions) ? parsed.sessions : [];
14
+ }
15
+ catch {
16
+ return []; // a missing or corrupt file just means nothing is registered
17
+ }
18
+ };
19
+ const write = (sessions) => {
20
+ const path = statePath();
21
+ mkdirSync(dirname(path), { recursive: true });
22
+ // ponytail: write-rename, no lock. Two agents registering in the same millisecond can lose one
23
+ // record; it reappears on that agent's next call. Use a lockfile if that ever matters.
24
+ const tmp = `${path}.${process.pid}.tmp`;
25
+ writeFileSync(tmp, `${JSON.stringify({ sessions }, null, 2)}\n`, { mode: 0o600 });
26
+ renameSync(tmp, path);
27
+ };
28
+ const running = (pid) => {
29
+ try {
30
+ process.kill(pid, 0);
31
+ return true;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ };
37
+ /** A session counts as alive while its process exists and it has checked in within three intervals. */
38
+ export function isLive(session, now) {
39
+ const grace = Math.max(session.interval_seconds * 3, 60) * 1000;
40
+ return running(session.pid) && now - Date.parse(session.last_seen) <= grace;
41
+ }
42
+ /**
43
+ * Record this agent and return the other live agents on the same bot. Two of them polling one
44
+ * token split the updates between themselves, so the caller warns the user about the ones returned.
45
+ */
46
+ export function touch(entry, now = Date.now()) {
47
+ const stamp = new Date(now).toISOString();
48
+ const others = read().filter((s) => !(s.pid === entry.pid && s.bot === entry.bot) && isLive(s, now));
49
+ const mine = read().find((s) => s.pid === entry.pid && s.bot === entry.bot);
50
+ write([...others, { ...entry, started_at: mine?.started_at ?? stamp, last_seen: stamp }]);
51
+ return others.filter((s) => s.bot === entry.bot);
52
+ }
53
+ /** Drop this process's record. Best effort — a crash is covered by the liveness check instead. */
54
+ export function release(pid = process.pid) {
55
+ try {
56
+ write(read().filter((s) => s.pid !== pid));
57
+ }
58
+ catch {
59
+ // nothing useful to do while exiting
60
+ }
61
+ }
62
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
package/dist/setup.js ADDED
@@ -0,0 +1,125 @@
1
+ /** Interactive bot onboarding: validate a token, learn the chat id by watching for a message. */
2
+ import { createInterface } from "node:readline/promises";
3
+ import { apiFor } from "./telegram.js";
4
+ import { readConfig, writeConfig, configPath } from "./config.js";
5
+ import { agents, runCli, writeFileConfig, ensureGitignored } from "./agents.js";
6
+ const say = (s = "") => console.log(s);
7
+ export async function addBotInteractive(preferredName) {
8
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
9
+ try {
10
+ say("1. Open https://t.me/BotFather, send /newbot and follow the prompts.");
11
+ say(" BotFather replies with a token like 8123456789:AAH...\n");
12
+ const token = (await rl.question("Paste the bot token: ")).trim();
13
+ const api = apiFor(token);
14
+ const me = await api("getMe", {}).catch(() => {
15
+ throw new Error("Telegram rejected that token. Copy the whole line BotFather sent, including the digits before the colon.");
16
+ });
17
+ say(`\n✓ Token belongs to @${me.username}\n`);
18
+ say(`2. Open https://t.me/${me.username} and send it any message (say "hi").`);
19
+ say(" Waiting...");
20
+ const { chatId, userId, chatType } = await waitForFirstMessage(api);
21
+ say(`\n✓ Chat id ${chatId}${chatType === "private" ? "" : ` (${chatType})`}, your user id ${userId}\n`);
22
+ const config = readConfig();
23
+ const fallback = preferredName ?? (Object.keys(config.bots).length ? "" : "main");
24
+ const name = preferredName ?? ((await rl.question(`Name for this bot${fallback ? ` [${fallback}]` : ""}: `)).trim() || fallback);
25
+ if (!name)
26
+ throw new Error("a name is required");
27
+ const bot = { token, chatId, allowFrom: [userId] };
28
+ config.bots[name] = bot;
29
+ config.defaultBot ??= name;
30
+ writeConfig(config);
31
+ say(`✓ Wrote ${configPath()} (default bot: ${config.defaultBot})`);
32
+ await api("sendMessage", {
33
+ chat_id: chatId,
34
+ text: `<b>telex</b>\n\nSetup complete — bot "${name}" is ready.`,
35
+ parse_mode: "HTML",
36
+ });
37
+ say("✓ Sent a test message; check Telegram.\n");
38
+ return name;
39
+ }
40
+ finally {
41
+ rl.close();
42
+ }
43
+ }
44
+ /** Long-poll until the user messages the bot, skipping whatever was already queued. */
45
+ async function waitForFirstMessage(api) {
46
+ const seen = await api("getUpdates", { offset: -1, timeout: 0 });
47
+ let offset = seen.length ? seen[seen.length - 1].update_id + 1 : 0;
48
+ for (;;) {
49
+ const updates = await api("getUpdates", { offset, timeout: 30, allowed_updates: ["message"] });
50
+ for (const u of updates) {
51
+ offset = Math.max(offset, u.update_id + 1);
52
+ const msg = u.message;
53
+ if (msg)
54
+ return { chatId: msg.chat.id, userId: msg.from?.id ?? msg.chat.id, chatType: msg.chat.type };
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Install the MCP registration into the current project: confirm the directory, pick an
60
+ * agent, then let that agent's CLI do it — or write the config file ourselves when it has none.
61
+ */
62
+ export async function installConfig(bot, flags) {
63
+ const dir = process.cwd();
64
+ const list = agents();
65
+ const entry = { command: "telex", args: ["serve"], ...(bot ? { env: { TELEX_BOT: bot } } : {}) };
66
+ if (flags.scope && flags.scope !== "local" && flags.scope !== "project")
67
+ throw new Error(`--scope takes "local" or "project"`);
68
+ const interactive = !flags.agent && process.stdin.isTTY;
69
+ if (!flags.agent && !interactive)
70
+ throw new Error(`not a terminal — pass --agent <${list.map((a) => a.id).join("|")}> (or --print to just see the config)`);
71
+ const rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : undefined;
72
+ try {
73
+ let agent;
74
+ if (flags.agent) {
75
+ agent = list.find((a) => a.id === flags.agent) ?? (() => { throw new Error(`unknown agent "${flags.agent}". Known: ${list.map((a) => a.id).join(", ")}`); })();
76
+ }
77
+ else {
78
+ if (!flags.yes) {
79
+ const answer = (await rl.question(`Install the telex MCP config in ${dir}? [Y/n] `)).trim().toLowerCase();
80
+ if (answer && !answer.startsWith("y"))
81
+ return say("Nothing written. cd to the project you want and run it there.");
82
+ }
83
+ say();
84
+ list.forEach((a, i) => say(` ${String(i + 1).padStart(2)}. ${a.label}`));
85
+ const pick = Number((await rl.question(`\nWhich agent? [1-${list.length}] `)).trim());
86
+ if (!Number.isInteger(pick) || pick < 1 || pick > list.length)
87
+ throw new Error("pick one of the listed numbers");
88
+ agent = list[pick - 1];
89
+ }
90
+ if (agent.cli) {
91
+ const argv = agent.cli(bot);
92
+ const result = runCli(argv, dir);
93
+ if (result)
94
+ return report(result, agent);
95
+ if (!agent.file)
96
+ throw new Error(`${agent.label}'s CLI ("${argv[0]}") is not on PATH, and it has no config file telex can write.`);
97
+ say(`! ${argv[0]} is not on PATH — writing ${agent.file} instead.\n`);
98
+ }
99
+ let local = flags.scope === "local";
100
+ if (agent.localFile && !flags.scope) {
101
+ if (!interactive)
102
+ throw new Error(`${agent.label} needs --scope local (gitignored) or --scope project (committed)`);
103
+ say(`\n 1. Committed ${agent.file} — shared with the team`);
104
+ say(` 2. Local only ${agent.localFile} — gitignored, just you`);
105
+ local = (await rl.question(`\nWhich one? [1-2] `)).trim() === "2";
106
+ }
107
+ const result = writeFileConfig(agent, entry, dir, local);
108
+ const ignored = local && agent.localFile ? ensureGitignored(dir, agent.localFile) : null;
109
+ report(result, agent);
110
+ if (ignored)
111
+ say(`✓ Added ${agent.localFile} to ${ignored}`);
112
+ }
113
+ finally {
114
+ rl?.close();
115
+ }
116
+ }
117
+ function report(result, agent) {
118
+ say(`${result.ok ? "✓" : "✗"} ${agent.label}: ${result.how}`);
119
+ if (result.output)
120
+ for (const line of result.output.split("\n"))
121
+ say(` ${line}`);
122
+ if (!result.ok)
123
+ process.exitCode = 1;
124
+ }
125
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,205 @@
1
+ /** Thin Telegram Bot API client plus an on-demand long-poll loop per bot token. */
2
+ export class TelegramError extends Error {
3
+ method;
4
+ description;
5
+ code;
6
+ constructor(method, description, code = 0) {
7
+ super(`telegram ${method} failed: ${description}`);
8
+ this.method = method;
9
+ this.description = description;
10
+ this.code = code;
11
+ }
12
+ }
13
+ /** Bot tokens appear in API URLs, so they leak through raw fetch errors. */
14
+ export const redactToken = (s, token) => (token ? s.split(token).join("<token>") : s);
15
+ export function apiFor(token) {
16
+ return async function call(method, params, attempt = 0) {
17
+ let body;
18
+ try {
19
+ const res = await fetch(`https://api.telegram.org/bot${token}/${method}`, {
20
+ method: "POST",
21
+ headers: { "content-type": "application/json" },
22
+ body: JSON.stringify(params),
23
+ });
24
+ body = (await res.json());
25
+ }
26
+ catch (err) {
27
+ throw new Error(redactToken(`telegram ${method} request failed: ${err.message}`, token));
28
+ }
29
+ if (body.ok)
30
+ return body.result;
31
+ const retryAfter = body.parameters?.retry_after;
32
+ if (retryAfter !== undefined && attempt < 2) {
33
+ await new Promise((r) => setTimeout(r, (retryAfter + 1) * 1000));
34
+ return call(method, params, attempt + 1);
35
+ }
36
+ throw new TelegramError(method, redactToken(body.description ?? "unknown error", token), 0);
37
+ };
38
+ }
39
+ /** Telegram's HTML subset only needs these three escaped in text nodes. */
40
+ export const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
41
+ /** Telegram counts message length in UTF-16 code units, which is what JS strings already are. */
42
+ export const MAX_MESSAGE_LEN = 4000;
43
+ /** An agent that never checks in must not grow the inbox without bound; oldest go first. */
44
+ const INBOX_LIMIT = 50;
45
+ /**
46
+ * One session per bot token. Polling runs while a question is open and, once a chat is watched,
47
+ * for the rest of the process's life — a project that is not running simply never answers.
48
+ */
49
+ export class BotSession {
50
+ api;
51
+ pollTimeout;
52
+ offset = 0;
53
+ backlogSkipped = false;
54
+ looping = false;
55
+ callbackWaiters = new Map();
56
+ textWaiters = new Map();
57
+ watched = new Map();
58
+ inbox = new Map();
59
+ ttl = new Map();
60
+ listening = false;
61
+ constructor(api, pollTimeout = 30) {
62
+ this.api = api;
63
+ this.pollTimeout = pollTimeout;
64
+ }
65
+ onCallback(requestId, fn) {
66
+ this.callbackWaiters.set(requestId, fn);
67
+ this.ensureLoop();
68
+ return () => this.callbackWaiters.delete(requestId);
69
+ }
70
+ onText(chatId, fn) {
71
+ this.textWaiters.set(String(chatId), fn);
72
+ this.ensureLoop();
73
+ return () => this.textWaiters.delete(String(chatId));
74
+ }
75
+ /**
76
+ * Take an interest in a chat and keep polling for the life of the process. Being alive is what
77
+ * makes a project reachable: if nothing polls, nothing acknowledges, and nothing was delivered.
78
+ */
79
+ watch(chatId, options = {}) {
80
+ this.watched.set(String(chatId), options);
81
+ this.listening = true;
82
+ this.ensureLoop();
83
+ }
84
+ /** Stop the permanent poll. The loop still runs out whatever question is currently open. */
85
+ stop() {
86
+ this.listening = false;
87
+ this.watched.clear();
88
+ this.ttl.clear();
89
+ }
90
+ /**
91
+ * How long a message may sit unclaimed. Set from the agent's heartbeat interval, so until it
92
+ * checks in for the first time telex has no idea how long "too long" is and nothing expires.
93
+ */
94
+ setInboxTtl(chatId, ms) {
95
+ this.ttl.set(String(chatId), ms);
96
+ }
97
+ /** Hand over everything held for this chat. */
98
+ take(chatId) {
99
+ const key = String(chatId);
100
+ const queued = this.inbox.get(key) ?? [];
101
+ this.inbox.delete(key);
102
+ return queued;
103
+ }
104
+ get idle() {
105
+ return this.callbackWaiters.size === 0 && this.textWaiters.size === 0;
106
+ }
107
+ ensureLoop() {
108
+ if (this.looping)
109
+ return;
110
+ this.looping = true;
111
+ void this.loop().finally(() => (this.looping = false));
112
+ }
113
+ /**
114
+ * Messages the user sent before we asked anything must not be read as an answer,
115
+ * so the first poll of a process discards whatever is queued.
116
+ */
117
+ async skipBacklog() {
118
+ if (this.backlogSkipped)
119
+ return;
120
+ this.backlogSkipped = true;
121
+ const updates = await this.api("getUpdates", { offset: -1, timeout: 0 }).catch(() => []);
122
+ for (const u of updates)
123
+ this.offset = Math.max(this.offset, u.update_id + 1);
124
+ }
125
+ async loop() {
126
+ await this.skipBacklog();
127
+ while (this.listening || !this.idle) {
128
+ let updates;
129
+ try {
130
+ updates = await this.api("getUpdates", {
131
+ offset: this.offset,
132
+ timeout: this.pollTimeout,
133
+ allowed_updates: ["message", "callback_query"],
134
+ });
135
+ }
136
+ catch (err) {
137
+ if (isPollingConflict(err)) {
138
+ process.stderr.write("telex: getUpdates conflict — another process is polling this bot token. " +
139
+ "Give telex its own bot, or stop the other poller.\n");
140
+ }
141
+ await new Promise((r) => setTimeout(r, 1000));
142
+ continue;
143
+ }
144
+ for (const u of updates)
145
+ this.dispatch(u);
146
+ this.sweepInbox();
147
+ }
148
+ }
149
+ /** Exposed for tests: route one update to whoever is waiting for it. */
150
+ dispatch(u) {
151
+ this.offset = Math.max(this.offset, u.update_id + 1);
152
+ const cq = u.callback_query;
153
+ if (cq?.data?.startsWith("telex:")) {
154
+ const [, requestId, payload] = cq.data.split(":");
155
+ this.callbackWaiters.get(requestId)?.(payload ?? "", {
156
+ callbackId: cq.id,
157
+ fromId: cq.from?.id,
158
+ chatId: cq.message?.chat.id,
159
+ });
160
+ return;
161
+ }
162
+ const msg = u.message;
163
+ // Slash commands stay available to whatever else the user runs against this bot.
164
+ if (msg?.text === undefined || msg.text.startsWith("/"))
165
+ return;
166
+ const key = String(msg.chat.id);
167
+ const answering = this.textWaiters.get(key);
168
+ if (answering)
169
+ return answering(msg.text, msg.from?.id);
170
+ this.queue(key, msg);
171
+ }
172
+ /** Nobody asked for this one, so hold it rather than let it answer whatever gets asked next. */
173
+ queue(key, msg, now = Date.now()) {
174
+ const watch = this.watched.get(key);
175
+ if (!watch)
176
+ return;
177
+ if (watch.allowFrom?.length && !(msg.from && watch.allowFrom.includes(msg.from.id)))
178
+ return;
179
+ const message = { message_id: msg.message_id, from_id: msg.from?.id, text: msg.text, received_at: now };
180
+ if (watch.accept && !watch.accept())
181
+ return watch.onRefused?.(message);
182
+ this.inbox.set(key, [...(this.inbox.get(key) ?? []), message].slice(-INBOX_LIMIT));
183
+ watch.onQueued?.(message);
184
+ }
185
+ /**
186
+ * Drop whatever outlived the TTL. Runs on every poll, so a message expires even when the agent
187
+ * has stopped checking in entirely — which is exactly when the user most needs telling.
188
+ */
189
+ sweepInbox(now = Date.now()) {
190
+ for (const [key, held] of this.inbox) {
191
+ const ttl = this.ttl.get(key);
192
+ if (ttl === undefined)
193
+ continue;
194
+ const expired = held.filter((m) => now - m.received_at > ttl);
195
+ if (!expired.length)
196
+ continue;
197
+ this.inbox.set(key, held.filter((m) => now - m.received_at <= ttl));
198
+ this.watched.get(key)?.onExpired?.(expired);
199
+ }
200
+ }
201
+ }
202
+ export const isPollingConflict = (err) => err instanceof TelegramError && /conflict|terminated by other/i.test(err.description);
203
+ const sessions = new Map();
204
+ export const sessionFor = (token) => sessions.get(token) ?? (sessions.set(token, new BotSession(apiFor(token))), sessions.get(token));
205
+ //# sourceMappingURL=telegram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../src/telegram.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@sojaner/telex",
3
+ "version": "0.1.15",
4
+ "description": "MCP server that lets local AI agents ask users questions through Telegram bots",
5
+ "type": "module",
6
+ "bin": {
7
+ "telex": "dist/cli.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=22.6",
11
+ "pnpm": ">=11"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.20.0",
15
+ "zod": "^3.25.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^24.0.0",
19
+ "typescript": "^5.9.0"
20
+ },
21
+ "license": "MIT",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "config.example.json",
28
+ "README.md"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/Sojaner/telex.git"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json",
36
+ "telex": "node src/cli.ts",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "node --test test/*.test.ts"
39
+ }
40
+ }